From 1a94e902f494403951fd3a4f49d82d8680a83a7c Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 4 Aug 2026 15:25:56 -0700 Subject: [PATCH 001/121] feature/AB#33563-AddPaymentScheduling --- .../Events/PaymentStatusChangedEvent.cs | 16 ++++ .../PaymentRequestAppService.cs | 38 ++++++++- .../CreateUpdateNotificationDto.cs | 2 + .../Notifications/NotificationDto.cs | 1 + .../ScheduledNotificationEventHandler.cs | 51 +++++++++++- .../AutomatedNotificationAppService.cs | 6 ++ .../Notifications/ScheduledNotification.cs | 2 + .../GrantTenantDbContext.cs | 1 + ...ModuleToScheduledNotifications.Designer.cs | 15 ++++ ...93000_AddModuleToScheduledNotifications.cs | 36 +++++++++ .../GrantTenantDbContextModelSnapshot.cs | 4 + .../FormNotificationsApiController.cs | 77 ++++++++++++++++--- .../Components/Notifications/Default.cshtml | 9 +++ .../Components/Notifications/Default.css | 42 +++++++++- .../Components/Notifications/Default.js | 67 ++++++++++++++-- 15 files changed, 344 insertions(+), 23 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/PaymentStatusChangedEvent.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/PaymentStatusChangedEvent.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/PaymentStatusChangedEvent.cs new file mode 100644 index 0000000000..b1e5f0de6b --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Events/PaymentStatusChangedEvent.cs @@ -0,0 +1,16 @@ +using System; +using Unity.Payments.Enums; + +namespace Unity.Payments.Events +{ + public class PaymentStatusChangedEvent + { + public Guid PaymentRequestId { get; set; } + + public Guid ApplicationId { get; set; } + + public PaymentRequestStatus Status { get; set; } + + public Guid? TenantId { get; set; } + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs index 6b3670d4cb..0e8b49e9a4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/PaymentRequestAppService.cs @@ -10,12 +10,14 @@ using Unity.Payments.Domain.Services; using Unity.Payments.Domain.Shared; using Unity.Payments.Enums; +using Unity.Payments.Events; using Unity.Payments.PaymentRequests.Notifications; using Unity.Payments.Permissions; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Data; +using Volo.Abp.EventBus.Local; using Volo.Abp.Features; using Volo.Abp.Users; @@ -31,7 +33,8 @@ public class PaymentRequestAppService( FsbPaymentNotifier fsbPaymentNotifier, IPaymentRequestQueryManager paymentRequestQueryManager, IPaymentRequestConfigurationManager paymentRequestConfigurationManager, - Lazy applicationLinksService) : PaymentsAppService, IPaymentRequestAppService + Lazy applicationLinksService, + ILocalEventBus localEventBus) : PaymentsAppService, IPaymentRequestAppService { public async Task GetDefaultAccountCodingId() @@ -60,6 +63,7 @@ public virtual async Task> CreateAsync(List> CreateHistoricalAsync(List GetNextBatchInfoAsync() { return await paymentRequestConfigurationManager.GetNextBatchInfoAsync(); @@ -212,6 +228,17 @@ public virtual async Task> UpdateStatusAsync(List CancelAsync(Guid paymentRequestId) .WithData("Status", payment.Status.ToString()); var result = await paymentsManager.CancelPaymentAsync(paymentRequestId); + + await localEventBus.PublishAsync(new PaymentStatusChangedEvent + { + PaymentRequestId = result.Id, + ApplicationId = result.CorrelationId, + Status = result.Status, + TenantId = CurrentTenant.Id + }); + return MapToPaymentRequestDto(result); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateUpdateNotificationDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateUpdateNotificationDto.cs index 9c8659e0d7..2c86cf9638 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateUpdateNotificationDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateUpdateNotificationDto.cs @@ -14,6 +14,8 @@ public class CreateUpdateNotificationDto [Required] public string TriggerType { get; set; } = "Event"; + public string? Module { get; set; } + public string? TriggerDetail { get; set; } public bool IsActive { get; set; } = true; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/NotificationDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/NotificationDto.cs index 815c3fadbe..f7071ef01f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/NotificationDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/NotificationDto.cs @@ -9,6 +9,7 @@ public class NotificationDto : EntityDto public Guid EmailTemplateId { get; set; } public string? TemplateName { get; set; } public string TriggerType { get; set; } = string.Empty; + public string? Module { get; set; } public string? TriggerDetail { get; set; } public bool IsActive { get; set; } public string? EventType { get; set; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ScheduledNotificationEventHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ScheduledNotificationEventHandler.cs index 82200085a5..5e742ea5a5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ScheduledNotificationEventHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ScheduledNotificationEventHandler.cs @@ -9,6 +9,7 @@ using Unity.Notifications.Events; using Unity.Notifications.Settings; using Unity.Notifications.Templates; +using Unity.Payments.Events; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus; @@ -34,7 +35,7 @@ internal class ScheduledNotificationEventHandler( ICurrentTenant currentTenant, ScheduledNotificationHelper scheduledNotificationHelper, ILogger logger) - : ILocalEventHandler, ITransientDependency + : ILocalEventHandler, ILocalEventHandler, ITransientDependency { public async Task HandleEventAsync(ApplicationChangedEvent eventData) { @@ -58,6 +59,7 @@ public async Task HandleEventAsync(ApplicationChangedEvent eventData) n => n.FormId == application.ApplicationFormId && n.TriggerType == "Event" && n.IsActive + && (n.Module == null || n.Module == "Application") && n.ApplicationStatusId == application.ApplicationStatusId)) .ToList(); @@ -83,6 +85,53 @@ public async Task HandleEventAsync(ApplicationChangedEvent eventData) } } + public async Task HandleEventAsync(PaymentStatusChangedEvent eventData) + { + if (!await featureChecker.IsEnabledAsync("Unity.Notifications")) + { + return; + } + + try + { + var application = await applicationRepository.GetAsync(eventData.ApplicationId, includeDetails: true); + if (application == null) + { + logger.LogWarning("ScheduledNotificationEventHandler: Application {ApplicationId} not found for payment {PaymentRequestId}.", + eventData.ApplicationId, eventData.PaymentRequestId); + return; + } + + var notifications = (await scheduledNotificationRepository.GetListAsync( + n => n.FormId == application.ApplicationFormId + && n.TriggerType == "Event" + && n.IsActive + && n.Module == "Payment" + && n.EventType == eventData.Status.ToString())) + .ToList(); + + if (notifications.Count == 0) + { + return; + } + + var defaultFromAddress = await settingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress); + string emailFrom = defaultFromAddress ?? "NoReply@gov.bc.ca"; + var applicantAgent = await applicantAgentRepository.FirstOrDefaultAsync(a => a.ApplicationId == application.Id); + + foreach (var notification in notifications) + { + await ProcessNotificationAsync(notification, application, applicantAgent, emailFrom); + } + } + catch (Exception ex) + { + logger.LogError(ex, + "ScheduledNotificationEventHandler: Error processing payment event for payment {PaymentRequestId}.", + eventData.PaymentRequestId); + } + } + private async Task ProcessNotificationAsync( ScheduledNotification notification, Application application, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Notifications/AutomatedNotificationAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Notifications/AutomatedNotificationAppService.cs index fba613275c..b7b5c0ac56 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Notifications/AutomatedNotificationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Notifications/AutomatedNotificationAppService.cs @@ -19,6 +19,7 @@ public async Task CreateAsync(CreateUpdateNotificationDto input FormId = input.FormId, EmailTemplateId = input.EmailTemplateId, TriggerType = input.TriggerType, + Module = input.Module, TriggerDetail = input.TriggerDetail, IsActive = input.IsActive, EventType = input.EventType, @@ -38,6 +39,7 @@ public async Task CreateAsync(CreateUpdateNotificationDto input EmailTemplateId = entity.EmailTemplateId, TemplateName = null, TriggerType = entity.TriggerType, + Module = entity.Module, TriggerDetail = entity.TriggerDetail, IsActive = entity.IsActive, EventType = entity.EventType, @@ -69,6 +71,7 @@ public async Task GetAsync(Guid id) EmailTemplateId = e.EmailTemplateId, TemplateName = null, TriggerType = e.TriggerType, + Module = e.Module, TriggerDetail = e.TriggerDetail, IsActive = e.IsActive, EventType = e.EventType, @@ -104,6 +107,7 @@ public async Task> GetListAsync(GetNotifications EmailTemplateId = e.EmailTemplateId, TemplateName = null, TriggerType = e.TriggerType, + Module = e.Module, TriggerDetail = e.TriggerDetail, IsActive = e.IsActive, EventType = e.EventType, @@ -122,6 +126,7 @@ public async Task UpdateAsync(Guid id, CreateUpdateNotification var e = await _repository.GetAsync(id); e.EmailTemplateId = input.EmailTemplateId; e.TriggerType = input.TriggerType; + e.Module = input.Module; e.TriggerDetail = input.TriggerDetail; e.IsActive = input.IsActive; e.EventType = input.EventType; @@ -140,6 +145,7 @@ public async Task UpdateAsync(Guid id, CreateUpdateNotification EmailTemplateId = e.EmailTemplateId, TemplateName = null, TriggerType = e.TriggerType, + Module = e.Module, TriggerDetail = e.TriggerDetail, IsActive = e.IsActive, EventType = e.EventType, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Notifications/ScheduledNotification.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Notifications/ScheduledNotification.cs index 6cd43e72fc..56d66838d9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Notifications/ScheduledNotification.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Notifications/ScheduledNotification.cs @@ -17,6 +17,8 @@ public class ScheduledNotification : FullAuditedAggregateRoot, IMultiTenan public string TriggerType { get; set; } = string.Empty; // Date or Event + public string? Module { get; set; } + public string? TriggerDetail { get; set; } public bool IsActive { get; set; } = true; 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 c77da4df0b..e6f20582d3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs @@ -426,6 +426,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.Property(x => x.FormId).IsRequired(); b.Property(x => x.EmailTemplateId).IsRequired(); b.Property(x => x.TriggerType).IsRequired().HasMaxLength(64); + b.Property(x => x.Module).HasMaxLength(64); b.Property(x => x.TriggerDetail).HasMaxLength(1000); b.Property(x => x.EventType).HasMaxLength(128); b.Property(x => x.ApplicationStatus).HasMaxLength(128); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.Designer.cs new file mode 100644 index 0000000000..3aeede4d72 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.Designer.cs @@ -0,0 +1,15 @@ +// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Unity.GrantManager.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20260804193000_AddModuleToScheduledNotifications")] + partial class AddModuleToScheduledNotifications + { + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.cs new file mode 100644 index 0000000000..e7a5e4a498 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260804193000_AddModuleToScheduledNotifications.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AddModuleToScheduledNotifications : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Module", + schema: "Notifications", + table: "ScheduledNotifications", + type: "character varying(64)", + maxLength: 64, + nullable: true); + + migrationBuilder.Sql(@" + UPDATE ""Notifications"".""ScheduledNotifications"" + SET ""Module"" = 'Application' + WHERE ""TriggerType"" = 'Event';"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Module", + schema: "Notifications", + table: "ScheduledNotifications"); + } + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 3df92980ba..22da5fd8cb 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -3110,6 +3110,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsActive") .HasColumnType("boolean"); + b.Property("Module") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("boolean") diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs index 1850dcb94e..7e6533d639 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs @@ -8,6 +8,7 @@ using Unity.Notifications.Emails; using Volo.Abp.Users; using Unity.GrantManager.Events; +using Unity.Payments.Enums; using Volo.Abp.Identity.Integration; namespace Unity.GrantManager.Web.Controllers @@ -40,6 +41,15 @@ public FormNotificationsApiController(IApplicationStatusService statusService, I _grantApplicationAppService = grantApplicationAppService; _scheduledNotificationHelper = scheduledNotificationHelper; } + + [HttpGet("payment-statuses")] + public ActionResult> GetPaymentStatuses() + { + var statuses = Enum.GetNames() + .Select(status => (object)new { id = status, internalStatus = status }) + .ToList(); + return Ok(statuses); + } // In-memory storage removed; persisting to ScheduledNotifications table via IAutomatedNotificationAppService @@ -245,8 +255,9 @@ public async Task>> GetForForm(strin TemplateId = e.EmailTemplateId, TemplateName = templateMap.TryGetValue(e.EmailTemplateId, out var t) && t != null ? t.Name : string.Empty, TriggerType = e.TriggerType, + Module = e.Module ?? (e.TriggerType == "Event" ? "Application" : null), DateType = e.DateField, - EventStatus = e.ApplicationStatus, + EventStatus = e.EventType ?? e.ApplicationStatus, ApplicationStatusId = e.ApplicationStatusId, RecipientCategory = e.RecipientCategory, RecipientIdentifier = e.RecipientIdentifier, @@ -262,11 +273,27 @@ public async Task> CreateForForm(string f { if (input.TemplateId == Guid.Empty) return BadRequest("TemplateId required"); + if (!ValidateModule(input, out var moduleError)) return BadRequest(moduleError); + if (string.Equals(input.TriggerType, "Event", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(input.RecipientIdentifier)) { return BadRequest("RecipientIdentifier required for Event trigger"); } + if (string.Equals(input.TriggerType, "Event", StringComparison.OrdinalIgnoreCase) && + string.Equals(input.Module, "Payment", StringComparison.OrdinalIgnoreCase) && + string.IsNullOrWhiteSpace(input.EventStatus)) + { + return BadRequest("EventStatus required for Event trigger"); + } + + if (string.Equals(input.TriggerType, "Event", StringComparison.OrdinalIgnoreCase) && + string.Equals(input.Module, "Application", StringComparison.OrdinalIgnoreCase) && + !input.ApplicationStatusId.HasValue) + { + return BadRequest("ApplicationStatusId required for Application event trigger"); + } + var template = await _templateService.GetTemplateById(input.TemplateId); if (template == null) return BadRequest("Template not found"); if (!Guid.TryParse(formId, out var parsedFormId)) return BadRequest("Invalid form id"); @@ -285,10 +312,11 @@ public async Task> CreateForForm(string f FormId = parsedFormId, EmailTemplateId = template.Id, TriggerType = input.TriggerType, - TriggerDetail = input.TriggerType == "Date" ? input.DateType : statusLabel, + Module = input.Module, + TriggerDetail = input.TriggerType == "Date" ? input.DateType : input.Module == "Payment" ? input.EventStatus : statusLabel, IsActive = true, - EventType = null, - ApplicationStatusId = input.ApplicationStatusId, + EventType = input.Module == "Payment" ? input.EventStatus : null, + ApplicationStatusId = input.Module == "Application" ? input.ApplicationStatusId : null, ApplicationStatus = statusLabel, DateField = input.DateType, RecipientCategory = input.RecipientCategory, @@ -303,8 +331,9 @@ public async Task> CreateForForm(string f TemplateId = input.TemplateId, TemplateName = template.Name, TriggerType = created.TriggerType, + Module = created.Module, DateType = created.DateField, - EventStatus = created.ApplicationStatus, + EventStatus = created.EventType ?? created.ApplicationStatus, ApplicationStatusId = created.ApplicationStatusId, RecipientCategory = created.RecipientCategory, RecipientIdentifier = created.RecipientIdentifier, @@ -355,6 +384,8 @@ public async Task> UpdateForForm(string f if (!Guid.TryParse(formId, out var parsedFormId)) return BadRequest("Invalid form id"); if (input.TemplateId == Guid.Empty) return BadRequest("TemplateId required"); + if (!ValidateModule(input, out var moduleError)) return BadRequest(moduleError); + var template = await _templateService.GetTemplateById(input.TemplateId); if (template == null) return BadRequest("Template not found"); @@ -371,10 +402,11 @@ public async Task> UpdateForForm(string f FormId = parsedFormId, EmailTemplateId = template.Id, TriggerType = input.TriggerType, - TriggerDetail = input.TriggerType == "Date" ? input.DateType : statusLabel, + Module = input.Module, + TriggerDetail = input.TriggerType == "Date" ? input.DateType : input.Module == "Payment" ? input.EventStatus : statusLabel, IsActive = true, - EventType = null, - ApplicationStatusId = input.ApplicationStatusId, + EventType = input.Module == "Payment" ? input.EventStatus : null, + ApplicationStatusId = input.Module == "Application" ? input.ApplicationStatusId : null, ApplicationStatus = statusLabel, DateField = input.DateType, RecipientCategory = input.RecipientCategory, @@ -389,8 +421,9 @@ public async Task> UpdateForForm(string f TemplateId = input.TemplateId, TemplateName = template.Name, TriggerType = updated.TriggerType, + Module = updated.Module, DateType = updated.DateField, - EventStatus = updated.ApplicationStatus, + EventStatus = updated.EventType ?? updated.ApplicationStatus, ApplicationStatusId = updated.ApplicationStatusId, RecipientCategory = updated.RecipientCategory, RecipientIdentifier = updated.RecipientIdentifier, @@ -399,6 +432,30 @@ public async Task> UpdateForForm(string f return Ok(dto); } + + private static bool ValidateModule(CreateScheduledNotificationInput input, out string error) + { + error = string.Empty; + if (!string.Equals(input.TriggerType, "Event", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.IsNullOrWhiteSpace(input.Module)) + { + error = "Module required for Event trigger"; + return false; + } + + if (!string.Equals(input.Module, "Application", StringComparison.OrdinalIgnoreCase) && + !string.Equals(input.Module, "Payment", StringComparison.OrdinalIgnoreCase)) + { + error = "Module must be Application or Payment"; + return false; + } + + return true; + } } public record EmailTemplateDto @@ -418,6 +475,7 @@ public record ScheduledNotificationDto public Guid TemplateId { get; init; } public string TemplateName { get; init; } = string.Empty; public string TriggerType { get; init; } = string.Empty; + public string? Module { get; init; } public string? DateType { get; init; } public string? EventStatus { get; init; } public Guid? ApplicationStatusId { get; init; } @@ -431,6 +489,7 @@ public record CreateScheduledNotificationInput { public Guid TemplateId { get; init; } public string TriggerType { get; init; } = "Date"; + public string? Module { get; init; } public string? DateType { get; init; } public Guid? ApplicationStatusId { get; init; } public string? EventStatus { get; init; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml index 061e8cd0f7..2cbf3eb2ad 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml @@ -84,6 +84,15 @@
+
+ + +
Please select a module.
+
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css index 2ed7dbd718..db488fafe0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css @@ -8,8 +8,18 @@ .notifications-widget .card { border: 0; } .notifications-widget .card .card-body { background: #fff; } -/* Select2 Bootstrap 5 Theme - Use default styling */ -/* Let Select2's Bootstrap 5 theme handle the layout naturally */ +/* Keep every notification form control aligned to the left column. */ +#notificationForm .left-col .form-select, +#notificationForm .left-col .form-control, +#notificationForm .left-col .select2, +#notificationForm .left-col .select2-container { + display: block; + width: 100% !important; + max-width: 100%; + box-sizing: border-box; +} + +/* Select2 Bootstrap 5 theme */ .select2-container--bootstrap-5 .select2-selection--multiple { min-height: 38px; height: auto; @@ -54,11 +64,13 @@ display: flex; flex: 1 1 auto; min-height: 0; + min-width: 0; } .left-col { - flex: 0 0 33%; + flex: 0 1 33%; min-width: 320px; + max-width: 100%; overflow-y: auto; } @@ -80,7 +92,8 @@ .notification-modal-content { display: flex; flex-direction: column; - min-width: 900px; + width: min(100%, 1200px); + min-width: min(900px, 100%); min-height: 480px; max-height: 85vh; } @@ -96,6 +109,27 @@ flex-shrink: 0; } +@media (max-width: 991.98px) { + #modalColumns { + flex-direction: column; + gap: 1.5rem; + } + + .left-col { + flex: 0 1 auto; + min-width: 0; + } + + .right-col { + min-height: 220px; + } + + .notification-modal-content { + min-width: 0; + width: 100%; + } +} + /* Notification info note styling */ .notification-info-note { background-color: #d1ecf1; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index ee3fdc4b0b..1f17a0e2d9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -137,6 +137,9 @@ function fetchStatuses() { return fetch('/api/form-notifications/statuses').then(r => r.json()); } + function fetchPaymentStatuses() { + return fetch('/api/form-notifications/payment-statuses').then(r => r.json()); + } function fetchRecipients(category) { return fetch('/api/form-notifications/recipients?category=' + encodeURIComponent(category)).then(r => r.json()); @@ -158,11 +161,19 @@ return detail; } + function renderTriggerType(data, type, row) { + if (row.triggerType === 'Event' && row.module) { + return 'Event - ' + row.module; + } + + return row.triggerType || ''; + } + function getNotificationColumns() { let index = 0; return [ { title: 'Template', name: 'templateName', data: 'templateName', visible: true, index: index++ }, - { title: 'Trigger Type', name: 'triggerType', data: 'triggerType', visible: true, index: index++ }, + { title: 'Trigger Type', name: 'triggerType', data: 'triggerType', visible: true, index: index++, render: renderTriggerType }, { title: 'Trigger Detail',name: 'triggerDetail',data: null, visible: true, orderable: true, defaultContent: '', index: index++, render: renderTriggerDetail }, { title: 'Status', name: 'status', data: 'isActive', visible: true, orderable: true, index: index++, @@ -295,7 +306,7 @@ if (modalEl) { modalEl.dataset.editId = row.id; } - document.getElementById('notificationModal')?.addEventListener('shown.bs.modal', function () { + document.getElementById('notificationModal')?.addEventListener('shown.bs.modal', async function () { const setVal = (id, val) => { document.getElementById(id).value = val ?? ''; }; @@ -326,7 +337,9 @@ const values = row.recipientIdentifier ? row.recipientIdentifier.split(',').map(v => v.trim()) : []; setSelectedRecipients(values); } else if (row.triggerType === 'Event') { - setVal('statusSelect', row.applicationStatusId); + setVal('moduleSelect', row.module); + await loadStatusesForModule(row.module); + setVal('statusSelect', row.applicationStatusId || row.eventStatus); setVal('recipientCategory', row.recipientCategory); // Set multiple values for recipient select const values = row.recipientIdentifier ? row.recipientIdentifier.split(',').map(v => v.trim()) : []; @@ -369,6 +382,25 @@ sel.appendChild(opt); }); } + async function loadStatusesForModule(module) { + const statusSelect = document.getElementById('statusSelect'); + if (!statusSelect) return; + + statusSelect.innerHTML = ''; + const blank = document.createElement('option'); + blank.value = ''; + blank.text = ''; + statusSelect.appendChild(blank); + statusSelect.disabled = !module; + + if (!module) return; + + const statuses = module === 'Payment' + ? await fetchPaymentStatuses() + : await fetchStatuses(); + populateStatuses(statuses); + statusSelect.disabled = false; + } function populateRecipients(list) { const sel = document.getElementById('recipientSelect'); @@ -480,9 +512,10 @@ function showModal() { resetValidationState(); - ['templateSelect', 'triggerType', 'dateType', 'statusSelect', 'recipientCategory'].forEach(id => { + ['templateSelect', 'triggerType', 'dateType', 'moduleSelect', 'statusSelect', 'recipientCategory'].forEach(id => { document.getElementById(id).value = ''; }); + document.getElementById('statusSelect').disabled = true; // Clear the recipient select clearSelectedRecipients(); @@ -506,7 +539,7 @@ const requiredAlways = ['templateSelect', 'triggerType']; const requiredForDate = ['dateType', 'recipientCategory', 'recipientSelect']; - const requiredForEvent = ['statusSelect', 'recipientCategory', 'recipientSelect']; + const requiredForEvent = ['moduleSelect', 'statusSelect', 'recipientCategory', 'recipientSelect']; const fieldsToValidate = [ ...requiredAlways, @@ -628,7 +661,7 @@ e.target.classList.remove('is-invalid'); updatePreview(); }); - ['dateType', 'statusSelect'].forEach(id => { + ['dateType', 'moduleSelect', 'statusSelect'].forEach(id => { document.getElementById(id)?.addEventListener('change', (e) => { e.target.classList.remove('is-invalid'); }); @@ -651,6 +684,13 @@ dateOptionsEl?.classList.add('hidden-section'); eventOptionsEl?.classList.remove('hidden-section'); recipientOptionsEl?.classList.remove('hidden-section'); + const moduleSelect = document.getElementById('moduleSelect'); + const statusSelect = document.getElementById('statusSelect'); + if (moduleSelect?.value) { + loadStatusesForModule(moduleSelect.value); + } else if (statusSelect) { + statusSelect.disabled = true; + } } else { dateOptionsEl?.classList.add('hidden-section'); eventOptionsEl?.classList.add('hidden-section'); @@ -660,6 +700,14 @@ e.target.classList.remove('is-invalid'); }); + document.getElementById('moduleSelect')?.addEventListener('change', (e) => { + e.target.classList.remove('is-invalid'); + loadStatusesForModule(e.target.value).catch(err => { + console.error('Failed to load module statuses', err); + abp.notify.error('Failed to load status triggers'); + }); + }); + document.getElementById('recipientCategory')?.addEventListener('change', (e) => { const cat = e.target.value; e.target.classList.remove('is-invalid'); @@ -693,19 +741,22 @@ const templateId = (document.getElementById('templateSelect').value || '').trim(); const dateType = document.getElementById('dateType').value; - const applicationStatusId = document.getElementById('statusSelect')?.value; + const module = document.getElementById('moduleSelect')?.value; + const statusValue = document.getElementById('statusSelect')?.value; const recipientCategory = document.getElementById('recipientCategory')?.value; // Collect multiple selected recipients as comma-separated string const recipientIdentifier = getSelectedRecipients().join(','); - const resolvedStatusId = triggerType === 'Event' ? (applicationStatusId || null) : null; + const resolvedStatusId = triggerType === 'Event' && module === 'Application' ? (statusValue || null) : null; const bodyObj = { templateId: templateId, triggerType: triggerType, + module: triggerType === 'Event' ? module : null, dateType: triggerType === 'Date' ? dateType : null, applicationStatusId: resolvedStatusId, + eventStatus: triggerType === 'Event' && module === 'Payment' ? (statusValue || null) : null, recipientCategory: recipientCategory, recipientIdentifier: recipientIdentifier }; From 3fa612f426752effff755ec0c4c05900ae8f81fa Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Wed, 5 Aug 2026 12:25:45 -0700 Subject: [PATCH 002/121] feature/AB#33652-ToolTip --- .../NotificationsSettingGroup/Default.css | 62 +++++++++++++++++-- .../NotificationsSettingGroup/Default.js | 12 +++- .../_TemplateDetails.cshtml | 16 ++++- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css index 14ef7a32e3..594aecbd47 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css @@ -1,4 +1,50 @@ -body { +.notification-tooltip { + background: transparent; + border: 0; + cursor: help; + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 20px; + height: 20px; + margin-left: 0.35rem; + padding: 0; + position: relative; + z-index: 2; + margin-top: -7px; +} + +.notification-tooltip-icon { + border: 2px solid rgb(46, 93, 215); + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + background-color: rgb(255, 255, 255); + color: rgb(46, 93, 215); + font-family: Georgia, serif; + font-size: 0.8rem; + font-weight: 700; + font-style: italic; + line-height: 22px; + transform: translateY(4px); +} + +.notification-tooltip:focus-visible { + outline: 2px solid #2e5dd7; + outline-offset: 2px; +} + +.notification-tooltip-popover .tooltip-inner { + font-size: 0.75rem; + line-height: 1.35; + max-width: 280px; + padding: 0.35rem 0.5rem; +} + +body { overflow-y:auto!important; } @@ -48,7 +94,7 @@ white-space: nowrap; transition: all 0.15s ease-in-out; border-radius: 4px; - font-size: 0.875rem; + font-size: 1rem; } .btn-add-user:hover:not(:disabled) { @@ -183,10 +229,16 @@ span.tooltip-wrapper { } .template-field { - flex: 0 0 135px !important; - min-width: 140px !important; + align-items: center; + box-sizing: border-box; + display: flex; + flex: 0 0 180px !important; + font-size: 0.975rem; + gap: 0.15rem; + min-width: 180px !important; white-space: nowrap; - margin: 0.5rem; + margin: 0.5rem 0.25rem 0.5rem 0.5rem; + width: 180px; } /* ── Drag ghost (prevent text selection while dragging) ───────────────────── */ diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js index 54e1360715..65b6569fec 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js @@ -1,4 +1,13 @@ - +function initializeTooltips() { + if (typeof bootstrap === 'undefined') return; + + document.querySelectorAll('#nav-template [data-bs-toggle="tooltip"]').forEach((tooltipElement) => { + bootstrap.Tooltip.getOrCreateInstance(tooltipElement, { + customClass: 'notification-tooltip-popover' + }); + }); +} + $(function () { const UiElements = { saveButton: $("#saveTemplateBtn"), @@ -23,6 +32,7 @@ $(function () { function init() { $('#email-attachments-section').hide(); + initializeTooltips(); initializeTemplateDataTables(); initializeDivider(); initializeTabPersistence(); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/_TemplateDetails.cshtml b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/_TemplateDetails.cshtml index e09b8ab96b..377a55840a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/_TemplateDetails.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/_TemplateDetails.cshtml @@ -29,7 +29,13 @@
- +
From 1d812356557ae52bc54327d124f20b5331dc4898 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Thu, 6 Aug 2026 11:42:06 -0700 Subject: [PATCH 003/121] feature/AB#33824-AlphabeticalTemplates --- .../Shared/Components/EmailsWidget/Default.js | 36 +++++++++++-------- .../Components/Notifications/Default.js | 16 +++++---- .../Components/Notifications/Notifications.js | 6 ++-- .../js/formConfiguration/Notifications.js | 6 ++-- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js index 167abe5906..a361aef984 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js @@ -912,25 +912,31 @@ $select.find('option').not($placeholder).remove(); const seenTemplateIds = new Set(); - templates.forEach((template) => { - const templateName = template.name || template.Name || 'Unnamed Template'; - const templateId = (template.id || template.Id || '').toString(); - if (!templateId || seenTemplateIds.has(templateId)) { - return; - } + [...templates] + .sort((left, right) => { + const leftName = (left.name || left.Name || 'Unnamed Template').trim(); + const rightName = (right.name || right.Name || 'Unnamed Template').trim(); + return leftName.localeCompare(rightName, undefined, { sensitivity: 'base' }); + }) + .forEach((template) => { + const templateName = template.name || template.Name || 'Unnamed Template'; + const templateId = (template.id || template.Id || '').toString(); + if (!templateId || seenTemplateIds.has(templateId)) { + return; + } - seenTemplateIds.add(templateId); + seenTemplateIds.add(templateId); - const $option = $('
- - - - - - - - + + + + + + + + -
+
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index e655747a58..90f11986d9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -999,54 +999,6 @@ UIElements.refreshAvailableWorksheetsHidden.val(data.chefsFormVersionId); } ); - - // AI Configuration tab - const btnSaveAIConfig = document.getElementById('btn-save-ai-config'); - const btnCancelAIConfig = document.getElementById('btn-cancel-ai-config'); - - if (btnSaveAIConfig) { - const aiFormId = document.getElementById('applicationFormId').value; - const automaticCheckbox = document.getElementById('AutomaticallyGenerateAIAnalysis'); - const manualCheckbox = document.getElementById('ManuallyInitiateAIAnalysis'); - - let lastSavedAIValues = { - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }; - - btnSaveAIConfig.addEventListener('click', function () { - btnSaveAIConfig.disabled = true; - abp.ajax({ - url: `/api/app/application-form/${aiFormId}/ai-config`, - type: 'PATCH', - data: JSON.stringify({ - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }), - contentType: 'application/json' - }) - .done(function () { - lastSavedAIValues = { - automaticallyGenerateAIAnalysis: automaticCheckbox ? automaticCheckbox.checked : false, - manuallyInitiateAIAnalysis: manualCheckbox ? manualCheckbox.checked : false - }; - abp.notify.success('AI configuration saved successfully.'); - }) - .fail(function () { - abp.notify.error('Failed to save AI configuration.'); - }) - .always(function () { - btnSaveAIConfig.disabled = false; - }); - }); - - if (btnCancelAIConfig) { - btnCancelAIConfig.addEventListener('click', function () { - if (automaticCheckbox) automaticCheckbox.checked = lastSavedAIValues.automaticallyGenerateAIAnalysis; - if (manualCheckbox) manualCheckbox.checked = lastSavedAIValues.manuallyInitiateAIAnalysis; - }); - } - } }); From ccf3725d814444adcf5985792ab3bcb513b4107e Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Mon, 10 Aug 2026 17:38:06 -0700 Subject: [PATCH 027/121] AB#33979 - Binded the back click handler with a closure-scoped callback --- .../Views/Shared/Components/AIConfiguration/Default.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js index 6638987fb8..8527f3422b 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js @@ -22,7 +22,9 @@ $(function () { function bindUIEvents() { UIElements.btnSave.on('click', handleSave); UIElements.btnCancel.on('click', handleCancel); - UIElements.btnBack.on('click', handleBack); + UIElements.btnBack.on('click', function () { + location.href = '/ApplicationForms'; + }); } function handleSave() { @@ -57,7 +59,3 @@ $(function () { UIElements.manualCheckbox.prop('checked', lastSavedAIValues.manuallyInitiateAIAnalysis); } }); - -function handleBack() { - location.href = '/ApplicationForms'; -} From 8a9d90a2f3dc5490ce3d28a8090d9d49db943b57 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 11 Aug 2026 07:56:27 -0700 Subject: [PATCH 028/121] [AB#33653] SQ Quality Fixes --- .../Applications/ApplicationListRecord.cs | 4 ++-- .../Pages/GrantApplications/Index.js | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs index 748e8a0ceb..8026f005c6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationListRecord.cs @@ -61,8 +61,8 @@ public class ApplicationListRecord // ApplicationStatus (always joined) public string Status { get; init; } = string.Empty; - public string ExternalStatus { get; set; } = string.Empty; - public string? PublishedStatus { get; set; } + public string ExternalStatus { get; init; } = string.Empty; + public string? PublishedStatus { get; init; } // ApplicationForm (always joined) public string Category { get; init; } = string.Empty; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js index eed3ad60b1..c8f2985a48 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Index.js @@ -819,7 +819,11 @@ $(function () { data: 'externalStatus', name: 'externalStatus', className: 'data-table-header', - index: columnIndex + index: columnIndex, + render: function (data, type) { + const value = data ?? ''; + return type === 'display' ? dtTextRenderer.display(value) : value; + } } } @@ -829,7 +833,11 @@ $(function () { data: 'publishedStatus', name: 'publishedStatus', className: 'data-table-header', - index: columnIndex + index: columnIndex, + render: function (data, type) { + const value = data ?? ''; + return type === 'display' ? dtTextRenderer.display(value) : value; + } } } From 092a990caa0a876d368a37d5c1aca7d0e546ba8e Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:05:15 -0700 Subject: [PATCH 029/121] [AB#33565] Tenant configuration localization for Grant Manager group --- .../GrantManagerFeaturesDefinitionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs index f302199750..7f7bf9fb0a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantManagerFeaturesDefinitionProvider.cs @@ -13,7 +13,7 @@ public class GrantManagerFeaturesDefinitionProvider : FeatureDefinitionProvider { public override void Define(IFeatureDefinitionContext context) { - var myGroup = context.AddGroup("GrantManager"); + var myGroup = context.AddGroup("GrantManager", displayName: LocalizableString.Create("Grant Manager")); var defaultValue = "false"; myGroup.AddFeature("Unity.Payments", From 2a738c85306b7d7ad123328b23c5b3ec27153226 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:24:21 -0700 Subject: [PATCH 030/121] [AB#33886] Bugfix for sort button layount on numeric and date type columns --- .../Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css index 93e869148d..c5c815a40a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css @@ -914,4 +914,11 @@ div.dt-container div.dt-search { .filter-search-action-bar_search-wrapper { flex: 1; +} + +table.dataTable th.dt-type-numeric div.dt-column-header, +table.dataTable th.dt-type-date div.dt-column-header, +table.dataTable td.dt-type-numeric div.dt-column-header, +table.dataTable td.dt-type-date div.dt-column-header { + flex-direction: row; } \ No newline at end of file From e852f3c1596d79999fe0ec2d71bce5694d6521a2 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:37:47 -0700 Subject: [PATCH 031/121] [AB#33914] Audit history widget should exclude initial published values --- .../HistoryWidget/HistoryWidgetViewComponent.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs index 8072f43856..b21e16eab9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/HistoryWidget/HistoryWidgetViewComponent.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.History; @@ -26,14 +27,21 @@ public async Task InvokeAsync(Guid applicationId) string? entityId = applicationId.ToString(); Dictionary applicationStatusDict = await GetApplicationStatusDict(); - HistoryWidgetViewModel model = new() - { - ApplicationStatusHistoryList = await historyAppService.GetEntityPropertyChangesAsync( + var historyList = await historyAppService.GetEntityPropertyChangesAsync( new GetEntityPropertyChangesInput { EntityId = entityId, PropertyNames = [Property_ApplicationStatusId, Property_ExternalStatusVisibility], - }, applicationStatusDict), + }, applicationStatusDict); + + // Suppress the automatic initial Unpublished entry created on new entities (no prior value) + historyList = historyList + .Where(h => !(h.PropertyName == Property_ExternalStatusVisibility && string.IsNullOrEmpty(h.OriginalValue))) + .ToList(); + + HistoryWidgetViewModel model = new() + { + ApplicationStatusHistoryList = historyList, }; return View(model); From 1f5dd23626c3706989527bfd75f88ecd1b9689e4 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:58:18 -0700 Subject: [PATCH 032/121] [AB#33914] CSS bugfix --- .../src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css index c5c815a40a..1567fe83c5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css @@ -920,5 +920,5 @@ table.dataTable th.dt-type-numeric div.dt-column-header, table.dataTable th.dt-type-date div.dt-column-header, table.dataTable td.dt-type-numeric div.dt-column-header, table.dataTable td.dt-type-date div.dt-column-header { - flex-direction: row; + flex-direction: row !important; } \ No newline at end of file From 8cd298a2e4f223272491c3065003269802d6abf8 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 11 Aug 2026 13:46:08 -0700 Subject: [PATCH 033/121] bugfix/AB#34036-CrashLoopAndSupplierException --- .../Integrations/Cas/SupplierService.cs | 10 ++--- .../Endpoints/EndpointManagementAppService.cs | 10 ++++- .../Middleware/ErrorCountingLoggerSink.cs | 24 ++++++++++- .../Middleware/ExceptionCounterMiddleware.cs | 40 +++++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs index d9e7a06529..d70f466e54 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/SupplierService.cs @@ -27,7 +27,7 @@ public class SupplierService : ApplicationService, ISupplierService { protected new ILogger Logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); private const string CFS_SUPPLIER = "cfs/supplier"; - private readonly Task casBaseApiTask; + private readonly Lazy> casBaseApiTask; private readonly ILocalEventBus localEventBus; private readonly IResilientHttpRequest resilientHttpRequest; private readonly ICasTokenService iTokenService; @@ -42,8 +42,8 @@ public SupplierService(ILocalEventBus localEventBus, this.resilientHttpRequest = resilientHttpRequest; this.iTokenService = iTokenService; - // Initialize the base API URL once during construction - casBaseApiTask = InitializeBaseApiAsync(endpointManagementAppService); + // Defer the database-backed lookup until the service is actually used. + casBaseApiTask = new(() => InitializeBaseApiAsync(endpointManagementAppService)); } private static async Task InitializeBaseApiAsync(IEndpointManagementAppService endpointManagementAppService) @@ -218,7 +218,7 @@ public async Task GetCasSupplierInformationAsync(string? supplierNumber { if (!string.IsNullOrEmpty(supplierNumber)) { - var casBaseApi = await casBaseApiTask; + var casBaseApi = await casBaseApiTask.Value; var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{supplierNumber}"; return await GetCasSupplierInformationByResourceAsync(resource); } @@ -232,7 +232,7 @@ public async Task GetCasSupplierInformationByBn9Async(string? bn9) { if (!string.IsNullOrEmpty(bn9)) { - var casBaseApi = await casBaseApiTask; + var casBaseApi = await casBaseApiTask.Value; var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{bn9}/businessnumber"; return await GetCasSupplierInformationByResourceAsync(resource); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs index 5c03430b2e..c369d76dc6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs @@ -15,7 +15,8 @@ namespace Unity.GrantManager.Integrations.Endpoints { public class EndpointManagementAppService( IRepository repository, - IDistributedCache cache) : + IDistributedCache cache, + IUnitOfWorkManager unitOfWorkManager) : CrudAppService< DynamicUrl, DynamicUrlDto, @@ -25,6 +26,7 @@ public class EndpointManagementAppService( IEndpointManagementAppService { private readonly IDistributedCache _cache = cache; + private readonly IUnitOfWorkManager _unitOfWorkManager = unitOfWorkManager; private const string CACHE_KEY_SET_PREFIX = "DynamicUrl:KeySet"; private static string BuildCacheKey(string keyName, bool tenantSpecific, Guid? tenantId) @@ -156,13 +158,17 @@ await _cache.SetStringAsync( private async Task GetUrlValueAsync(string keyName, Guid? tenantId) { + using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false); var queryable = await Repository.GetQueryableAsync(); - return await AsyncExecuter.FirstOrDefaultAsync( + var url = await AsyncExecuter.FirstOrDefaultAsync( queryable .AsNoTracking() .Where(x => x.KeyName == keyName && x.TenantId == tenantId) .Select(x => x.Url)); + + await uow.CompleteAsync(); + return url; } // ------------------------------ diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs index aff8108b90..cd9c75e401 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ErrorCountingLoggerSink.cs @@ -18,8 +18,12 @@ namespace Unity.GrantManager.Web.Middleware; /// public sealed class ErrorCountingLoggerSink : ILogEventSink { + private static readonly TimeSpan PersistenceBackoff = TimeSpan.FromSeconds(30); private static IServiceScopeFactory? _scopeFactory; private static readonly AsyncLocal IsPersistingExceptionLog = new(); + private readonly object _persistenceGate = new(); + private bool _persistenceInFlight; + private DateTimeOffset _persistenceDisabledUntil; internal static readonly Counter ErrorCounter = Metrics.CreateCounter( @@ -59,6 +63,16 @@ public void Emit(LogEvent logEvent) return; } + lock (_persistenceGate) + { + if (_persistenceInFlight || DateTimeOffset.UtcNow < _persistenceDisabledUntil) + { + return; + } + + _persistenceInFlight = true; + } + _ = Task.Run(async () => { IsPersistingExceptionLog.Value = true; @@ -116,11 +130,19 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch { - // Swallow to avoid recursive logging from logger sink failures. + lock (_persistenceGate) + { + _persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); + } } finally { IsPersistingExceptionLog.Value = false; + + lock (_persistenceGate) + { + _persistenceInFlight = false; + } } }); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs index d8bf6faef4..5f232169f1 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs @@ -19,6 +19,11 @@ public class ExceptionCounterMiddleware( ExceptionNotificationThrottle throttle, ILogger logger) { + private static readonly TimeSpan PersistenceBackoff = TimeSpan.FromSeconds(30); + private readonly object persistenceGate = new(); + private bool persistenceInFlight; + private DateTimeOffset persistenceDisabledUntil; + // Notify only in these environments; add "Staging" if desired private static readonly HashSet NotifyEnvironments = new(StringComparer.OrdinalIgnoreCase) @@ -122,6 +127,19 @@ private void QueueLogNotification(HttpContext context, Exception ex) // we can safely use it after the request scope has ended var scopeFactory = context.RequestServices.GetRequiredService(); + // Acquire the single-flight gate only once the synchronous, potentially-throwing prep + // above has succeeded — otherwise an exception here would leave persistenceInFlight + // stuck "true" forever, since the Task.Run below (whose finally resets it) never starts. + lock (persistenceGate) + { + if (persistenceInFlight || DateTimeOffset.UtcNow < persistenceDisabledUntil) + { + return; + } + + persistenceInFlight = true; + } + _ = Task.Run(async () => { try @@ -132,6 +150,11 @@ private void QueueLogNotification(HttpContext context, Exception ex) var notifications = scope.ServiceProvider.GetRequiredService(); var exceptionLogs = scope.ServiceProvider.GetService(); + if (exceptionLogs == null) + { + OpenPersistenceBackoff(); + } + // Get current user and tenant name var userId = AbpUserTenantAccessor.GetCurrentUserId(scope.ServiceProvider); var userName = AbpUserTenantAccessor.GetCurrentUserName(scope.ServiceProvider) ?? "unknown"; @@ -262,6 +285,7 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch (Exception logEx) { + OpenPersistenceBackoff(); logger.LogWarning(logEx, "Failed to create exception log within UnitOfWork"); } } @@ -270,6 +294,7 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } catch (Exception uowEx) { + OpenPersistenceBackoff(); logger.LogWarning(uowEx, "Failed to complete UnitOfWork for exception handling"); } } @@ -279,9 +304,24 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto notifyEx, "Failed to send Teams exception notification"); } + finally + { + lock (persistenceGate) + { + persistenceInFlight = false; + } + } }); } + private void OpenPersistenceBackoff() + { + lock (persistenceGate) + { + persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); + } + } + private static string BuildApplicationStackExcerpt(Exception ex) { return ExceptionNotificationHelpers.BuildApplicationStackExcerpt(ex); From 4da18fb79046e88ccc694efd24daa66ab61d91b9 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 11 Aug 2026 13:53:23 -0700 Subject: [PATCH 034/121] bugfix/AB#34036-CrashLoopAndSupplierException-PS1update --- .../scripts/Get-SonarIssues.ps1 | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 b/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 index 736c658310..994a1ece4a 100644 --- a/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 +++ b/applications/Unity.GrantManager/scripts/Get-SonarIssues.ps1 @@ -3,12 +3,16 @@ Pulls open SonarCloud issues for a branch via the public API and writes them to a Markdown report. .DESCRIPTION - Calls the SonarCloud /api/issues/search endpoint (paginating past its 500-per-page limit), + Calls the SonarQube /api/issues/search endpoint (paginating past its 500-per-page limit), then groups the results by severity into a Markdown file - handy for pasting into Copilot/Claude - or attaching to a PR instead of screen-scraping the SonarCloud UI. + or attaching to a PR instead of screen-scraping the SonarQube UI. + +.PARAMETER ServerUrl + SonarQube server URL. Default: https://sonarqube.econ.gov.bc.ca/sonar. Set this to + https://sonarcloud.io when querying SonarCloud. .PARAMETER ProjectKey - SonarCloud project (component) key. Default: bcgov_Unity. + SonarQube project (component) key. Default: UnityScanKey. .PARAMETER Branch Branch name to query. If omitted, you'll be prompted to pick the current git branch, one of @@ -28,6 +32,10 @@ .PARAMETER Types Optional filter, e.g. -Types BUG,VULNERABILITY. Valid values: BUG, VULNERABILITY, CODE_SMELL. +.PARAMETER FileType + Source file type to include. Valid values: js, css, cshtml, cs, all. If omitted, you'll be + prompted to choose one. + .PARAMETER IncludeResolved Include resolved/closed issues too. By default only unresolved (open) issues are fetched. @@ -41,7 +49,10 @@ this for unattended/CI runs where nothing should launch afterwards. .EXAMPLE - .\Get-SonarIssues.ps1 -Branch main -Token $env:SONAR_TOKEN + .\Get-SonarIssues.ps1 -Branch dev -Token $env:SONAR_TOKEN + +.EXAMPLE + .\Get-SonarIssues.ps1 -ServerUrl https://sonarcloud.io -ProjectKey bcgov_Unity -Branch main -Token $env:SONAR_TOKEN .EXAMPLE .\Get-SonarIssues.ps1 -ProjectKey bcgov_Unity -Branch feature/AB-12345 -Severities BLOCKER,CRITICAL -OutputPath .\sonar-report.md @@ -59,7 +70,9 @@ .\Get-SonarIssues.ps1 -Branch main -FixLevel Quick #> param( - [string]$ProjectKey = "bcgov_Unity", + [string]$ServerUrl = "https://sonarqube.econ.gov.bc.ca/sonar", + + [string]$ProjectKey = "UnityScanKey", [string]$Branch = "", @@ -73,6 +86,9 @@ param( [ValidateSet("BUG", "VULNERABILITY", "CODE_SMELL")] [string[]]$Types = @(), + [ValidateSet("js", "css", "cshtml", "cs", "all")] + [string]$FileType = "", + [switch]$IncludeResolved, [ValidateSet("None", "Quick", "QuickModerate", "All")] @@ -83,7 +99,7 @@ param( $ErrorActionPreference = "Stop" -$ApiBase = "https://sonarcloud.io/api/issues/search" +$ApiBase = "$($ServerUrl.TrimEnd('/'))/api/issues/search" $PageSize = 500 $SeverityOrder = @("BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO") $WellKnownBranches = @("dev", "test", "main") @@ -151,6 +167,32 @@ function Read-BranchSelection { } } +function Read-FileTypeSelection { + $menu = [ordered]@{ + "1" = @{ Label = "JavaScript (.js)"; Extension = "js" } + "2" = @{ Label = "Stylesheet (.css)"; Extension = "css" } + "3" = @{ Label = "Razor (.cshtml)"; Extension = "cshtml" } + "4" = @{ Label = "C# (.cs)"; Extension = "cs" } + "5" = @{ Label = "All source file types"; Extension = "all" } + } + + Write-Host "" + Write-Host "Which source file type should be included?" -ForegroundColor Cyan + foreach ($key in $menu.Keys) { + Write-Host " [$key] $($menu[$key].Label)" + } + + while ($true) { + $choice = Read-Host "Enter choice (default: 5 - All source file types)" + if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "5" } + + if ($menu.Contains($choice)) { + return $menu[$choice].Extension + } + Write-Host "Invalid choice '$choice' - try again." -ForegroundColor Yellow + } +} + # Maps a -FixLevel value (or the equivalent interactive menu choice) to the FixComplexity tiers # it covers. "None" intentionally maps to an empty array - nothing gets fixed. $FixLevelTierMap = [ordered]@{ @@ -190,6 +232,10 @@ if (-not $Branch) { $Branch = Read-BranchSelection -CurrentBranch (Get-CurrentGitBranch) } +if (-not $FileType) { + $FileType = Read-FileTypeSelection +} + if (-not $OutputPath) { $branchSlug = if ($Branch) { ($Branch -replace '[\\/:*?"<>|]', '-') } else { "default-branch" } $OutputPath = "sonar-issues-$branchSlug.md" @@ -287,6 +333,14 @@ function Get-IssueFilePath { return ($Issue.component -replace "^$([regex]::Escape($ProjectKey)):", "") } +if ($FileType -ne "all") { + $extension = ".$FileType" + $allIssues = [System.Collections.Generic.List[object]]@( + $allIssues | Where-Object { (Get-IssueFilePath $_).EndsWith($extension, [System.StringComparison]::OrdinalIgnoreCase) } + ) + Write-Host "Issues after file type filter '$FileType': $($allIssues.Count)" +} + # --- Fix-complexity classification --- # Goal: separate mechanical, low-risk fixes (rename, swap one API for another, drop an unused var) # from ones that ripple across every call site or need an actual design change, so the report can @@ -366,6 +420,7 @@ $md = New-Object System.Text.StringBuilder [void]$md.AppendLine("") [void]$md.AppendLine("- **Project:** $ProjectKey") [void]$md.AppendLine("- **Branch:** $(if ($Branch) { $Branch } else { '(default)' })") +[void]$md.AppendLine("- **Source file type:** $FileType") [void]$md.AppendLine("- **Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$md.AppendLine("- **Total open issues:** $($allIssues.Count)") [void]$md.AppendLine("") @@ -404,7 +459,7 @@ if ($quickWins.Count -gt 0) { $file = Get-IssueFilePath $issue $line = if ($issue.textRange -and $issue.textRange.startLine) { $issue.textRange.startLine } else { "-" } $message = ($issue.message -replace '\|', '\|') - $link = "https://sonarcloud.io/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" + $link = "$($ServerUrl.TrimEnd('/'))/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" [void]$md.AppendLine("| $($issue.severity) | ``$file`` | $line | [$($issue.rule)]($link) | $message |") } [void]$md.AppendLine("") @@ -425,7 +480,7 @@ foreach ($sev in $SeverityOrder) { $line = if ($issue.textRange -and $issue.textRange.startLine) { $issue.textRange.startLine } else { "-" } $message = ($issue.message -replace '\|', '\|') $effort = if ($issue.effort) { $issue.effort } else { "-" } - $link = "https://sonarcloud.io/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" + $link = "$($ServerUrl.TrimEnd('/'))/project/issues?id=$ProjectKey&issues=$($issue.key)&open=$($issue.key)$(if ($Branch) { "&branch=$Branch" })" [void]$md.AppendLine("| ``$file`` | $line | $($issue.type) | $($issue.FixComplexity.Badge) | $effort | [$($issue.rule)]($link) | $message |") } [void]$md.AppendLine("") From 09786472a0b13792c0743254aae058bf6c2e35c5 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 11 Aug 2026 14:51:38 -0700 Subject: [PATCH 035/121] AB#32831 init CLAUDE md and update for project --- CLAUDE.md | 63 +++++++ .../agents/application-service-designer.md | 46 ++++++ .../.claude/agents/ddd-modeler.md | 47 ++++++ .../agents/efcore-migration-planner.md | 46 ++++++ .../.claude/agents/feature-planner.md | 131 +++++++++++++++ .../permissions-localization-auditor.md | 43 +++++ .../.claude/agents/pr-readiness-deep.md | 102 ++++++++++++ .../.claude/agents/pr-readiness.md | 43 +++++ .../.claude/agents/test-strategy.md | 45 +++++ .../.claude/agents/test-triage.md | 45 +++++ .../.claude/rules/csharp.md | 120 ++++++++++++++ .../.claude/rules/efcore.md | 25 +++ .../.claude/rules/javascript.md | 61 +++++++ .../.claude/rules/security.md | 49 ++++++ .../.claude/rules/testing.md | 30 ++++ .../.claude/skills/abp-cli/SKILL.md | 78 +++++++++ .../skills/unity-application-layer/SKILL.md | 102 ++++++++++++ .../unity-domain-driven-design/SKILL.md | 105 ++++++++++++ .../.claude/skills/unity-ef-core/SKILL.md | 112 +++++++++++++ .../skills/unity-module-structure/SKILL.md | 113 +++++++++++++ .../.claude/skills/unity-testing/SKILL.md | 155 ++++++++++++++++++ 21 files changed, 1561 insertions(+) create mode 100644 CLAUDE.md create mode 100644 applications/Unity.GrantManager/.claude/agents/application-service-designer.md create mode 100644 applications/Unity.GrantManager/.claude/agents/ddd-modeler.md create mode 100644 applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md create mode 100644 applications/Unity.GrantManager/.claude/agents/feature-planner.md create mode 100644 applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md create mode 100644 applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md create mode 100644 applications/Unity.GrantManager/.claude/agents/pr-readiness.md create mode 100644 applications/Unity.GrantManager/.claude/agents/test-strategy.md create mode 100644 applications/Unity.GrantManager/.claude/agents/test-triage.md create mode 100644 applications/Unity.GrantManager/.claude/rules/csharp.md create mode 100644 applications/Unity.GrantManager/.claude/rules/efcore.md create mode 100644 applications/Unity.GrantManager/.claude/rules/javascript.md create mode 100644 applications/Unity.GrantManager/.claude/rules/security.md create mode 100644 applications/Unity.GrantManager/.claude/rules/testing.md create mode 100644 applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md create mode 100644 applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md create mode 100644 applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md create mode 100644 applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md create mode 100644 applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md create mode 100644 applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..96ae0cbbf4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +> This is **Unity Portal**, a grant management system for the Province of British Columbia. It is NOT the Unity game engine — do not suggest UnityEngine APIs. + +## Repository Layout + +`applications/Unity.GrantManager/` is where almost all work happens — a self-contained ABP Framework solution with its own extensive AI-agent instructions. `applications/Unity.AutoUI/` holds Cypress E2E tests; `applications/Unity.Tools/`, `database/`, and `documentation/` round out the rest (see root `README.md` for the full layout). + +**Read these before making non-trivial changes in `applications/Unity.GrantManager/`** — note this solution has its own `.github/`, separate from the root `.github/` above it: + +- `applications/Unity.GrantManager/.github/copilot-instructions.md` — authoritative project overview, layering, and conventions (trust this first) +- `applications/Unity.GrantManager/.github/instructions/*.instructions.md` — path-scoped rules for C#, EF Core, JavaScript, security, testing +- `applications/Unity.GrantManager/.github/skills/*/SKILL.md` — deep-dive patterns: DDD, application layer, EF Core, testing, ABP CLI, module structure +- `applications/Unity.GrantManager/.github/agents/*.agent.md` — planning agents for features, DDD modeling, EF migrations, permissions/localization audits, test strategy, PR readiness + +Where those files and this one overlap, prefer the more specific ones under `applications/Unity.GrantManager/.github/`. + +## Build & Test + +All commands run from `applications/Unity.GrantManager/`: + +```bash +dotnet restore Unity.GrantManager.sln +dotnet build Unity.GrantManager.sln --no-restore # ~3 min, 81 projects +dotnet test Unity.GrantManager.sln --no-build # ~470 tests, ~1-2 min + +# Single test project +dotnet test test/Unity.GrantManager.Application.Tests/ --no-build +``` + +- No PostgreSQL setup needed for tests — SQLite in-memory (most projects) or `EFCore.InMemory` (`Unity.GrantManager.Web.Tests`). +- `Unity.GrantManager.Web/Pages/Dashboard/Index.cshtml.cs` has one expected `CS8604` warning — don't fix it unless asked. +- `Directory.Build.props` / `common.props` (repo-wide MSBuild props) already suppress `NU1701`, `MSB3277`, `CS1591` — don't re-suppress per-project. + +### Local dev environment + +`docker-compose.yml` + `.env.example` in `applications/Unity.GrantManager/` spin up the web app, PostgreSQL, a DB migrator, and Redis. Copy `.env.example` to `.env` and fill in secrets before running `docker compose up`. + +### EF Core migrations + +There are **two separate database contexts** — always specify which one: + +```bash +cd applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore + +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations # host/shared tables +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations # per-tenant data +``` + +## Architecture + +ABP Framework modular monolith, DDD-layered — see `applications/Unity.GrantManager/.github/copilot-instructions.md` and `applications/Unity.GrantManager/.github/skills/unity-module-structure/SKILL.md` for the full module list and dependency-direction diagram. + +Business rules belong in Domain entities/managers, not controllers or app services. Don't call another app service within the same module — push shared logic into a domain service. + +### Key conventions + +C#, EF Core, JavaScript, security, and testing conventions are detailed in `.claude/rules/*.md` (loaded automatically for matching files) and `applications/Unity.GrantManager/.github/instructions/*.instructions.md`. + +- **Branching**: `dev` → `test` → `main` promotion. Feature branches `feature/*`, fixes `bugfix/*`, urgent `hotfix/*`. PRs to `dev` come from `feature/*`/`bugfix/*`/`hotfix/*`; PRs to `main` only from `test` or `hotfix/*`. +- **Commit messages**: prefix with `[AB#]` extracted from the branch name (e.g. `feature/AB#32037-...` → `AB#32037`), then a short description. diff --git a/applications/Unity.GrantManager/.claude/agents/application-service-designer.md b/applications/Unity.GrantManager/.claude/agents/application-service-designer.md new file mode 100644 index 0000000000..c1ccceafe1 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/application-service-designer.md @@ -0,0 +1,46 @@ +--- +name: application-service-designer +description: Designs ABP application service contracts, DTOs, authorization, and Mapperly mapping plans for Unity Grant Manager. Use when adding or changing app services, DTOs, or mapping profiles. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Application Service Designer Agent + +You are the application-layer design specialist for Unity Grant Manager. + +## Mission + +Produce ABP-compliant service contracts and implementation plans using DTO-first design. + +## Inputs + +- Use cases and API behavior. +- Existing service interfaces and DTOs. +- Target module and permissions. + +## Process + +1. Propose or update `I*AppService` method signatures. +2. Define DTOs per method intent (create, update, get, list). +3. Identify authorization requirements and permission constants. +4. Define Mapperly mapper changes. +5. Define validation and business-exception boundaries. + +## Output Format + +1. Contract changes. +2. DTO matrix. +3. Authorization matrix. +4. Mapping profile changes. +5. Service implementation checklist. +6. Test targets. + +## Guardrails + +- Apply the `unity-application-layer` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Methods must be async and end with `Async`. +- Accept/return DTOs only, never entities. +- Use Mapperly with `ObjectMapper.Map<>()`, never AutoMapper. Mapper classes inherit `MapperBase` or `TwoWayMapperBase` and are decorated with `[Mapper]`. +- This agent designs only — it does not edit files. diff --git a/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md b/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md new file mode 100644 index 0000000000..9e400fe9cf --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/ddd-modeler.md @@ -0,0 +1,47 @@ +--- +name: ddd-modeler +description: Designs and reviews ABP DDD models, aggregates, repositories, and domain managers for Unity Grant Manager. Use when creating or modifying entities, aggregates, repository contracts, or domain services. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP DDD Modeler Agent + +You are the DDD modeling specialist for Unity Grant Manager. + +## Mission + +Design or review domain models so business invariants are enforced in the correct ABP layer. + +## Inputs + +- Business rules and scenarios. +- Existing entities and repository interfaces. +- Target module. + +## Process + +1. Define aggregate boundaries and ownership rules. +2. Identify entity/value object responsibilities. +3. Propose behavior methods that enforce invariants. +4. Define repository contract additions only for aggregate roots. +5. Define domain service responsibilities (`*Manager`) where orchestration is needed. +6. Propose business error codes and exception points. + +## Output Format + +1. Aggregate model proposal. +2. Invariants and rule enforcement table. +3. Repository contract changes. +4. Domain manager methods. +5. Error code list. +6. Anti-pattern checks. + +## Guardrails + +- Apply the `unity-domain-driven-design` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Do not generate GUIDs in entity constructors. +- Reference external aggregates by Id only. +- Keep app-service logic out of the domain model design. +- This agent designs/reviews only — it does not edit files. diff --git a/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md new file mode 100644 index 0000000000..eab4dad198 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md @@ -0,0 +1,46 @@ +--- +name: efcore-migration-planner +description: Plans EF Core model updates and host versus tenant migrations safely for Unity Grant Manager. Use when a change touches entity mapping or requires a database migration. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP EF Core Migration Planner Agent + +You are the EF Core migration planning specialist for Unity Grant Manager. + +## Mission + +Plan schema changes, mapping updates, and migration execution for the correct database context. + +## Inputs + +- Proposed entity/model changes. +- Whether data is host-wide or tenant-scoped. +- Existing migrations and repository code. + +## Process + +1. Classify each change as host, tenant, or both. +2. Propose `ModelBuilder` mapping updates. +3. Verify repository impact and query behavior. +4. Produce migration commands and ordering. +5. Identify rollback and data backfill considerations. + +## Output Format + +1. Context classification. +2. Mapping change checklist. +3. Migration command plan. +4. Data safety notes. +5. Repository update checklist. +6. Validation tests. + +## Guardrails + +- Apply the `unity-ef-core` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/efcore.instructions.md`. +- Always call `ConfigureByConvention()` for mapped entities. +- Do not use `includeAllEntities: true` with default repositories. +- Always specify context (`GrantManagerDbContext` or `GrantTenantDbContext`) for migration commands. +- This agent plans only — it does not run `dotnet ef migrations add` or edit files itself. diff --git a/applications/Unity.GrantManager/.claude/agents/feature-planner.md b/applications/Unity.GrantManager/.claude/agents/feature-planner.md new file mode 100644 index 0000000000..73a21bab90 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/feature-planner.md @@ -0,0 +1,131 @@ +--- +name: feature-planner +description: Plans feature implementation across Domain, Application, EF Core, Web, and tests for Unity Grant Manager, respecting ABP layering. Use when a feature or bug needs a structured implementation plan before coding starts. +tools: Read, Grep, Glob, Bash, AskUserQuestion +model: inherit +--- + +# ABP Feature Planner Agent + +You are the FEATURE PLANNING AGENT for Unity Grant Manager, pairing with the user to create a detailed, actionable plan. + +You research the codebase → clarify with the user → produce a comprehensive plan that respects ABP modular layering and delivery flow. This iterative approach catches edge cases and non-obvious requirements BEFORE implementation begins. + +Your SOLE responsibility is planning. NEVER start implementation — you have no `Edit`/`Write` tools for that reason. + + +- Do not attempt to edit or write files — plans are for the user (or a follow-up implementation turn) to execute. +- Use `AskUserQuestion` freely to clarify requirements — don't make large assumptions. +- Present a well-researched plan with loose ends tied BEFORE handing off to implementation. + + + +Cycle through these phases based on user input. This is iterative, not linear. If the task is highly ambiguous, do only *Discovery* to outline a draft plan, then move to alignment before fleshing out the full plan. + +## 1. Discovery + +Read and search the codebase to gather context: analogous existing features to use as implementation templates, and potential blockers or ambiguities. + +Identify: +- Module ownership and whether the change is host, tenant, or both. +- Work split by ABP layer: Domain.Shared → Domain → Application.Contracts → Application → EntityFrameworkCore → HttpApi/Web → Tests. +- Dependencies and ordering constraints between layers. +- Cross-module impacts and permission/localization requirements. + +## 2. Alignment + +If research reveals major ambiguities or if you need to validate assumptions: +- Use `AskUserQuestion` to clarify intent with the user. +- Surface discovered technical constraints or alternative approaches. +- If answers significantly change the scope, loop back to **Discovery**. + +## 3. Design + +Once context is clear, draft a comprehensive implementation plan structured around ABP layers. + +The plan should reflect: +- Structured concisely enough to be scannable and detailed enough for effective execution. +- Step-by-step implementation with explicit dependencies — mark which steps can run in parallel vs. which block on prior steps. +- For plans with many steps, group into named phases that are each independently verifiable. +- Verification steps for validating the implementation, both automated and manual. +- Critical architecture to reuse or use as reference — reference specific functions, types, or patterns, not just file names. +- Critical files to be modified (with full paths). +- Explicit scope boundaries — what's included and what's deliberately excluded. +- Reference decisions from the discussion. +- Leave no ambiguity. + +Present the plan directly in your response — this agent has no persistent scratch file, so the plan you return IS the deliverable. + +## 4. Refinement + +On user input after showing the plan: +- Changes requested → revise and present updated plan. +- Questions asked → clarify, or use `AskUserQuestion` for follow-ups. +- Alternatives wanted → loop back to **Discovery**. +- Approval given → acknowledge; implementation is a separate turn/agent from here. + +Keep iterating until explicit approval. + + +## Inputs + +- Feature or bug statement. +- Acceptance criteria. +- Target module(s). +- Any constraints (timeline, migration risk, tenant scope, security requirements). + + +```markdown +## Plan: {Title (2-10 words)} + +{TL;DR - what, why, and how (your recommended approach).} + +**Steps** + +### Phase 1 — Domain & Contracts +1. {Domain.Shared changes — enums, consts, error codes} +2. {Domain entity/aggregate changes — note dependency ("*depends on N*") or parallelism ("*parallel with step N*") when applicable} +3. {Application.Contracts — DTOs, IAppService interfaces, permissions} + +### Phase 2 — Application & Persistence +4. {Application service implementation} +5. {EntityFrameworkCore — DbContext, entity config, migration} + +### Phase 3 — API & Frontend +6. {HttpApi controller / AutoAPI} +7. {Web — Pages, JS, localization} + +### Phase 4 — Tests +8. {Unit and integration tests} + +**Relevant files** +- `{full/path/to/file}` — {what to modify or reuse, referencing specific functions/patterns} + +**Migration & Data Impact** +- {Host vs tenant migration scope, data backfill needs, breaking schema changes} + +**Verification** +1. {Verification steps for validating the implementation (**Specific** tasks, tests, commands, etc; not generic statements)} + +**Decisions** (if applicable) +- {Decision, assumptions, and includes/excluded scope} + +**Risks & Mitigations** (if applicable) +- {Risk and mitigation strategy} + +**Definition of Done** +- [ ] {Checklist item} +``` + +Rules: +- NO code blocks — describe changes, link to files and specific symbols/functions. +- NO blocking questions at the end — ask during workflow via `AskUserQuestion`. +- The plan MUST be presented in full to the user, not just summarized. + + +## Guardrails + +- Enforce module dependency direction from the `unity-module-structure` skill. +- Enforce ABP app/domain rules from `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- Do not use AutoMapper. Use Mapperly (`[Mapper]` attribute, `MapperBase`). +- Do not place business rules in controllers or app services. diff --git a/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md b/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md new file mode 100644 index 0000000000..1e124f4d7a --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/permissions-localization-auditor.md @@ -0,0 +1,43 @@ +--- +name: permissions-localization-auditor +description: Audits ABP changes in Unity Grant Manager for permission coverage, localization correctness, and policy compliance. Use before a PR to check for missing permissions or hardcoded user-facing strings. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Permissions and Localization Auditor Agent + +You are the ABP compliance auditing specialist for Unity Grant Manager. + +## Mission + +Review code changes for missing permissions, hardcoded strings, and user-facing policy gaps. + +## Inputs + +- Diff or list of changed files. +- Affected user flows and roles. + +## Process + +1. Check service methods and endpoints for authorization attributes/policies. +2. Verify permission constants and definition provider coverage. +3. Scan for hardcoded user-facing text. +4. Verify localization key usage and resource updates. +5. Identify likely regressions and required tests. + +## Output Format + +1. Findings by severity. +2. Missing permissions list. +3. Localization findings list. +4. Required code changes. +5. Validation checklist. + +## Guardrails + +- Follow `applications/Unity.GrantManager/.github/copilot-instructions.md` and `applications/Unity.GrantManager/.github/instructions/csharp.instructions.md`. +- All user-facing text must be localized. +- Permissions must be defined in Application.Contracts permission providers. +- Do not propose hardcoded strings in services, controllers, or UI code. +- This agent audits only — it reports findings rather than editing files. diff --git a/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md b/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md new file mode 100644 index 0000000000..a8178f1d05 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/pr-readiness-deep.md @@ -0,0 +1,102 @@ +--- +name: pr-readiness-deep +description: Deep PR quality gate for Unity Grant Manager that checks ABP architecture, runs backend and Cypress E2E tests. Use for a more thorough pre-PR check than pr-readiness, when you specifically need Cypress E2E coverage included. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# PR Readiness Agent (Deep Scan) + +Final quality gate for Unity Grant Manager PRs, covering ABP architecture, backend tests, and Cypress E2E. + +> **Scope note**: the original Copilot version of this agent also drove SonarQube (`sonarqube_analyze_file`, `sonarqube_list_potential_security_issues`) and CodeQL scanning/auto-fix. Those depend on VS Code extension tooling that isn't wired into this Claude Code setup (no SonarQube/CodeQL MCP server is configured in this project). This version keeps the parts that work standalone — ABP architecture review, build/test, and Cypress E2E — and applies the security-pattern checks below via code review instead of a scanner. If SonarQube/CodeQL MCP tools are added to this project later, re-introduce the scanning steps. + +## Inputs +- Branch diff, build/test status, target branch + +## Quality Checks Workflow + +### Step 1: ABP Architecture Review +- Layer boundaries (Domain → Application → Web) — see the `unity-module-structure` skill. +- Repository/DTO/Mapperly conventions — see the `unity-application-layer` skill. +- Permissions and localization keys present for all new user-facing behavior. +- EF migrations correct (host vs tenant context) if schema changes exist — see the `unity-ef-core` skill. + +### Step 2: Security Pattern Review (manual, in lieu of SonarQube/CodeQL) +Review changed files for the patterns in **Common Fixes** below. Flag any match as a blocking issue. + +### Step 3: Build & Backend Tests +```bash +dotnet build Unity.GrantManager.sln --no-restore +dotnet test Unity.GrantManager.sln --no-build +``` + +### Step 4: Cypress E2E Testing +```bash +cd applications/Unity.AutoUI +npm install +npx cypress run # headless +# npx cypress open # interactive, for debugging failures +``` + +Check for: all specs passing, no failed assertions, no unexpected console errors. On failure, review `cypress/screenshots/` and `cypress/videos/`, determine whether it's a stale selector (UI changed) or a real regression, then report which. + +## Common Fixes (patterns to flag during Step 2) + +```csharp +// ❌ SQL Injection +var sql = $"SELECT * FROM Users WHERE Email = '{email}'"; + +// ✅ Use EF LINQ +var users = await _dbContext.Users.Where(u => u.Email == email).ToListAsync(); + +// ❌ Missing authorization +public async Task DeleteAsync(Guid id) + +// ✅ Add attribute +[Authorize(GrantManagerPermissions.Applications.Delete)] +public async Task DeleteAsync(Guid id) + +// ❌ Return entity +public async Task GetAsync(Guid id) + +// ✅ Return DTO +public async Task GetAsync(Guid id) +{ + var entity = await _repository.GetAsync(id); + return ObjectMapper.Map(entity); +} + +// ❌ Path traversal +public async Task GetDocumentAsync(string fileName) +{ + var path = Path.Combine(root, "Documents", fileName); + return await File.ReadAllBytesAsync(path); +} + +// ✅ Validate path +public async Task GetDocumentAsync(Guid documentId) +{ + var doc = await _repository.GetAsync(documentId); + var safeFileName = Path.GetFileName(doc.FileName); + var fullPath = Path.GetFullPath(Path.Combine(root, "Documents", safeFileName)); + var allowedPath = Path.GetFullPath(Path.Combine(root, "Documents")); + + if (!fullPath.StartsWith(allowedPath)) + throw new BusinessException("Invalid path"); + + return await File.ReadAllBytesAsync(fullPath); +} +``` + +Also flag: hardcoded credentials/secrets, resource leaks (missing `using`/disposal), empty catch blocks, and logging of sensitive data. + +## Output + +1. **Summary**: files reviewed, issues found by severity, backend test result (X passed / Y failed), Cypress result (X passed / Y failed, with screenshot/video paths for failures). +2. **Go/No-Go**: + - ✅ GO — no blocking issues, all tests pass. + - ❌ NO-GO — blocking issues, test failures, or flagged security patterns need resolution. + - ⚠️ CONDITIONAL — minor issues present but mergeable with a follow-up task. +3. **Detailed findings**: file:line for each issue, with the specific fix. +4. **Validation commands run**, so the user can reproduce. diff --git a/applications/Unity.GrantManager/.claude/agents/pr-readiness.md b/applications/Unity.GrantManager/.claude/agents/pr-readiness.md new file mode 100644 index 0000000000..0294a5be73 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/pr-readiness.md @@ -0,0 +1,43 @@ +--- +name: pr-readiness +description: Performs a pre-PR quality gate for Unity Grant Manager - build, tests, ABP layering, and policy compliance. Use before opening a PR to get a go/no-go readiness check. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP PR Readiness Agent + +You are the final quality gate specialist for Unity Grant Manager pull requests. + +## Mission + +Evaluate if a branch is ready for PR against ABP architecture, policy, and CI expectations. + +## Inputs + +- Branch diff. +- Build and test status. +- Target branch. + +## Process + +1. Verify branch policy and PR source/target compatibility (`dev` from `feature/*`/`bugfix/*`/`hotfix/*`; `main` only from `test` or `hotfix/*`). +2. Check layering boundaries and module dependency direction. +3. Check mapping, DTO boundaries, localization, and permissions. +4. Check migration context correctness when EF changes exist. +5. Confirm test coverage and CI command readiness. + +## Output Format + +1. Go/No-go recommendation. +2. Blocking issues. +3. Non-blocking improvements. +4. Required validation commands. +5. PR description checklist. + +## Guardrails + +- Follow `applications/Unity.GrantManager/.github/copilot-instructions.md`. +- Require `dotnet build Unity.GrantManager.sln --no-restore` and `dotnet test Unity.GrantManager.sln --no-build` readiness — run them if not already confirmed clean. +- Enforce ABP module layering rules from the `unity-module-structure` skill. +- Enforce Mapperly, localization, and permissions conventions. diff --git a/applications/Unity.GrantManager/.claude/agents/test-strategy.md b/applications/Unity.GrantManager/.claude/agents/test-strategy.md new file mode 100644 index 0000000000..42e943fb18 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/test-strategy.md @@ -0,0 +1,45 @@ +--- +name: test-strategy +description: Builds a risk-based test strategy for Unity Grant Manager using xUnit, Shouldly, NSubstitute, and layered coverage. Use when planning test coverage for a new feature or bug fix. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Test Strategy Agent + +You are the testing strategy specialist for Unity Grant Manager. + +## Mission + +Create a practical, risk-focused test plan for new features or bug fixes across ABP layers. + +## Inputs + +- Feature scope or code diff. +- Changed modules and layers. +- Known edge cases. + +## Process + +1. Identify impacted behavior per layer. +2. Split test coverage into unit, integration, and optional web tests. +3. Propose fixtures and test data setup. +4. Map scenarios to concrete test cases. +5. Prioritize tests for fastest feedback. + +## Output Format + +1. Coverage scope summary. +2. Unit test cases. +3. Integration test cases. +4. Test data and fixture requirements. +5. Execution order and commands. + +## Guardrails + +- Apply the `unity-testing` skill's patterns. +- Follow `applications/Unity.GrantManager/.github/instructions/testing.instructions.md`. +- Use xUnit with Shouldly and NSubstitute. +- Avoid `Assert.*` and Moq patterns. +- Keep tests deterministic and isolated. +- This agent plans only — it does not write test code itself. diff --git a/applications/Unity.GrantManager/.claude/agents/test-triage.md b/applications/Unity.GrantManager/.claude/agents/test-triage.md new file mode 100644 index 0000000000..f73f49b8e9 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/agents/test-triage.md @@ -0,0 +1,45 @@ +--- +name: test-triage +description: Diagnoses failing Unity Grant Manager tests, isolates root cause, and proposes minimal-risk fixes. Use when tests are failing and you need to find the smallest reliable fix. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +# ABP Test Triage Agent + +You are the failure triage specialist for Unity Grant Manager tests. + +## Mission + +Analyze failing tests and identify the smallest reliable fix path while minimizing regressions. + +## Inputs + +- Test output logs. +- Recent code diff. +- Affected project/module. + +## Process + +1. Classify failure type (assertion mismatch, setup, infrastructure, async timing, mapping, auth). +2. Correlate failing tests with changed code paths. +3. Identify probable root cause and confidence level. +4. Propose minimum fix sequence with verification steps. +5. Identify regression tests that must be added or updated. + +## Output Format + +1. Failure summary. +2. Root-cause hypotheses ranked by probability. +3. Recommended fix path. +4. Verification command checklist. +5. Regression prevention tests. + +## Guardrails + +- Use module/layer rules from the `unity-module-structure` skill. +- Use testing conventions from the `unity-testing` skill. +- You may run `dotnet test` (e.g. `dotnet test Unity.GrantManager.sln --no-build`) to reproduce failures and verify hypotheses. +- Prefer minimal changes over broad refactors during triage. +- Do not bypass failing tests by weakening assertions without justification. +- This agent diagnoses and proposes fixes — it does not apply code edits itself. diff --git a/applications/Unity.GrantManager/.claude/rules/csharp.md b/applications/Unity.GrantManager/.claude/rules/csharp.md new file mode 100644 index 0000000000..8ec10b8ffc --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/csharp.md @@ -0,0 +1,120 @@ +--- +globs: "**/*.cs" +--- + +# C# Conventions for Unity Grant Manager + +> C# and .NET 10 development standards for ABP Framework 10.5. + +- Target framework: .NET 10.0 with `latest`. +- Nullable reference types are enabled in most projects. +- This is an ABP Framework project. Use ABP base classes, not raw ASP.NET Core. +- This is NOT the Unity game engine. Do not suggest UnityEngine APIs. + +## ABP Base Classes + +- Application Services: Inherit `ApplicationService`, implement interface from Application.Contracts +- Domain Services: Inherit `DomainService`, use `Manager` suffix +- Entities: Inherit `FullAuditedAggregateRoot` or `AuditedAggregateRoot` +- API Controllers: Inherit `AbpController` +- Repositories: Use `IRepository` by default; custom only when needed + +### Injected Properties Available in Base Classes + +These properties are pre-injected in `ApplicationService`, `DomainService`, and `AbpController`: + +| Property | Purpose | +|---|---| +| `GuidGenerator` | Create new entity IDs — never use `Guid.NewGuid()` | +| `Clock` | Use `Clock.Now` — never use `DateTime.Now` or `DateTime.UtcNow` | +| `CurrentUser` | Access authenticated user (Id, Name, Email, Roles) | +| `CurrentTenant` | Access current tenant context (Id, Name) | +| `L` / `L["Key"]` | Localization shortcut | +| `ObjectMapper` | Mapperly-based mapping | +| `Logger` | Structured logging via `ILogger` | +| `AuthorizationService` | Programmatic authorization checks | +| `UnitOfWorkManager` | Manual unit-of-work control | + +## Dependency Injection + +- ABP auto-registers services using marker interfaces — do NOT manually call `services.AddScoped<>()` +- `ITransientDependency` — new instance per injection +- `ISingletonDependency` — single shared instance +- `IScopedDependency` — one per request +- Application services, domain services, and repositories are auto-registered by ABP + +## Entities & Domain + +- Entities use rich domain model: private/protected setters, behaviour via methods. +- Include `protected` parameterless constructor for EF Core deserialization. +- Do not generate `Guid` keys inside constructors; accept `id` from `IGuidGenerator`. +- Reference other aggregate roots by Id only, not navigation properties. +- Domain services use `*Manager` suffix. +- Throw `BusinessException` with namespaced error codes for rule violations. + +## Application Services + +- Interface naming: `I*AppService` inheriting `IApplicationService`. +- All methods `async`, name ends with `Async`. +- Accept/return DTOs only, never entities. Define DTOs in `*.Application.Contracts`. +- Make all public methods `virtual`. +- Use **Mapperly** (`ObjectMapper.Map<>()`) for DTO mapping. Do NOT use AutoMapper. +- Mapper classes: `*MapperlyProfile.cs` decorated with `[Mapper]`, inheriting `MapperBase` or `TwoWayMapperBase`. + +## Code Style + +- 4 spaces indentation, no tabs +- No emojis in comments +- Always use braces, even for single-line statements +- Use `nameof` instead of string literals when referring to member names +- Prefer pattern matching and switch expressions where appropriate +- All user-facing text must be localized via `L["Key"]`. No hardcoded English strings. +- Permissions defined in `*PermissionDefinitionProvider` in Application.Contracts. +- Do not call other application services within the same module; push shared logic to domain services. + +## Naming Conventions + +- Follow PascalCase for public members, types, and methods +- Use camelCase for private fields and local variables +- Prefix interface names with `I` +- Domain Services: `*Manager` suffix (e.g., `AssessmentManager`) +- Application Services: `*AppService` suffix (e.g., `ApplicationAppService`) +- DTOs: Descriptive suffixes (`CreateApplicationDto`, `UpdateApplicationDto`, `ApplicationDto`) +- Event Transfer Objects: `*Eto` suffix for distributed events + +## DTOs vs Entities + +- Application services MUST accept and return DTOs only, never entities +- Use `ObjectMapper` (Mapperly) to map between entities and DTOs +- Define mappers in `*MapperlyProfile` class in Application project + +## Authorization + +- Apply `[Authorize(PermissionName)]` attributes on application service methods +- Define permissions in `*Permissions` static class in Domain.Shared project + +## Multi-Tenancy + +- Tenant entities MUST implement `IMultiTenant` interface +- NEVER manually filter by `TenantId` — ABP handles this automatically +- Use `GrantTenantDbContext` for tenant data, `GrantManagerDbContext` for host data + +## Error Handling + +- Use `BusinessException` for domain-level errors with namespaced error codes (e.g., `"GrantManager:ApplicationNotFound"`) +- Map error codes to localization keys for user-friendly messages +- Use `.WithData("key", value)` for localized message interpolation +- Catch specific exception types, not generic `Exception` + +## Common Mistakes to Avoid + +- Don't expose entities from application services — always return DTOs +- Don't put business logic in application services — use domain services +- Don't create custom repositories unnecessarily — use generic `IRepository` first +- Don't mix host and tenant data in same DbContext +- Don't ignore nullable warnings — fix them properly +- Don't use `DateTime.Now` — use `Clock.Now` or inject `IClock` +- Don't use `Guid.NewGuid()` — use `GuidGenerator.Create()` +- Don't use `services.AddScoped<>()` for ABP services — use marker interfaces +- Don't call application services from within the same module — extract shared logic to a domain service +- Don't embed entity name in app service methods — use `GetAsync`, not `GetApplicationAsync` diff --git a/applications/Unity.GrantManager/.claude/rules/efcore.md b/applications/Unity.GrantManager/.claude/rules/efcore.md new file mode 100644 index 0000000000..07bd97e41a --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/efcore.md @@ -0,0 +1,25 @@ +--- +globs: "**/EntityFrameworkCore/**/*.cs" +--- + +# EF Core Conventions for Unity Grant Manager + +- Provider: **Npgsql** (PostgreSQL 17). +- Two database contexts: `GrantManagerDbContext` (host) and `GrantTenantDbContext` (tenant). +- Entity configuration is done inline in `OnModelCreating` of `GrantManagerDbContext` and `GrantTenantDbContext`. +- When configuring entities, follow ABP conventions (e.g., table naming, key configuration) consistently. +- Use `options.AddDefaultRepositories(includeAllEntities: true)` in `GrantManagerEntityFrameworkCoreModule`. +- Prefer ABP's generated default repositories; add custom repositories only when additional behavior is required. +- Tests use **SQLite in-memory** databases, not PostgreSQL. + +## Migrations + +Always specify the context when adding migrations: + +```bash +# Host migrations +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations + +# Tenant migrations +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations +``` diff --git a/applications/Unity.GrantManager/.claude/rules/javascript.md b/applications/Unity.GrantManager/.claude/rules/javascript.md new file mode 100644 index 0000000000..1cb2f05bc9 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/javascript.md @@ -0,0 +1,61 @@ +--- +globs: "**/*.js" +--- + +# JavaScript Development Standards + +> JavaScript development standards for ABP Framework frontend patterns. + +- Variables should be declared with "let" or "const" instead of "var" + +## General Patterns + +- Wrap all page scripts in IIFE: `(function ($) { ... })(jQuery);` +- Never create global JavaScript variables +- Use `var l = abp.localization.getResource('GrantManager');` for all user-facing text +- Use ABP's dynamic JavaScript API client proxies instead of manual AJAX + +## ABP JavaScript Utilities + +- Notifications: `abp.notify.success()`, `.error()`, `.warn()`, `.info()` +- Confirmation: `abp.message.confirm()` for destructive actions +- Authorization: `abp.auth.isGranted()` for permission checks +- Busy indicators: `abp.ui.setBusy()` / `abp.ui.clearBusy()` +- Localization: `l('LocalizationKey')` — never hardcode user-facing strings + +## DataTables Integration + +- Use DataTables.net 2.x with Bootstrap 5 integration (`datatables.net-bs5`) +- Always wrap configuration with `abp.libs.datatables.normalizeConfiguration()` +- Use `abp.libs.datatables.createAjax()` for server-side pagination +- Use `rowAction` for action buttons with `abp.auth.isGranted()` visibility checks +- Use `dataFormat` property for automatic date/boolean formatting +- Always call `dataTable.ajax.reload()` after CRUD operations + +## Modal Manager + +- Use `abp.ModalManager` for all modal dialogs +- Configure with `viewUrl`, `scriptUrl`, and `modalClass` +- Implement `onResult()` callback to reload DataTable after save +- Modal script classes: register in `abp.modals.*` namespace +- Return `NoContent()` from Razor Page handler to close modal + +## DOM Auto-Initialization + +- ABP auto-initializes: tooltips, popovers, datepickers, AJAX forms, autocomplete selects +- Use `data-bs-toggle="tooltip"` for tooltips +- Use `class="auto-complete-select"` with `data-autocomplete-*` attributes for lookups +- Use `data-ajaxForm="true"` for AJAX form submission + +## Client-Side Package Management + +- Add NPM packages to `package.json`, prefer `@abp/*` packages +- Configure `abp.resourcemapping.js` to map from `node_modules` to `wwwroot/libs` +- Run `abp install-libs` to copy resources +- Add to bundle contributor in `Unity.Theme.UX2` module + +## WaterMark +- Whenever we edit a .js file, we should add a watermark to the top of the file with the following format: +```javascript +// - - + diff --git a/applications/Unity.GrantManager/.claude/rules/security.md b/applications/Unity.GrantManager/.claude/rules/security.md new file mode 100644 index 0000000000..48fe0e92da --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/security.md @@ -0,0 +1,49 @@ +--- +globs: "**/*.cs, **/*.cshtml, **/*.js" +--- + +# Security Standards + +> Security best practices for Unity Grant Manager. + +## Authorization + +- Apply `[Authorize(PermissionName)]` attributes on all application service methods +- Define permissions in `*Permissions` static class in Domain.Shared project +- Use `abp.auth.isGranted()` in JavaScript for UI permission checks +- Never rely solely on UI-level permission hiding — always enforce server-side + +## Multi-Tenancy Security + +- Never manually filter by `TenantId` — ABP handles tenant isolation automatically +- Ensure tenant-scoped entities implement `IMultiTenant` +- Test cross-tenant data isolation explicitly +- Use `GrantTenantDbContext` for tenant data, `GrantManagerDbContext` for host data +- Be cautious with `[IgnoreMultiTenancy]` — understand the security implications + +## Input Validation + +- Validate all inputs at the application service boundary using data annotations or FluentValidation +- Use ABP's `Check.*` methods for domain-level validation (e.g., `Check.NotNullOrWhiteSpace`) +- Sanitize user inputs before storage — prevent XSS and injection attacks +- Use parameterized queries — never concatenate user input into SQL + +## Secrets Management + +- Never commit secrets, connection strings, or API keys to source code +- Use environment variables or secure configuration providers +- Reference `.env.example` for required environment variables +- Sensitive configuration is stored in OpenShift secrects and Hashicorp Vault when deployed + +## Authentication + +- Authentication is handled via Keycloak (OpenID Connect) +- Do not implement custom authentication — use ABP's identity infrastructure +- Ensure all API endpoints require authentication unless explicitly public + +## Data Protection + +- Use Redis-backed data protection for key storage in distributed deployments +- Encrypt sensitive data at rest when required by compliance +- Follow government security standards (BC Government policies) +- Audit logging is enabled via ABP — ensure sensitive operations are captured diff --git a/applications/Unity.GrantManager/.claude/rules/testing.md b/applications/Unity.GrantManager/.claude/rules/testing.md new file mode 100644 index 0000000000..538fd25278 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/rules/testing.md @@ -0,0 +1,30 @@ +--- +globs: "**/test/**/*.cs" +--- + +# Testing Conventions for Unity Grant Manager + +- Framework: **xUnit 2.9.3** with **Shouldly 4.3.0** assertions and **NSubstitute 5.3.0** mocks. +- Tests use in-memory database providers (SQLite in-memory for most test projects; `Unity.GrantManager.Web.Tests` uses `Microsoft.EntityFrameworkCore.InMemory`). No external PostgreSQL/database setup is required. +- Test class naming: `*Tests.cs`. +- Base class hierarchy: `AbpIntegratedTest` → `GrantManagerTestBase` → domain-specific bases. +- Use `[Fact]` for single tests, `[Theory]` with `[InlineData]` for parameterized. +- Assertions: Shouldly (`result.ShouldBe(expected)`, `result.ShouldNotBeNull()`). Do NOT use `Assert.*`. +- Mocking: NSubstitute (`Substitute.For()`). Do NOT use Moq. +- JSON test fixtures loaded from `AppDomain.CurrentDomain.BaseDirectory`. +- Run all tests: `dotnet test Unity.GrantManager.sln --no-build` +- Test method naming: `Should_[Expected]_[Scenario]` +- Follow Arrange-Act-Assert pattern consistently +- Do not emit "Arrange", "Act", or "Assert" comments in generated tests + +## Multi-Tenancy Testing + +- Test tenant data isolation using `CurrentTenant.Change(tenantId)` +- Verify that data created in one tenant is not visible in another +- Test both host-level and tenant-level operations + +## Test Data Management + +- Use helper methods for test data creation (e.g., `CreateTestApplicationAsync()`) +- Use static test data constants for well-known IDs +- Keep test data self-contained — each test should set up its own state diff --git a/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md b/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md new file mode 100644 index 0000000000..8ef9306fc6 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/abp-cli/SKILL.md @@ -0,0 +1,78 @@ +--- +name: abp-cli +description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install NPM libraries, or use ABP Suite. +--- + +# ABP CLI Commands + +> **Full documentation**: https://abp.io/docs/latest/cli +> Use `abp help [command]` for detailed options. + +## Generate Client Proxies + +```bash +# URL flag: `-u` (short) or `--url` (long). Use whichever your team prefers, but keep it consistent. +# +# Angular (host must be running) +abp generate-proxy -t ng + +# C# client proxies +abp generate-proxy -t csharp -u https://localhost:44300 + +# Integration services only (microservices) +abp generate-proxy -t csharp -u https://localhost:44300 -st integration + +# JavaScript +abp generate-proxy -t js -u https://localhost:44300 +``` + +## Install Client-Side Libraries + +```bash +# Install NPM packages for MVC/Blazor Server +abp install-libs +``` + +## Add Package Reference + +```bash +# Add project reference with module dependency +abp add-package-ref Acme.BookStore.Domain +abp add-package-ref Acme.BookStore.Domain -t Acme.BookStore.Application +``` + +## Module Operations + +```bash +# Create new module in solution +abp new-module Acme.OrderManagement -t module:ddd + +# Install published module +abp install-module Volo.Blogging + +# Add ABP NuGet package +abp add-package Volo.Abp.Caching.StackExchangeRedis +``` + +## Update & Clean + +```bash +abp update # Update all ABP packages +abp update --version 8.0.0 # Specific version +abp clean # Delete bin/obj folders +``` + +## Quick Reference + +| Task | Command | +|------|---------| +| Angular proxies | `abp generate-proxy -t ng` | +| C# proxies | `abp generate-proxy -t csharp -u URL` | +| Install JS libs | `abp install-libs` | +| Add reference | `abp add-package-ref PackageName` | +| Create module | `abp new-module ModuleName` | +| Install module | `abp install-module ModuleName` | +| Update packages | `abp update` | +| Clean solution | `abp clean` | +| Suite CRUD | `abp suite generate -e entity.json -s solution.sln` | +| Get help | `abp help [command]` | diff --git a/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md new file mode 100644 index 0000000000..d12d277aee --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md @@ -0,0 +1,102 @@ +--- +name: unity-application-layer +description: ABP Application Services, DTOs, AutoMapper profiles, validation, and error handling for Unity. Use when creating or modifying app services, DTOs, or mapping profiles in Application or Application.Contracts projects. +--- + +# Unity Application Layer Patterns + +## Application Service Contracts (Application.Contracts) + +- Interface naming: `I*AppService` inheriting `IApplicationService`. +- Define DTOs in `*.Application.Contracts` — never in Domain or Web. +- All methods async, end with `Async`. +- Do NOT repeat entity name in method names: use `GetAsync`, not `GetGrantAsync`. + +```csharp +public interface IGrantAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task> GetListAsync(GetGrantListInput input); + Task CreateAsync(CreateGrantDto input); + Task UpdateAsync(Guid id, UpdateGrantDto input); // ID separate from DTO + Task DeleteAsync(Guid id); +} +``` + +## DTO Conventions + +| Purpose | Convention | Example | +|---------|------------|---------| +| Query input | `Get{Entity}Input` | `GetGrantInput` | +| List query | `Get{Entity}ListInput` | `GetGrantListInput` | +| Create input | `Create{Entity}Dto` | `CreateGrantDto` | +| Update input | `Update{Entity}Dto` | `UpdateGrantDto` | +| Output | `{Entity}Dto` | `GrantDto` | + +- Use data annotations for validation; reuse constants from Domain.Shared. +- Do NOT share input DTOs between methods. +- Do NOT put logic in DTOs (except `IValidatableObject` when necessary). + +## Implementation (Application) + +- Inherit from `ApplicationService`. +- Make all public methods `virtual`. +- Prefer `protected virtual` over `private` for helper methods. +- Use dedicated repositories, not inline LINQ in app services. +- Call `repository.UpdateAsync()` explicitly after mutations (don't assume change tracking). +- Do NOT use web types (`IFormFile`, `Stream`) — accept `byte[]` from controllers. +- Do NOT call other app services in the same module. Use domain services or repositories. + +## Object Mapping (Mapperly) + +This project uses **Mapperly** (not AutoMapper). Mapper classes are defined using `Riok.Mapperly.Abstractions` and `Volo.Abp.Mapperly`: + +```csharp +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +[Mapper] +public partial class GrantToGrantDtoMapper : MapperBase +{ + public override partial GrantDto Map(Grant source); + public override partial void Map(Grant source, GrantDto destination); +} + +// For bidirectional mapping: +[Mapper] +public partial class ZoneGroupDefinitionMapper : TwoWayMapperBase +{ + public override partial ZoneGroupDefinitionDto Map(ZoneGroupDefinition source); + public override partial void Map(ZoneGroupDefinition source, ZoneGroupDefinitionDto destination); + public override partial ZoneGroupDefinition ReverseMap(ZoneGroupDefinitionDto source); + public override partial void ReverseMap(ZoneGroupDefinitionDto source, ZoneGroupDefinition destination); +} +``` + +- Mapper files follow `*MapperlyProfile.cs` naming; each Application and Web project has its own file. +- Use `[MapperIgnoreTarget(nameof(...))]` to skip properties, `[MapProperty]` to rename, and `[MapPropertyFromSource]` for custom resolver methods. +- Call sites still use `ObjectMapper.Map(source)` — Mapperly provides the source-generated implementation. + +## Error Handling + +```csharp +// Business rule violation — use namespaced error code +throw new BusinessException("GrantManager:DuplicateName") + .WithData("Name", name); + +// Entity not found +throw new EntityNotFoundException(typeof(Grant), id); + +// User-facing message (use localized string) +throw new UserFriendlyException(L["GrantNotAvailable"]); +``` + +## Authorization + +- Use `[Authorize(PermissionName)]` on service methods. +- Permission names defined as constants in `*Permissions` classes in Application.Contracts. + +## Cross-Module Calls + +- You MAY call other modules' app services via their Application.Contracts interfaces. +- Do NOT call app services within the same module — use domain services or repositories. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md new file mode 100644 index 0000000000..f33f45ffc8 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-domain-driven-design/SKILL.md @@ -0,0 +1,105 @@ +--- +name: unity-domain-driven-design +description: DDD patterns for Unity - Entities, Aggregate Roots, Repositories, Domain Services, Domain Events. Use when creating or modifying entities, repositories, or domain services in Domain or Domain.Shared projects. +--- + +# Unity ABP DDD Patterns + +> Based on ABP Framework DDD conventions. This project uses ABP 10.5 with PostgreSQL 17 and EF Core 10. + +## Entities + +- Define entities in `*.Domain` projects. +- Use **rich domain model**: private/protected setters with methods that enforce invariants. +- Always provide a `protected` parameterless constructor for EF Core. +- Accept `Guid id` in the primary constructor; do NOT generate GUIDs inside constructors. Use `IGuidGenerator` from calling code. +- Make members `virtual` for ORM proxy compatibility. +- Initialize sub-collections in the primary constructor. + +```csharp +public class Grant : AuditedAggregateRoot +{ + public string Name { get; private set; } + public GrantStatus Status { get; private set; } + public ICollection Applications { get; private set; } + + protected Grant() { } // For EF Core + + public Grant(Guid id, string name) : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Status = GrantStatus.Draft; + Applications = new List(); + } + + public void SetName(string name) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + } +} +``` + +## Aggregate Roots + +- Use a single `Id` property, prefer `Guid` keys. +- Inherit from `AggregateRoot` or audited base classes (`AuditedAggregateRoot`, `FullAuditedAggregateRoot`). +- Reference other aggregate roots **by Id only** — no cross-aggregate navigation properties. +- Keep aggregates small. + +## Repositories + +- Define repository interfaces in the Domain layer. +- One repository per aggregate root only. Never create repositories for child entities. +- Custom repository interface should inherit `IRepository`. +- All methods async with `CancellationToken cancellationToken = default`. +- Single-entity methods: `includeDetails = true` by default. +- List methods: `includeDetails = false` by default. + +```csharp +public interface IGrantRepository : IRepository +{ + Task FindByNameAsync(string name, bool includeDetails = true, CancellationToken cancellationToken = default); + Task> GetListByStatusAsync(GrantStatus status, bool includeDetails = false, CancellationToken cancellationToken = default); +} +``` + +## Domain Services + +- Naming: `*Manager` suffix (e.g., `GrantManager`). +- No interface by default unless multiple implementations are needed. +- Accept/return domain objects, not DTOs. +- Do NOT depend on authenticated user; accept required values from application layer. +- Use `GuidGenerator`, `Clock` from base class properties. + +```csharp +public class GrantManager : DomainService +{ + private readonly IGrantRepository _grantRepository; + + public GrantManager(IGrantRepository grantRepository) + { + _grantRepository = grantRepository; + } + + public async Task CreateAsync(string name) + { + var existing = await _grantRepository.FindByNameAsync(name); + if (existing != null) + throw new BusinessException("GrantManager:NameAlreadyExists").WithData("Name", name); + + return new Grant(GuidGenerator.Create(), name); + } +} +``` + +## Domain Events + +- `AddLocalEvent()` — same transaction, can access full entity state. +- `AddDistributedEvent()` — async, use ETOs defined in Domain.Shared. +- This project uses **RabbitMQ** for distributed events via `IDistributedEventBus`. + +## Shared Constants + +- Define constants, enums, and error codes in `*.Domain.Shared`. +- Localization resources (JSON) live under `Domain.Shared/Localization/*/en.json`. +- Error codes: namespaced as `ModuleName:ErrorCode`. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md new file mode 100644 index 0000000000..45e49b4ca5 --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md @@ -0,0 +1,112 @@ +--- +name: unity-ef-core +description: ABP Entity Framework Core for Unity - DbContext configuration, entity mapping, repository implementation, EF migrations. Use when working in EntityFrameworkCore projects, adding migrations, or implementing repositories. +--- + +# Unity EF Core Patterns + +> This project uses EF Core 10 with PostgreSQL 17 (Npgsql). Tests use SQLite in-memory. + +## Database Contexts + +This project has **two distinct database contexts**: + +| Context | Purpose | Migrations Directory | +|---------|---------|---------------------| +| `GrantManagerDbContext` | Host/shared system tables | `Migrations/HostMigrations` | +| `GrantTenantDbContext` | Per-tenant isolated data | `Migrations/TenantMigrations` | + +Always specify the context when adding migrations: + +```bash +cd src/Unity.GrantManager.EntityFrameworkCore + +# Host migration +dotnet ef migrations add --context GrantManagerDbContext --output-dir Migrations/HostMigrations + +# Tenant migration +dotnet ef migrations add --context GrantTenantDbContext --output-dir Migrations/TenantMigrations +``` + +## Entity Configuration + +Entity mapping is done via extension methods on `ModelBuilder`, NOT inline in `OnModelCreating`. + +```csharp +public static class GrantManagerDbContextModelCreatingExtensions +{ + public static void ConfigureGrantManager(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable(GrantManagerConsts.DbTablePrefix + "Grants", GrantManagerConsts.DbSchema); + b.ConfigureByConvention(); // Always call this first + + b.Property(x => x.Name) + .IsRequired() + .HasMaxLength(GrantConsts.MaxNameLength); + + b.HasIndex(x => x.Name); + }); + } +} +``` + +**Rules:** +- Always call `b.ConfigureByConvention()` for every entity. +- Use table prefix from constants (not hardcoded). +- Default schema should be `null`. + +## Repository Implementation + +```csharp +public class GrantRepository : EfCoreRepository, IGrantRepository +{ + public GrantRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) { } + + public async Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + return await dbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync(g => g.Name == name, GetCancellationToken(cancellationToken)); + } +} +``` + +- Use DbContext interface as generic parameter. +- Pass cancellation tokens via `GetCancellationToken(cancellationToken)`. +- Use `IncludeDetails()` extensions per aggregate root. + +## Module Registration + +```csharp +context.Services.AddAbpDbContext(options => +{ + options.AddDefaultRepositories(); // Aggregate roots only, NOT includeAllEntities: true +}); + +Configure(options => +{ + options.UseNpgsql(); // PostgreSQL +}); +``` + +## Never Do + +| Don't | Do Instead | +|-------|-----------| +| `AddDefaultRepositories(includeAllEntities: true)` | `AddDefaultRepositories()` — aggregate roots only | +| Skip `ConfigureByConvention()` | Always call it first in entity config | +| Inject DbContext in app/domain services | Use `IRepository` or custom repository interface | +| Use lazy loading | Explicit `.Include()` via `IncludeDetails()` | + +## Migrations .editorconfig + +The `Migrations/` folder has its own `.editorconfig` suppressing analyzer warnings (S1128, S1192, CS8981, CA1861, IDE naming rules). This is intentional — do not modify migration files for style. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md new file mode 100644 index 0000000000..c26502645d --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md @@ -0,0 +1,113 @@ +--- +name: unity-module-structure +description: ABP module architecture and layering rules for Unity. Use when creating new modules, adding cross-module dependencies, or understanding project organization and dependency direction. +--- + +# Unity Module Architecture + +## Module Layout + +Each ABP module follows a standard layered structure under `modules/`: + +``` +Unity.{ModuleName}/ + src/ + Unity.{ModuleName}.Domain.Shared/ ← Enums, constants, localization, ETOs + Unity.{ModuleName}.Domain/ ← Entities, repository interfaces, domain services + Unity.{ModuleName}.Application.Contracts/ ← DTOs, app service interfaces + Unity.{ModuleName}.Application/ ← App service implementations, Mapperly mappers + Unity.{ModuleName}.EntityFrameworkCore/ ← DbContext, migrations (if module has own DB tables) + Unity.{ModuleName}.HttpApi/ ← REST controllers + Unity.{ModuleName}.HttpApi.Client/ ← Remote client proxies + Unity.{ModuleName}.Web/ ← Razor Pages, view components + test/ + Unity.{ModuleName}.TestBase/ + Unity.{ModuleName}.Application.Tests/ + Unity.{ModuleName}.Domain.Tests/ + Unity.{ModuleName}.EntityFrameworkCore.Tests/ +``` + +Not all modules have every layer. Simpler modules may only have `Application`, `Application.Contracts`, `Shared`, and `Web`. + +## Current Modules + +| Module | Layers Present | Purpose | +|--------|---------------|---------| +| **Unity.Flex** | Shared, App.Contracts, App, Web, Tests | Dynamic forms/worksheets | +| **Unity.Notifications** | Full stack (Domain→Web, HttpApi, EF) | Email/messaging | +| **Unity.Payments** | Shared, App.Contracts, App, Web, Tests | Financial transactions | +| **Unity.Reporting** | Shared, App.Contracts, App, Web, Tests | Analytics & reports | +| **Unity.AI** | Shared, App.Contracts, App, Web | AI analysis (OpenAI) | +| **Unity.TenantManagement** | App.Contracts, App, HttpApi, Web, Tests | Multi-tenant admin | +| **Unity.Identity.Web** | Web, Tests | OIDC authentication UI | +| **Unity.Theme.UX2** | Theme package, Tests | Custom Razor Pages theme | +| **Unity.SharedKernel** | Single project | Cross-cutting utilities | + +## Dependency Direction (Strict) + +``` +Web → HttpApi → Application.Contracts +Application → Domain + Application.Contracts +Domain → Domain.Shared +EntityFrameworkCore → Domain only +``` + +### Rules + +- Web/HttpApi must NEVER depend on Application (only Application.Contracts). +- Application must NEVER depend on Web or EF Core. +- Domain must NEVER depend on Application, Web, or EF Core. +- Domain.Shared must have NO dependencies on other layers. +- EF Core must ONLY depend on Domain. + +## ABP Module Classes + +Every package has exactly one `AbpModule` class with `[DependsOn]` attributes. + +```csharp +[DependsOn( + typeof(GrantManagerDomainModule), + typeof(AbpEntityFrameworkCoreModule) +)] +public class GrantManagerEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(); + }); + } +} +``` + +## Multi-Tenancy + +- The system uses ABP multi-tenancy with separate database per tenant. +- `GrantManagerDbContext` = host context, `GrantTenantDbContext` = tenant context. +- Tenant-scoped data is accessed via `ICurrentTenant` / tenant switching. +- The `Unity.TenantManagement` module handles tenant administration. + +## Adding a New Feature + +1. Identify which module the feature belongs to. +2. Add entities/repositories in Domain layer. +3. Add DTOs/interfaces in Application.Contracts. +4. Implement app services in Application. +5. Add EF Core configuration if new tables are needed. +6. Add UI in Web layer. +7. Add tests in the module's test projects. +8. Register the module class with `[DependsOn]`. +9. Run `dotnet build Unity.GrantManager.sln` and `dotnet test Unity.GrantManager.sln` to verify. + +## Localization + +Each module with Domain.Shared has its own localization under: +`src/Unity.{ModuleName}.Domain.Shared/Localization/{ModuleName}/en.json` + +Use `L["Key"]` in application services and pages. All user-facing text must be localized. + +## Permissions + +Define in `*PermissionDefinitionProvider` in Application.Contracts. +Permission names follow `{ModuleName}.{Resource}.{Action}` convention. diff --git a/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md new file mode 100644 index 0000000000..4ddd9b1d0c --- /dev/null +++ b/applications/Unity.GrantManager/.claude/skills/unity-testing/SKILL.md @@ -0,0 +1,155 @@ +--- +name: unity-testing +description: Testing patterns for Unity - xUnit, Shouldly assertions, NSubstitute mocks, ABP test infrastructure. Use when writing or modifying unit tests or integration tests. +--- + +# Unity Testing Patterns + +## Test Infrastructure + +| Aspect | Value | +|--------|-------| +| Framework | xUnit 2.9.3 | +| Assertions | Shouldly 4.3.0 | +| Mocking | NSubstitute 5.3.0 | +| Database | In-memory (SQLite for most projects; EFCore.InMemory for Web tests – no PostgreSQL required) | +| Base Classes | ABP `AbpIntegratedTest` | +| Target | .NET 10 | + +## Test Project Locations + +``` +test/ + Unity.GrantManager.TestBase/ ← Shared fixtures & test data + Unity.GrantManager.Application.Tests/ ← App service tests + Unity.GrantManager.Domain.Tests/ ← Domain logic tests + Unity.GrantManager.EntityFrameworkCore.Tests/ + Unity.GrantManager.Web.Tests/ +modules/Unity.*/test/ ← Each module has its own test projects +``` + +## Running Tests + +```bash +# All tests (~470 tests, ~2 min) +dotnet test Unity.GrantManager.sln + +# Single project +dotnet test test/Unity.GrantManager.Application.Tests/ + +# After build (faster) +dotnet test Unity.GrantManager.sln --no-build +``` + +## Base Class Hierarchy + +``` +AbpIntegratedTest (Volo.Abp.Testing) +└── GrantManagerTestBase (shared UoW helpers) + ├── GrantManagerDomainTestBase (domain tests) + ├── GrantManagerEntityFrameworkCoreTestBase + └── Module-specific bases: + ├── FlexTestBaseModule + ├── TenantManagementTestBase + └── ReportingTestBase +``` + +## Writing Tests + +### Unit Test Example (with mocking) + +```csharp +public class MyServiceTests +{ + private readonly IMyRepository _repository; + private readonly MyService _sut; + + public MyServiceTests() + { + _repository = Substitute.For(); + _sut = new MyService(_repository); + } + + [Fact] + public async Task CreateAsync_WithValidInput_ShouldSucceed() + { + // Arrange + _repository.FindByNameAsync(Arg.Any()).Returns((MyEntity?)null); + + // Act + var result = await _sut.CreateAsync("test"); + + // Assert + result.ShouldNotBeNull(); + result.Name.ShouldBe("test"); + } +} +``` + +### Integration Test Example (ABP) + +```csharp +public class GrantAppServiceTests : GrantManagerApplicationTestBase +{ + private readonly IGrantAppService _grantAppService; + + public GrantAppServiceTests() + { + _grantAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_Grant_By_Id() + { + var result = await _grantAppService.GetAsync(GrantManagerTestData.GrantId); + result.ShouldNotBeNull(); + result.Id.ShouldBe(GrantManagerTestData.GrantId); + } +} +``` + +### Parameterized Tests + +```csharp +[Theory] +[InlineData("schema1.json", 128)] +[InlineData("schema2.json", 10)] +public void TestMapping(string filename, int expectedCount) +{ + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestData", filename); + var json = File.ReadAllText(path); + var result = Parse(json); + result.Count.ShouldBe(expectedCount); +} +``` + +## Test Data + +- JSON fixtures are loaded from `AppDomain.CurrentDomain.BaseDirectory` subdirectories. +- Domain tests include JSON files in `Intake/Files/*.json` and `Intake/Mapping/*.json` (copied to output via `.csproj`). +- Shared test data constants live in `*TestData.cs` classes within TestBase projects. + +## Web Tests + +Web tests use `[Collection]` fixture pattern: + +```csharp +[Collection(WebTestCollection.Name)] +public class MyWidgetTests +{ + private readonly IAbpLazyServiceProvider _lazyServiceProvider; + + public MyWidgetTests(WebTestFixture fixture) + { + _lazyServiceProvider = fixture.Services.GetRequiredService(); + } +} +``` + +## Conventions + +- Test class naming: `*Tests.cs` +- Method naming: `Should_ExpectedBehavior_When_Condition` or `MethodName_Scenario_ExpectedResult` +- Always use `Shouldly` for assertions (not `Assert.Equal`) +- Always use `NSubstitute` for mocking (not Moq) +- Test runner config: `xunit.runner.json` with `"shadowCopy": false` From 595bfa677f4e7e552c471cbdee7f0519ada8ee1d Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 11 Aug 2026 14:58:52 -0700 Subject: [PATCH 036/121] bugfix/AB#34036-CrashLoopAndSupplierException-AIReview --- .../Middleware/ExceptionCounterMiddleware.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs index 5f232169f1..9cdff84b83 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Middleware/ExceptionCounterMiddleware.cs @@ -20,9 +20,9 @@ public class ExceptionCounterMiddleware( ILogger logger) { private static readonly TimeSpan PersistenceBackoff = TimeSpan.FromSeconds(30); - private readonly object persistenceGate = new(); - private bool persistenceInFlight; - private DateTimeOffset persistenceDisabledUntil; + private readonly object _persistenceGate = new(); + private bool _persistenceInFlight; + private DateTimeOffset _persistenceDisabledUntil; // Notify only in these environments; add "Staging" if desired private static readonly HashSet NotifyEnvironments = @@ -130,14 +130,14 @@ private void QueueLogNotification(HttpContext context, Exception ex) // Acquire the single-flight gate only once the synchronous, potentially-throwing prep // above has succeeded — otherwise an exception here would leave persistenceInFlight // stuck "true" forever, since the Task.Run below (whose finally resets it) never starts. - lock (persistenceGate) + lock (_persistenceGate) { - if (persistenceInFlight || DateTimeOffset.UtcNow < persistenceDisabledUntil) + if (_persistenceInFlight || DateTimeOffset.UtcNow < _persistenceDisabledUntil) { return; } - persistenceInFlight = true; + _persistenceInFlight = true; } _ = Task.Run(async () => @@ -306,9 +306,9 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto } finally { - lock (persistenceGate) + lock (_persistenceGate) { - persistenceInFlight = false; + _persistenceInFlight = false; } } }); @@ -316,9 +316,9 @@ await exceptionLogs.CreateAsync(new CreateExceptionLogDto private void OpenPersistenceBackoff() { - lock (persistenceGate) + lock (_persistenceGate) { - persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); + _persistenceDisabledUntil = DateTimeOffset.UtcNow.Add(PersistenceBackoff); } } From 9d1f0f4bd5ac16d73e88c1eb797b731997c5753b Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 11 Aug 2026 15:11:13 -0700 Subject: [PATCH 037/121] AB#32831 remove watermark check --- applications/Unity.GrantManager/.claude/rules/javascript.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/applications/Unity.GrantManager/.claude/rules/javascript.md b/applications/Unity.GrantManager/.claude/rules/javascript.md index 1cb2f05bc9..5885faa808 100644 --- a/applications/Unity.GrantManager/.claude/rules/javascript.md +++ b/applications/Unity.GrantManager/.claude/rules/javascript.md @@ -54,8 +54,4 @@ globs: "**/*.js" - Run `abp install-libs` to copy resources - Add to bundle contributor in `Unity.Theme.UX2` module -## WaterMark -- Whenever we edit a .js file, we should add a watermark to the top of the file with the following format: -```javascript -// - - From 0dd08d34dd8d7da9af88cb3d04e6055a5408900c Mon Sep 17 00:00:00 2001 From: Andre Goncalves <98196495+AndreGAot@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:13:38 -0700 Subject: [PATCH 038/121] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../.claude/agents/efcore-migration-planner.md | 2 +- applications/Unity.GrantManager/.claude/rules/security.md | 2 +- .../.claude/skills/unity-application-layer/SKILL.md | 2 +- .../Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md | 3 +-- .../.claude/skills/unity-module-structure/SKILL.md | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md index eab4dad198..64e91f6487 100644 --- a/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md +++ b/applications/Unity.GrantManager/.claude/agents/efcore-migration-planner.md @@ -41,6 +41,6 @@ Plan schema changes, mapping updates, and migration execution for the correct da - Apply the `unity-ef-core` skill's patterns. - Follow `applications/Unity.GrantManager/.github/instructions/efcore.instructions.md`. - Always call `ConfigureByConvention()` for mapped entities. -- Do not use `includeAllEntities: true` with default repositories. +- Default repositories are currently registered with `includeAllEntities: true`; remove it only when you intentionally want aggregate-roots-only repositories and have verified no callers rely on entity repositories. - Always specify context (`GrantManagerDbContext` or `GrantTenantDbContext`) for migration commands. - This agent plans only — it does not run `dotnet ef migrations add` or edit files itself. diff --git a/applications/Unity.GrantManager/.claude/rules/security.md b/applications/Unity.GrantManager/.claude/rules/security.md index 48fe0e92da..73f0eecac5 100644 --- a/applications/Unity.GrantManager/.claude/rules/security.md +++ b/applications/Unity.GrantManager/.claude/rules/security.md @@ -33,7 +33,7 @@ globs: "**/*.cs, **/*.cshtml, **/*.js" - Never commit secrets, connection strings, or API keys to source code - Use environment variables or secure configuration providers - Reference `.env.example` for required environment variables -- Sensitive configuration is stored in OpenShift secrects and Hashicorp Vault when deployed +- Sensitive configuration is stored in OpenShift secrets and HashiCorp Vault when deployed ## Authentication diff --git a/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md index d12d277aee..ea2111c494 100644 --- a/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md +++ b/applications/Unity.GrantManager/.claude/skills/unity-application-layer/SKILL.md @@ -1,6 +1,6 @@ --- name: unity-application-layer -description: ABP Application Services, DTOs, AutoMapper profiles, validation, and error handling for Unity. Use when creating or modifying app services, DTOs, or mapping profiles in Application or Application.Contracts projects. +description: ABP Application Services, DTOs, Mapperly mapping, validation, and error handling for Unity. Use when creating or modifying app services, DTOs, or mapping profiles in Application or Application.Contracts projects. --- # Unity Application Layer Patterns diff --git a/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md index 45e49b4ca5..6ed9e666a4 100644 --- a/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md +++ b/applications/Unity.GrantManager/.claude/skills/unity-ef-core/SKILL.md @@ -30,8 +30,7 @@ dotnet ef migrations add --context GrantTenantDbContext --output-dir Migr ## Entity Configuration -Entity mapping is done via extension methods on `ModelBuilder`, NOT inline in `OnModelCreating`. - +Entity mapping is primarily configured inline in `OnModelCreating` in `GrantManagerDbContext` / `GrantTenantDbContext`. Extension methods on `ModelBuilder` are also used for shared/module-specific configuration (e.g., `modelBuilder.ConfigureAI()`). ```csharp public static class GrantManagerDbContextModelCreatingExtensions { diff --git a/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md index c26502645d..851e3db082 100644 --- a/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md +++ b/applications/Unity.GrantManager/.claude/skills/unity-module-structure/SKILL.md @@ -75,7 +75,7 @@ public class GrantManagerEntityFrameworkCoreModule : AbpModule { context.Services.AddAbpDbContext(options => { - options.AddDefaultRepositories(); + options.AddDefaultRepositories(includeAllEntities: true); }); } } From 9e58736deb37bced6a69f554d10e3181e7697c7d Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 11 Aug 2026 15:59:28 -0700 Subject: [PATCH 039/121] feature/AB#33985-FixDBMigrator --- .../20260805185000_RebuildAIModels.cs | 161 +++++++++--------- 1 file changed, 85 insertions(+), 76 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs index 6846c54e87..28d7d6c9a7 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20260805185000_RebuildAIModels.cs @@ -21,13 +21,26 @@ ALTER TABLE "AI"."AIOperations" DROP INDEX IF EXISTS "AI"."IX_AIOperations_AIModelId"; DROP INDEX IF EXISTS "AI"."IX_AIModels_Name"; - ALTER TABLE "AI"."AIModels" + ALTER TABLE IF EXISTS "AI"."AIModels" RENAME TO "AIModels_Legacy"; - ALTER TABLE "AI"."AIModels_Legacy" - RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Legacy"; - - CREATE TABLE "AI"."AIModels" + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'AI' + AND t.relname = 'AIModels_Legacy' + AND c.conname = 'PK_AIModels' + ) THEN + ALTER TABLE "AI"."AIModels_Legacy" + RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Legacy"; + END IF; + END $$; + + CREATE TABLE IF NOT EXISTS "AI"."AIModels" ( "Id" uuid NOT NULL, "Name" character varying(200) NOT NULL, @@ -43,40 +56,31 @@ CREATE TABLE "AI"."AIModels" CONSTRAINT "PK_AIModels" PRIMARY KEY ("Id") ); - INSERT INTO "AI"."AIModels" - ( - "Id", - "Name", - "Provider", - "IsActive", - "SettingsJson", - "ExtraProperties", - "ConcurrencyStamp", - "CreationTime", - "CreatorId", - "LastModificationTime", - "LastModifierId" - ) - SELECT - "Id", - CASE "Name" - WHEN 'Gpt4oMini' THEN 'gpt-4o-mini' - WHEN 'Gpt5Mini' THEN 'gpt-5-mini' - WHEN 'Gpt5Nano' THEN 'gpt-5-nano' - ELSE "Name" - END, - 'OpenAI', - "IsActive", - "SettingsJson", - "ExtraProperties", - "ConcurrencyStamp", - "CreationTime", - "CreatorId", - "LastModificationTime", - "LastModifierId" - FROM "AI"."AIModels_Legacy"; - - DROP TABLE "AI"."AIModels_Legacy"; + DO $$ + BEGIN + IF to_regclass('"AI"."AIModels_Legacy"') IS NOT NULL THEN + INSERT INTO "AI"."AIModels" + ( + "Id", "Name", "Provider", "IsActive", "SettingsJson", + "ExtraProperties", "ConcurrencyStamp", "CreationTime", "CreatorId", + "LastModificationTime", "LastModifierId" + ) + SELECT + "Id", + CASE "Name" + WHEN 'Gpt4oMini' THEN 'gpt-4o-mini' + WHEN 'Gpt5Mini' THEN 'gpt-5-mini' + WHEN 'Gpt5Nano' THEN 'gpt-5-nano' + ELSE "Name" + END, + 'OpenAI', "IsActive", "SettingsJson", "ExtraProperties", + "ConcurrencyStamp", "CreationTime", "CreatorId", + "LastModificationTime", "LastModifierId" + FROM "AI"."AIModels_Legacy"; + END IF; + END $$; + + DROP TABLE IF EXISTS "AI"."AIModels_Legacy"; CREATE UNIQUE INDEX "IX_AIModels_Name" ON "AI"."AIModels" ("Name"); @@ -102,13 +106,26 @@ ALTER TABLE "AI"."AIOperations" DROP INDEX IF EXISTS "AI"."IX_AIOperations_AIModelId"; DROP INDEX IF EXISTS "AI"."IX_AIModels_Name"; - ALTER TABLE "AI"."AIModels" + ALTER TABLE IF EXISTS "AI"."AIModels" RENAME TO "AIModels_Current"; - ALTER TABLE "AI"."AIModels_Current" - RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Current"; - - CREATE TABLE "AI"."AIModels" + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'AI' + AND t.relname = 'AIModels_Current' + AND c.conname = 'PK_AIModels' + ) THEN + ALTER TABLE "AI"."AIModels_Current" + RENAME CONSTRAINT "PK_AIModels" TO "PK_AIModels_Current"; + END IF; + END $$; + + CREATE TABLE IF NOT EXISTS "AI"."AIModels" ( "Id" uuid NOT NULL, "Name" character varying(200) NOT NULL, @@ -123,38 +140,30 @@ CREATE TABLE "AI"."AIModels" CONSTRAINT "PK_AIModels" PRIMARY KEY ("Id") ); - INSERT INTO "AI"."AIModels" - ( - "Id", - "Name", - "IsActive", - "SettingsJson", - "ExtraProperties", - "ConcurrencyStamp", - "CreationTime", - "CreatorId", - "LastModificationTime", - "LastModifierId" - ) - SELECT - "Id", - CASE "Name" - WHEN 'gpt-4o-mini' THEN 'Gpt4oMini' - WHEN 'gpt-5-mini' THEN 'Gpt5Mini' - WHEN 'gpt-5-nano' THEN 'Gpt5Nano' - ELSE "Name" - END, - "IsActive", - "SettingsJson", - "ExtraProperties", - "ConcurrencyStamp", - "CreationTime", - "CreatorId", - "LastModificationTime", - "LastModifierId" - FROM "AI"."AIModels_Current"; - - DROP TABLE "AI"."AIModels_Current"; + DO $$ + BEGIN + IF to_regclass('"AI"."AIModels_Current"') IS NOT NULL THEN + INSERT INTO "AI"."AIModels" + ( + "Id", "Name", "IsActive", "SettingsJson", "ExtraProperties", + "ConcurrencyStamp", "CreationTime", "CreatorId", "LastModificationTime", + "LastModifierId" + ) + SELECT + "Id", + CASE "Name" + WHEN 'gpt-4o-mini' THEN 'Gpt4oMini' + WHEN 'gpt-5-mini' THEN 'Gpt5Mini' + WHEN 'gpt-5-nano' THEN 'Gpt5Nano' + ELSE "Name" + END, + "IsActive", "SettingsJson", "ExtraProperties", "ConcurrencyStamp", + "CreationTime", "CreatorId", "LastModificationTime", "LastModifierId" + FROM "AI"."AIModels_Current"; + END IF; + END $$; + + DROP TABLE IF EXISTS "AI"."AIModels_Current"; CREATE UNIQUE INDEX "IX_AIModels_Name" ON "AI"."AIModels" ("Name"); From 3beaa9000f3b1b901f87535c2db37ff7e912470d Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Tue, 11 Aug 2026 16:12:17 -0700 Subject: [PATCH 040/121] feature/AB#33985-FixDBMigrator --- .../Unity.GrantManager/ocUnityDbConnect.ps1 | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/applications/Unity.GrantManager/ocUnityDbConnect.ps1 b/applications/Unity.GrantManager/ocUnityDbConnect.ps1 index a575ec6c4b..68097f1f84 100644 --- a/applications/Unity.GrantManager/ocUnityDbConnect.ps1 +++ b/applications/Unity.GrantManager/ocUnityDbConnect.ps1 @@ -1,9 +1,30 @@ +# Prompt user for environment selection +$validEnvironments = @("dev", "test", "prod") +do { + Write-Host "Enter environment (dev, test, prod)" -ForegroundColor Green + $environment = Read-Host +} while (-not ($validEnvironments -contains $environment)) + +# Prompt user for cluster selection +$validPlatforms = @("gold", "silver") +do { + Write-Host "Enter OpenShift cluster (gold, silver)" -ForegroundColor Green + $platform = Read-Host +} while (-not ($validPlatforms -contains $platform.ToLowerInvariant())) + +$platform = $platform.ToLowerInvariant() +$server = if ($platform -eq "gold") { + "https://api.gold.devops.gov.bc.ca:6443" +} else { + "https://api.silver.devops.gov.bc.ca:6443" +} + # Prompt the user to optionally login to OpenShift Write-Host "Do you want to log in to OpenShift now? (y/n)" -ForegroundColor Green $loginResponse = Read-Host if ($loginResponse -match '^(y|yes)$') { try { - oc login --web --server=https://api.silver.devops.gov.bc.ca:6443 + oc login --web --server=$server } catch { Write-Host "Login failed. Please check your connection and credentials." -ForegroundColor Red @@ -11,13 +32,6 @@ if ($loginResponse -match '^(y|yes)$') { } } -# Prompt user for environment selection -$validEnvironments = @("dev", "test", "prod") -do { - Write-Host "Enter environment (dev, test, prod)" -ForegroundColor Green - $environment = Read-Host -} while (-not ($validEnvironments -contains $environment)) - # Define cluster mappings $clusterMappings = @{ @@ -37,8 +51,9 @@ if ($environment -eq 'prod') { } -# Configuration parameters (dynamically updated based on environment) -$NameSpace = "d18498-$environment" # OpenShift project namespace +# Configuration parameters (dynamically updated based on environment and cluster) +$namespacePrefix = if ($platform -eq "gold") { "ce395f" } else { "d18498" } +$NameSpace = "$namespacePrefix-$environment" # OpenShift project namespace $ClusterName = "$cluster-crunchy-postgres" $LocalPort = 5436 $RemotePort = 5432 From 9c7ea763208b06c03101b132af114f021a0181b7 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Wed, 5 Aug 2026 11:55:51 -0700 Subject: [PATCH 041/121] AB#33983 update AI architecture documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../modules/Unity.AI/docs/README.md | 3 + .../modules/Unity.AI/docs/configuration.md | 35 ++++++ .../modules/Unity.AI/docs/flow-map.md | 27 +++-- .../Unity.AI/docs/implementation-playbook.md | 64 +++-------- .../modules/Unity.AI/docs/index.md | 102 +++++------------- .../Unity.AI/docs/operation-pipeline.md | 5 +- .../docs/operations/form-scoresheet.md | 21 ++++ .../modules/Unity.AI/docs/prompt-map.md | 13 ++- .../DataSeed/AIPromptDataSeeder.cs | 8 +- .../Generation/AIGenerationAppService.cs | 1 + .../RateLimit/AIRateLimiter.cs | 6 +- .../ApplicationAIGenerationQueue.cs | 4 +- .../AIGenerationBackgroundJob.cs | 1 + ...ueApplicationAIPipelineOnProcessHandler.cs | 1 + .../Automation/Generation/README.md | 13 +++ 15 files changed, 156 insertions(+), 148 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md create mode 100644 applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-scoresheet.md create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/README.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/README.md index 8b8537d6b0..9baa5e28ea 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/README.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/README.md @@ -3,7 +3,9 @@ ## Architecture - [`index.md`](./index.md) - [`flow-map.md`](./flow-map.md) +- [`operation-pipeline.md`](./operation-pipeline.md) - [`prompt-map.md`](./prompt-map.md) +- [`configuration.md`](./configuration.md) - [`implementation-playbook.md`](./implementation-playbook.md) ## Operations @@ -12,3 +14,4 @@ - [`operations/application-scoring.md`](./operations/application-scoring.md) - [`operations/form-mapping.md`](./operations/form-mapping.md) - [`operations/form-worksheet.md`](./operations/form-worksheet.md) +- [`operations/form-scoresheet.md`](./operations/form-scoresheet.md) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md new file mode 100644 index 0000000000..49177164c9 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md @@ -0,0 +1,35 @@ +# Runtime Configuration + +AI behavior is split between host-owned database configuration and deployment +configuration. The database is the source of truth for which model, operation, and +prompt are used; appsettings holds deployment connectivity and operational settings. + +## Database configuration + +| Record | Owns | +| --- | --- | +| `AIModel` | Provider, deployment name (`Name`), active state, and model settings JSON | +| `AIOperation` | Prompt family (`Name`), selected model, execution mode, completion-token limit, and active state | +| `AIPrompt` | Versioned system/user templates, metadata, active state, and optional tenant ownership | + +Host seeders create the built-in models, operations, and global prompts. Operations +select models by ID; `AIModel.Name` is the provider deployment identifier. The runtime +rejects inactive or unsupported configuration rather than choosing a fallback model. + +## Prompt selection + +For a prompt family, host requests use the newest active global prompt. Tenant requests +use the newest active tenant prompt, then fall back to the newest active global prompt. +Operations do not store a prompt ID or version. + +## External configuration + +Provider endpoint, API key, and authenticated-user cooldown remain deployment configuration: + +```text +Azure:OpenAI:Endpoint +Azure:OpenAI:ApiKey +Azure:Generation:CooldownSeconds +``` + +Do not add operation defaults, profile maps, or prompt versions to appsettings. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/flow-map.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/flow-map.md index 42bd018836..0401643211 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/flow-map.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/flow-map.md @@ -1,14 +1,21 @@ # Flow Map -## Standard path -UI -> API app service -> queue -> background job -> AI runtime -> persisted result +```text +UI -> AIGenerationAppService -> IApplicationGenerationQueue +automation -------------------> IApplicationGenerationQueue +IApplicationGenerationQueue -> AIGenerationRequest + background job + -> operation executor + -> Unity.AI runtime + -> operation-specific persisted result +``` -## Operation families -- Application Analysis: submission -> analysis -- Attachment Summary: attachment ids -> summaries -- Application Scoring: application + scoresheet -> scoring -- Form Mapping: form version -> mapping -- Form Worksheet: form version -> worksheet +The app service authorizes and feature-gates UI requests. Automatic intake checks its +own tenant, form, and feature preconditions before entering the queue. The Grant Manager +queue resolves the active database operation, prevents duplicate active requests, +validates prerequisites, and enqueues work. The background job establishes tenant scope +and records request state; its executor owns operation-specific input and persistence. +The runtime resolves the prompt and model configuration, renders the request, calls the +provider, and parses the response. -## Build Rule -See [`implementation-playbook.md`](./implementation-playbook.md) for the canonical add-a-new-operation sequence. +The form mapping, worksheet, and scoresheet operations require an application form +version. See [operation pipeline](./operation-pipeline.md) for ownership rules. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/implementation-playbook.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/implementation-playbook.md index 9a9a50a207..7c11f6f702 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/implementation-playbook.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/implementation-playbook.md @@ -10,64 +10,28 @@ Use these existing operations as the canonical references: 3. `AttachmentSummary` 4. `FormMapping` 5. `FormWorksheet` +6. `FormScoresheet` ## Base Pattern -1. Define the prompt type. -2. Add the v2 prompt seed. -3. Add the operation seed. -4. Add the runtime contract method. -5. Add the runtime implementation. -6. Add the app service or queue entry. -7. Add the background job only if the result must be applied or persisted. -8. Add the UI button and status polling only if users trigger the operation from the web app. -9. Add tests for the prompt, runtime parsing, and job or service path. - -## Bare Minimum -For the first pass, only add what is required for a working operation: - -- prompt type -- prompt seed -- operation seed -- runtime method -- queue/app service entry -- job or direct apply path, if needed - -## Optional Pieces -Add these only when the operation needs them: - -- feature flag -- permissions -- permission definition provider entries -- menu entry -- UI button -- status polling -- refresh-after-complete behavior -- persistence/import/publish/assign behavior +1. Add the catalog definition, prompt family, model/operation seed, feature, and permissions. +2. Add supported prompt versions; do not assume a specific version number. +3. Add the runtime request/response contract and implementation. +4. Add an executor when Grant Manager must load input or persist a result. +5. Register the executor through the existing transient DI convention. +6. Expose a generate surface and UI only when users need one. +7. Add focused catalog, runtime, executor, and persistence tests. ## Rules -- Keep the prompt as the source of truth. -- Reuse the existing async generation pattern. +- Keep prompt content and operation/model configuration in the database. +- Reuse the shared generation pipeline. - Do not hardcode field buckets or response shapes in UI code. - Do not invent new plumbing if an existing operation already does the same job. -- Do not add tenant feature seeding. -- Do not add write-back UI behavior unless the operation already persists output. - -## Expected Flow -1. User clicks Generate. -2. UI disables the button and shows generating state, if the operation has UI. -3. API checks permission and feature flag, if the operation uses them. -4. API queues the generation request. -5. Background job loads the operation context. -6. Job builds the prompt payload from existing data. -7. AI runtime renders v2 prompts and logs input/output. -8. Job parses the AI response. -9. Job applies the result if needed. -10. Job stamps status and rate limit state. -11. UI polls status and refreshes after completion, if applicable. +- Keep operation-specific input and persistence in the executor. +- Do not add UI write-back behavior unless the operation persists its output. ## Validation -- Confirm the prompt version is v2. -- Confirm the operation exists in the AI operation seed. +- Confirm the operation exists in the catalog and host seed. +- Confirm every supported prompt version resolves correctly. - Confirm any required feature flag exists in the host feature definitions. - Confirm any required permission is wired in the permission definition provider. - Confirm the UI button uses the same generating/status flow as the other operations, if it is user-triggered. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md index bc108e3310..65386574ab 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md @@ -1,80 +1,36 @@ -# Unity.AI Index +# Unity.AI -## Domain.Shared -AI constants: -- feature flags -- permission names -- localization keys -- prompt type names +`Unity.AI` owns provider-neutral AI contracts, prompt/model/operation configuration, +runtime execution, and the generation API. Grant Manager owns the application data, +queue implementation, operation executors, and persistence of generated results. -## Application.Contracts -Public AI surface: -- app service interfaces -- queue interfaces -- DTOs -- permission definitions +## Boundaries -## Application -AI implementation: -- runtime -- prompt seeding -- generation app services -- validators -- prompt logging +| Area | Responsibility | +| --- | --- | +| `Domain.Shared` | Features, permissions, localization, and prompt family names | +| `Application.Contracts` | Runtime, generation, queue, and DTO contracts | +| `Application` | Prompt/model/operation seeds, provider runtime, API, and status reads | +| `Runtime/Execution` | Prompt rendering, provider calls, response parsing, and prompt logging | +| Grant Manager | Request locking, background jobs, operation executors, and result persistence | +| `Web` | Menus, generate actions, and status polling | -## Web -UI-facing AI bits: -- menus -- generation buttons -- status polling +## Operation catalog -## Files -### Application -- `Operations` - validators and helpers -- `Runtime/Execution` - rendering, parsing, logging, provider calls -- `Runtime/Prompts` - prompt types and template plumbing -- `DataSeed` - seeded prompt and operation data -- `Generation/AIGenerationAppService.cs` - generation API +`AIGenerationOperations` is the single catalog for operation type, prompt family, +feature, permissions, and form-version requirement. -### Application.Contracts -- `IAIService.cs` - runtime contract -- `Generation/IAIGenerationAppService.cs` - generation app service contract -- `Generation/*ResultDto.cs` - queued result DTOs -- `Operations/IAIGenerationPrerequisiteValidator.cs` - queue prerequisites -- `Automation/IApplicationAIGenerationQueue.cs` - queue contract -- `Permissions/*` - permissions - -### Domain.Shared -- `Features/AIFeatures.cs` - feature flags -- `Localization/AILocalizationKeys.cs` - messages -- `PromptTypes/AIPromptTypes.cs` - prompt family names - -### Web -- `Menus/AIMenuContributor.cs` - menu entries -- `Menus/AIMenus.cs` - menu item names - -## Access -| Operation | View | Generate | +| Operation | Type | Requires form version | | --- | --- | --- | -| Application Analysis | `ViewApplicationAnalysis` | `GenerateApplicationAnalysis` | -| Attachment Summary | `ViewAttachmentSummary` | `GenerateAttachmentSummaries` | -| Application Scoring | `ViewScoringResult` | `GenerateScoring` | -| Form Mapping | `ViewFormMapping` | `GenerateFormMapping` | -| Form Worksheet | `ViewFormWorksheet` | `GenerateFormWorksheet` | - -- Features: - - `Unity.AI.ApplicationAnalysis` - - `Unity.AI.AttachmentSummaries` - - `Unity.AI.Scoring` - - `Unity.AI.FormMapping` - - `Unity.AI.FormWorksheet` - -- Rule: - - Both permission and feature gate must allow generation. - -## AI Notes -- Prompt logging: logs rendered system/user prompts and provider output. -- Response parsing: parses provider output into stable app-facing results. -- Feature gating: disabled features fail early at the API boundary. -- Background jobs: mark failures, then re-throw. -- New operation playbook: see `implementation-playbook.md`. +| Application Analysis | `application-analysis` | No | +| Attachment Summary | `attachment-summary` | No | +| Application Scoring | `application-scoring` | No | +| Form Mapping | `form-mapping` | Yes | +| Form Worksheet | `form-worksheet` | Yes | +| Form Scoresheet | `form-scoresheet` | Yes | + +Generation requires both the catalogued feature and generate permission. Status reads +require the corresponding view permission. + +See [configuration](./configuration.md), [pipeline](./operation-pipeline.md), and the +[implementation playbook](./implementation-playbook.md). diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operation-pipeline.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operation-pipeline.md index e31433e284..bfd5d555ac 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operation-pipeline.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operation-pipeline.md @@ -1,6 +1,6 @@ # AI operation pipeline -AI generation uses a shared operation catalog and queued lifecycle. The catalog owns the operation key, seeded operation name, feature gate, permissions, and whether a form version is required. Submission enters `IAIGenerationAppService.SubmitAsync`, and the Grant Manager queue preserves the existing duplicate-request lock and operation-specific validation. +AI generation uses a shared operation catalog and queued lifecycle. The catalog owns the operation key, seeded operation name, feature gate, permissions, and whether a form version is required. UI submission enters `IAIGenerationAppService.SubmitAsync`; automatic intake checks its own preconditions and enters the Grant Manager queue directly. The queue preserves the duplicate-request lock and operation-specific validation. The generic background-job base owns tenant scope, structured logging, request state transitions, failure handling, and cooldown stamping. Operation-specific executors remain responsible for loading input, calling the AI contract, validating the response, and persisting the result. @@ -14,3 +14,6 @@ The generic background-job base owns tenant scope, structured logging, request s 6. Add focused catalog, lifecycle, executor, and persistence tests. Do not add another queue branch for shared lifecycle concerns. New operation behavior belongs in its executor; request locking, status transitions, tenant scope, logging, and cooldown behavior stay in the common pipeline. + +For Grant Manager queue and executor ownership, see the +[generation hand-off](../../../src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md). diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-scoresheet.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-scoresheet.md new file mode 100644 index 0000000000..dde122e67a --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-scoresheet.md @@ -0,0 +1,21 @@ +# Form Scoresheet + +## Goal + +Generate and publish a scoresheet definition for a form version. + +## Inputs + +- Form version and form context +- Existing linked scoresheet, when present +- Existing scoresheet sections and fields + +## Surface + +- `POST /api/app/ai/generation/form-scoresheet` +- `GET /api/app/ai/generation/status` + +## Result + +The executor validates the generated scoresheet JSON, creates or replaces the form's +scoresheet, publishes it, and links it to the application form. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md index c5b346acf7..8de9d3350c 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/prompt-map.md @@ -6,15 +6,18 @@ - `ApplicationScoring` - question scoring - `FormMapping` - CHEFS to Unity mapping - `FormWorksheet` - worksheet generation +- `FormScoresheet` - scoresheet generation ## Versions -- Built-in `v0`, `v1`, and `v2` prompt rows are defined and seeded by `AIPromptDataSeeder` -- Runtime selects the newest active prompt by family. +- Built-in prompt rows are defined and seeded by `AIPromptDataSeeder`. +- Families may have `v0`, `v1`, and `v2` rows; a new operation only needs the versions it supports. +- Without an explicit request version, runtime selects the newest active prompt by family. ## Tenant selection - `AIOperation.Name` is the prompt family; operations do not pin a prompt row or version. -- Host requests use the newest active global prompt in the family. -- Tenant requests use the newest active prompt owned by that tenant, falling back to the newest active global prompt. +- An explicit request version selects that active version, with the same tenant/global fallback. +- Otherwise, host requests use the newest active global prompt in the family. +- Otherwise, tenant requests use the newest active prompt owned by that tenant, falling back to the newest active global prompt. - To roll back a tenant or global prompt, deactivate the active version and leave the prior version active. - Tenant prompt rows are administrator-created; deployments seed only global prompts and operations. @@ -22,7 +25,7 @@ - Versioned prompts are the source of truth. - Prompt templates define the request shape. - Structured outputs should stay JSON-shaped. -- New versions should not silently change behavior. +- A new version should be additive and must not silently change an active prompt's behavior. ## Build Rule Use [`implementation-playbook.md`](./implementation-playbook.md) when adding a new prompt-backed operation. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs index 9fb8b7659d..90238c25b1 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs @@ -12,8 +12,7 @@ namespace Unity.AI.DataSeed; /// -/// Seeds the built-in AI prompts (application analysis, attachment summary, application scoring) into the host database. -/// Each prompt family is represented as versioned rows in AIPrompts. +/// Seeds host-owned, versioned built-in AI prompts. /// public class AIPromptDataSeeder( IRepository promptRepository, @@ -21,7 +20,10 @@ public class AIPromptDataSeeder( { public async Task SeedAsync(DataSeedContext context) { - if (context.TenantId != null) return; // host database only + if (context.TenantId != null) + { + return; + } using (currentTenant.Change(null)) { diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Generation/AIGenerationAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Generation/AIGenerationAppService.cs index 582c7d1cfb..c436d59f06 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Generation/AIGenerationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Generation/AIGenerationAppService.cs @@ -25,6 +25,7 @@ public class AIGenerationAppService( [HttpPost("submit")] public virtual async Task SubmitAsync(string operationType, AIGenerationSubmissionDto request) { + // All generation routes converge here so authorization, feature, and form-version rules stay consistent. var operation = AIGenerationOperations.Get(operationType); await featureGuard.EnsureEnabledAsync(operation.FeatureName, operation.DisabledLocalizationKey); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/RateLimit/AIRateLimiter.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/RateLimit/AIRateLimiter.cs index 6b6f1dc8a1..162efc619e 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/RateLimit/AIRateLimiter.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/RateLimit/AIRateLimiter.cs @@ -12,10 +12,8 @@ namespace Unity.AI.RateLimit; /// -/// Per-user cooldown for AI generate calls. KISS: a single cache entry per user -/// holds the cooldown end ticks; the cache TTL matches the cooldown so a missing -/// entry means the user can generate again. Anonymous/system callers are not -/// rate-limited (background event handlers also flow through the AI queue). +/// Per-user AI cooldown. Anonymous and system callers bypass it; activity providers +/// augment the state returned to authenticated users. /// public class AIRateLimiter( IDistributedCache cache, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/ApplicationAIGenerationQueue.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/ApplicationAIGenerationQueue.cs index fa2331a192..c29d5df821 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/ApplicationAIGenerationQueue.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/ApplicationAIGenerationQueue.cs @@ -138,6 +138,7 @@ private async Task EnsureRequestAndEnqueueAsync( var persistedOperation = await ResolveOperationAsync(operation); var requestLock = distributedLockProvider.CreateLock($"ai-generation:{tenantId}:{request.ApplicationId}:{persistedOperation.Id}"); + // The lock must cover the active-request check so each tenant/application/operation queues only once. using (await requestLock.AcquireAsync()) { var query = await generationRequestRepository.GetQueryableAsync(); @@ -159,8 +160,7 @@ private async Task EnsureRequestAndEnqueueAsync( await validateInput(); - // Single chokepoint for all AI generate flows (manual + auto). - // The limiter is a no-op for system/background callers without an authenticated user. + // Manual and automatic flows share this user-scoped limiter; system callers bypass it. await aiRateLimiter.EnsureAsync(currentUser.Id); var generationRequest = new AIGenerationRequest( diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/BackgroundJobs/AIGenerationBackgroundJob.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/BackgroundJobs/AIGenerationBackgroundJob.cs index 3252f0df84..04d0768fb8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/BackgroundJobs/AIGenerationBackgroundJob.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/BackgroundJobs/AIGenerationBackgroundJob.cs @@ -20,6 +20,7 @@ public sealed class AIGenerationBackgroundJob( { public override async Task ExecuteAsync(AIGenerationBackgroundJobArgs args) { + // The job owns tenant scope and request lifecycle; executors own AI input and result persistence. using var logScope = AIGenerationLogScope.Begin( logger, args.OperationType, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs index 2459de85c8..926ae0448d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/Handlers/QueueApplicationAIPipelineOnProcessHandler.cs @@ -27,6 +27,7 @@ public async Task HandleEventAsync(ApplicationProcessEvent eventData) return; } + // Automatic generation requires tenant and form opt-in plus at least one enabled intake feature. var automaticGenerationEnabled = await settingProvider.GetAsync(AISettings.AutomaticGenerationEnabled, defaultValue: false); if (!automaticGenerationEnabled) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md new file mode 100644 index 0000000000..f6994e5427 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md @@ -0,0 +1,13 @@ +# Grant Manager AI Generation + +This folder owns the Grant Manager side of AI generation. Unity.AI owns shared +contracts, runtime execution, and database configuration; Grant Manager owns request +queueing, background execution, application-specific input, and result persistence. + +`ApplicationAIGenerationQueue` serializes queueing per tenant, application, and +operation, then records one active request before enqueuing its job. +`AIGenerationBackgroundJob` owns tenant scope and request lifecycle state. +Operation executors own their input and persistence; they must not duplicate queue, +status, or cooldown behavior. + +See the shared [Unity.AI operation pipeline](../../../../../modules/Unity.AI/docs/operation-pipeline.md). From 6c01a8d23921009f5c901db9614f16a6def2865a Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Wed, 12 Aug 2026 08:24:38 -0700 Subject: [PATCH 042/121] AB#33983 correct AI operation contracts --- .../Unity.AI/docs/operations/application-analysis.md | 2 +- .../Unity.AI/docs/operations/application-scoring.md | 2 +- .../Unity.AI/docs/operations/attachment-summary.md | 2 +- .../modules/Unity.AI/docs/operations/form-mapping.md | 2 +- .../modules/Unity.AI/docs/operations/form-worksheet.md | 9 +++++---- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-analysis.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-analysis.md index 1b2bfdb194..d1c87ae7b1 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-analysis.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-analysis.md @@ -13,7 +13,7 @@ Generate an AI analysis of an application submission. - `GET /api/app/ai/generation/status` ## Contract -- Structured analysis output. Returns an immediate queued result via API app service, queue, background job, and AI runtime. +- Structured analysis output. The POST request enqueues generation and returns without the generated payload; clients use the shared status endpoint while the background executor persists the result. ## Notes - This is a reviewer-oriented summary and recommendation flow. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-scoring.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-scoring.md index a19e1f8bb1..0fa174c45c 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-scoring.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/application-scoring.md @@ -13,7 +13,7 @@ Generate scored answers for a submitted application against an assigned scoreshe - `GET /api/app/ai/generation/status` ## Contract -- Structured scoring output. Returns an immediate queued result via API app service, queue, background job, and AI runtime. +- Structured scoring output. The POST request enqueues generation and returns without the generated payload; clients use the shared status endpoint while the background executor persists the result. ## Notes - The prompt asks for answers only for the configured section or scoresheet context. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/attachment-summary.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/attachment-summary.md index 5735c3996f..27e7386037 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/attachment-summary.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/attachment-summary.md @@ -12,7 +12,7 @@ Generate summaries for selected application attachments. - `GET /api/app/ai/generation/status` ## Contract -- Structured attachment summary output. Returns an immediate queued result via API app service, queue, background job, and AI runtime. +- Structured attachment summary output. The POST request enqueues generation and returns without the generated payload; clients use the shared status endpoint while the background executor persists the result. ## Notes - Each attachment is processed as part of the generation request. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-mapping.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-mapping.md index 2881cefe9f..d7b66db156 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-mapping.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-mapping.md @@ -18,7 +18,7 @@ Generate recommended CHEFS-to-Unity field mapping for a form version. - `GET /api/app/application-form-version/{id}` ## Contract -- Structured mapping recommendation JSON output. Returns an immediate queued result via API app service, queue, background job, and AI runtime. +- Structured mapping recommendation JSON output. The POST request enqueues generation and returns without the generated payload; clients use the shared status endpoint while the background executor persists the result. ## Output Shape - Core field matches. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-worksheet.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-worksheet.md index 49d76dd9df..ffed292894 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-worksheet.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/operations/form-worksheet.md @@ -18,12 +18,13 @@ Generate a recommended worksheet definition for a form version. - `GET /api/app/ai/generation/status` ## Contract -- Structured Flex worksheet JSON output. Returns an immediate queued result via API app service, queue, background job, and AI runtime. +- Structured worksheet field-suggestion JSON. The POST request enqueues generation and returns without the generated payload; clients use the shared status endpoint while the background executor validates the suggestions and creates an unpublished worksheet for review. ## Output Shape -- Full worksheet definition JSON. -- Include only additional worksheet fields that the form needs beyond core Unity fields. -- Keep the result valid JSON and compatible with Flex import. +- A `fields` collection containing the suggested additional worksheet fields. +- Each suggestion supplies the field key, label, and supported custom-field type. +- The executor builds the worksheet and its `Suggested Fields` section from the validated suggestions. +- Keep the result valid JSON and include only fields that the form needs beyond core Unity fields. ## Notes - The AI output should stay valid JSON. From 8b0fe86be9a3ee3483a991283ea10c0d604268e9 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Wed, 12 Aug 2026 09:45:43 -0700 Subject: [PATCH 043/121] AB#33983 address documentation review --- .../modules/Unity.AI/docs/configuration.md | 2 +- .../Unity.GrantManager/modules/Unity.AI/docs/index.md | 5 +++-- .../GrantApplications/Automation/Generation/README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md index 49177164c9..aecbcd34e9 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/configuration.md @@ -1,6 +1,6 @@ # Runtime Configuration -AI behavior is split between host-owned database configuration and deployment +AI behavior is split between database-owned configuration and deployment configuration. The database is the source of truth for which model, operation, and prompt are used; appsettings holds deployment connectivity and operational settings. diff --git a/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md b/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md index 65386574ab..bc5832aab7 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md +++ b/applications/Unity.GrantManager/modules/Unity.AI/docs/index.md @@ -29,8 +29,9 @@ feature, permissions, and form-version requirement. | Form Worksheet | `form-worksheet` | Yes | | Form Scoresheet | `form-scoresheet` | Yes | -Generation requires both the catalogued feature and generate permission. Status reads -require the corresponding view permission. +User-triggered generation requires both the catalogued feature and generate permission. +Automatic intake enforces its tenant, form, feature, and generation prerequisites without +user permission authorization. Status reads require the corresponding view permission. See [configuration](./configuration.md), [pipeline](./operation-pipeline.md), and the [implementation playbook](./implementation-playbook.md). diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md index f6994e5427..0dce7162d8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Generation/README.md @@ -4,7 +4,7 @@ This folder owns the Grant Manager side of AI generation. Unity.AI owns shared contracts, runtime execution, and database configuration; Grant Manager owns request queueing, background execution, application-specific input, and result persistence. -`ApplicationAIGenerationQueue` serializes queueing per tenant, application, and +`ApplicationGenerationQueue` serializes queueing per tenant, application, and operation, then records one active request before enqueuing its job. `AIGenerationBackgroundJob` owns tenant scope and request lifecycle state. Operation executors own their input and persistence; they must not duplicate queue, From 84440802713879e772a06abf8399c3ac1dbe59fe Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Wed, 12 Aug 2026 13:20:15 -0700 Subject: [PATCH 044/121] bugfix/AB#33870-DBMigrationFix --- .../Unity.GrantManager.DbMigrator/README.md | 6 +++++ .../appsettings.json | 3 +++ ...ameworkCoreGrantManagerDbSchemaMigrator.cs | 23 +++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md index 9aa057d9fd..bc1982b52d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md @@ -51,4 +51,10 @@ Once you've configured your connection strings via `appsettings.secrets.json` (o dotnet run ``` +Migration history flattening is disabled by default. To reconcile databases that still +contain migration ids from the removed migration set, run the migrator once with +`Database__FlattenMigrations=true` (or set `Database:FlattenMigrations` to `true` in +`appsettings.secrets.json`). Do not leave this enabled for normal migration runs: it +deletes migration history rows that were added after the flattened `Initial` migration. + Or run it from Visual Studio by setting `Unity.GrantManager.DbMigrator` as the startup project. diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index 325101ebd6..1e3abd26c8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -4,6 +4,9 @@ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;", "Onboarding": "Host=localhost;port=5432;Database=Onboarding;Username=postgres;" }, + "Database": { + "FlattenMigrations": false + }, "StringEncryption": { "DefaultPassPhrase": "g2IuZx7PwXDvCmlW" }, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs index 0b68fd752b..290e4d9a6b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs @@ -20,6 +20,7 @@ namespace Unity.GrantManager.EntityFrameworkCore; public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( IServiceProvider serviceProvider, IStringEncryptionService encryptionService, + IConfiguration configuration, ILogger logger) : IGrantManagerDbSchemaMigrator, ITransientDependency { @@ -30,13 +31,10 @@ public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( * "Initial" and would make Database.MigrateAsync() below try to re-run Initial's * CreateTable operations against a schema that already has them. * - * ReconcileMigrationHistoryAsync resets history to just the Initial row *before* - * MigrateAsync() is called, so EF sees it as already applied and skips it. Brand - * new databases (including newly provisioned tenants) have an empty or nonexistent - * history table at this point, so the reconciliation is a no-op and MigrateAsync() - * runs Initial for real to build the schema. Safe to run unconditionally on every - * migrator invocation, forever - after the first run per database, history only - * ever contains the Initial row so the guard clause never fires again. + * ReconcileMigrationHistoryAsync resets history to just the Initial row before + * MigrateAsync() is called, so EF sees it as already applied and skips it. This is + * an explicit one-time operation because running it during normal migration startup + * would also remove legitimate migrations added after the flattening. */ private const string HostInitialMigrationId = "20260722193713_Initial"; private const string TenantInitialMigrationId = "20260721203242_Initial"; @@ -44,6 +42,7 @@ public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( private readonly IServiceProvider _serviceProvider = serviceProvider; private readonly IStringEncryptionService _encryptionService = encryptionService; + private readonly bool _flattenMigrations = configuration.GetValue("Database:FlattenMigrations"); private readonly ILogger _logger = logger; public async Task MigrateAsync(Tenant? tenant) @@ -104,7 +103,10 @@ public async Task MigrateAsync(Tenant? tenant) await tenantDb.ExecuteSqlRawAsync( tenantDb.GetService().GetCreateIfNotExistsScript()); - await ReconcileMigrationHistoryAsync(tenantDb, TenantInitialMigrationId); + if (_flattenMigrations) + { + await ReconcileMigrationHistoryAsync(tenantDb, TenantInitialMigrationId); + } // Run migrations as admin against the tenant database await MigrateAndLogAsync(tenantDb, $"tenant:{tenant.Name}"); @@ -162,7 +164,10 @@ the correct one. */ await hostDb.ExecuteSqlRawAsync( hostDb.GetService().GetCreateIfNotExistsScript()); - await ReconcileMigrationHistoryAsync(hostDb, HostInitialMigrationId); + if (_flattenMigrations) + { + await ReconcileMigrationHistoryAsync(hostDb, HostInitialMigrationId); + } await MigrateAndLogAsync(hostDb, "host"); } From 10718068923b30db3abffc25a3e4256668790e7a Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Wed, 12 Aug 2026 15:09:13 -0700 Subject: [PATCH 045/121] bugfix/AB#33850-CSS-NotifcationsIssueWithModal --- .../Views/Shared/Components/Notifications/Default.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index edd18555fb..0d17c84bd3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -619,6 +619,11 @@ console.warn('init() called again but already initialized, returning early'); return; } + + const modalEl = document.getElementById('notificationModal'); + if (modalEl && modalEl.parentElement !== document.body) { + document.body.appendChild(modalEl); + } console.debug('init() starting'); @@ -633,7 +638,6 @@ configureSubmitOnlyValidation(); - const modalEl = document.getElementById('notificationModal'); if (modalEl) { // Always reset validation when modal is fully closed modalEl.addEventListener('hidden.bs.modal', () => resetValidationState()); From 89923b6dc2502c4cceb4a2121deac895b154514d Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:39:48 -0700 Subject: [PATCH 046/121] [AB33234] Add renewal link support to ApplicationForm and DTOs --- .../ProfileData/ExternalLinkDto.cs | 22 ++++++++++++++ .../ProfileData/SubmissionInfoItemDto.cs | 22 +++++++------- .../ApplicantProfile/ExternalLink.cs | 29 +++++++++++++++++++ .../Applications/ApplicationForm.cs | 5 ++++ 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ExternalLinkDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs 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..1a21d149b6 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicantProfile/ProfileData/ExternalLinkDto.cs @@ -0,0 +1,22 @@ +namespace Unity.GrantManager.ApplicantProfile.ProfileData; + +/// +/// Represents a link to be used within the Applicant Portal, including the URL, title, and description. +/// +public class ExternalLinkDto +{ + /// + /// Gets or sets the URL of the external link. + /// + public string Uri { get; set; } = string.Empty; + + /// + /// Gets or sets the title of the external link. + /// + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets the description of the external link. + /// + public string Description { get; set; } = string.Empty; +} 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..815a618d75 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,15 @@ using System; -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; } } 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..0c5c29b668 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -0,0 +1,29 @@ +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 URL, title, and description. +/// +[ComplexType] +public class ExternalLink +{ + /// + /// Gets or sets the URL of the external link. + /// + [MaxLength(2048)] + public required string Uri { get; set; } + + /// + /// Gets or sets the title of the external link. + /// + [MaxLength(255)] + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets the description of the external link. + /// + [MaxLength(512)] + public string Description { get; set; } = string.Empty; +} \ No newline at end of file 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..5d32c560a3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -1,6 +1,7 @@ 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.Domain.Entities.Auditing; @@ -32,6 +33,10 @@ public class ApplicationForm : FullAuditedAggregateRoot, IMultiTenant public FormHierarchyType? FormHierarchy { get; set; } public Guid? ParentFormId { get; set; } public bool IsDirectApproval { get; set; } = false; + + public bool PublishRenewalLink { get; set; } + public ExternalLink? RenewalLink { get; set; } + public bool AutomaticallyGenerateAIAnalysis { get; set; } = false; public bool ManuallyInitiateAIAnalysis { get; set; } = false; [MaxLength(100)] From f6cb7821e2e50cb27d5ceb8d67a2eb72c37ebb33 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:56:08 -0700 Subject: [PATCH 047/121] [AB#33234] Add renewal link DbMigration --- ...0812224928_AB33234_RenewalLink.Designer.cs | 5299 +++++++++++++++++ .../20260812224928_AB33234_RenewalLink.cs | 62 + .../GrantTenantDbContextModelSnapshot.cs | 22 + 3 files changed, 5383 insertions(+) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs new file mode 100644 index 0000000000..18b7991bb3 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs @@ -0,0 +1,5299 @@ +// +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("20260812224928_AB33234_RenewalLink")] + partial class AB33234_RenewalLink + { + /// + 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("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("PublishRenewalLink") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.ComplexProperty(typeof(Dictionary), "RenewalLink", "Unity.GrantManager.Applications.ApplicationForm.RenewalLink#ExternalLink", b1 => + { + b1.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("Uri") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + }); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .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("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs new file mode 100644 index 0000000000..24e60223cb --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33234_RenewalLink : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PublishRenewalLink", + table: "ApplicationForms", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "RenewalLink_Description", + table: "ApplicationForms", + type: "character varying(512)", + maxLength: 512, + nullable: true); + + migrationBuilder.AddColumn( + name: "RenewalLink_Title", + table: "ApplicationForms", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "RenewalLink_Uri", + table: "ApplicationForms", + type: "character varying(2048)", + maxLength: 2048, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PublishRenewalLink", + table: "ApplicationForms"); + + migrationBuilder.DropColumn( + name: "RenewalLink_Description", + table: "ApplicationForms"); + + migrationBuilder.DropColumn( + name: "RenewalLink_Title", + table: "ApplicationForms"); + + migrationBuilder.DropColumn( + name: "RenewalLink_Uri", + table: "ApplicationForms"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 1d8fd17a8c..4c3afe0e44 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1801,6 +1802,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PreventPayment") .HasColumnType("boolean"); + b.Property("PublishRenewalLink") + .HasColumnType("boolean"); + b.Property("ScoresheetId") .HasColumnType("uuid"); @@ -1814,6 +1818,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); + b.ComplexProperty(typeof(Dictionary), "RenewalLink", "Unity.GrantManager.Applications.ApplicationForm.RenewalLink#ExternalLink", b1 => + { + b1.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("Uri") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + }); + b.HasKey("Id"); b.HasIndex("IntakeId"); From 6c7ef82b616f51cab35ca5287b594920cdcc88f2 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:24:54 -0700 Subject: [PATCH 048/121] Revert "[AB#33234] Add renewal link DbMigration" This reverts commit f6cb7821e2e50cb27d5ceb8d67a2eb72c37ebb33. --- ...0812224928_AB33234_RenewalLink.Designer.cs | 5299 ----------------- .../20260812224928_AB33234_RenewalLink.cs | 62 - .../GrantTenantDbContextModelSnapshot.cs | 22 - 3 files changed, 5383 deletions(-) delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs deleted file mode 100644 index 18b7991bb3..0000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.Designer.cs +++ /dev/null @@ -1,5299 +0,0 @@ -// -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("20260812224928_AB33234_RenewalLink")] - partial class AB33234_RenewalLink - { - /// - 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("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IdentityEmail") - .HasColumnType("text"); - - b.Property("IdentityName") - .HasColumnType("text"); - - b.Property("IdentityProvider") - .HasColumnType("text"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsConfirmed") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OidcSubUser") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("Phone2") - .HasColumnType("text"); - - b.Property("Phone2Extension") - .HasColumnType("text"); - - b.Property("PhoneExtension") - .HasColumnType("text"); - - b.Property("RoleForApplicant") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationId") - .IsUnique(); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicantAgents", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("ApplicantAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AIAnalysis") - .HasColumnType("text"); - - b.Property("AIScoresheetAnswers") - .HasColumnType("jsonb"); - - b.Property("Acquisition") - .HasColumnType("text"); - - b.Property("ApplicantElectoralDistrict") - .HasColumnType("text"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("ApplicationStatusId") - .HasColumnType("uuid"); - - b.Property("ApprovedAmount") - .HasColumnType("numeric"); - - b.Property("AssessmentResultDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AssessmentResultStatus") - .HasColumnType("text"); - - b.Property("AssessmentStartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Community") - .HasColumnType("text"); - - b.Property("CommunityPopulation") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContractExecutionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ContractNumber") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeclineRational") - .HasColumnType("text"); - - b.Property("DefaultSiteId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("DueDate") - .HasColumnType("timestamp without time zone"); - - b.Property("DueDiligenceStatus") - .HasColumnType("text"); - - b.Property("EconomicRegion") - .HasColumnType("text"); - - b.Property("ElectoralDistrict") - .HasColumnType("text"); - - b.Property("ExternalStatusVisibility") - .HasColumnType("boolean"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FinalDecisionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Forestry") - .HasColumnType("text"); - - b.Property("ForestryFocus") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LikelihoodOfFunding") - .HasColumnType("text"); - - b.Property("Notes") - .HasColumnType("text"); - - b.Property("NotificationDate") - .HasColumnType("timestamp without time zone"); - - b.Property("OwnerId") - .HasColumnType("uuid"); - - b.Property("Payload") - .HasColumnType("jsonb"); - - b.Property("PercentageTotalProjectBudget") - .HasColumnType("double precision"); - - b.Property("Place") - .HasColumnType("text"); - - b.Property("ProjectEndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ProjectFundingTotal") - .HasColumnType("numeric"); - - b.Property("ProjectName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("ProjectStartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ProjectSummary") - .HasColumnType("text"); - - b.Property("ProposalDate") - .HasColumnType("timestamp without time zone"); - - b.Property("RecommendedAmount") - .HasColumnType("numeric"); - - b.Property("ReferenceNo") - .IsRequired() - .HasColumnType("text"); - - b.Property("RegionalDistrict") - .HasColumnType("text"); - - b.Property("RequestedAmount") - .HasColumnType("numeric"); - - b.Property("RiskRanking") - .HasColumnType("text"); - - b.Property("SigningAuthorityBusinessPhone") - .HasColumnType("text"); - - b.Property("SigningAuthorityCellPhone") - .HasColumnType("text"); - - b.Property("SigningAuthorityEmail") - .HasColumnType("text"); - - b.Property("SigningAuthorityFullName") - .HasColumnType("text"); - - b.Property("SigningAuthorityTitle") - .HasColumnType("text"); - - b.Property("SubStatus") - .HasColumnType("text"); - - b.Property("SubmissionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TotalProjectBudget") - .HasColumnType("numeric"); - - b.Property("TotalScore") - .HasColumnType("integer"); - - b.Property("UnityApplicationId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationFormId"); - - b.HasIndex("ApplicationStatusId"); - - b.HasIndex("OwnerId"); - - b.HasIndex("ReferenceNo"); - - b.HasIndex("TenantId", "SubmissionDate") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("Applications", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("AssigneeId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Duty") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("AssigneeId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationAssignments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AISummary") - .HasColumnType("text"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChefsFileId") - .HasColumnType("text"); - - b.Property("ChefsSubmissionId") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationChefsFileAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContactEmail") - .HasColumnType("text"); - - b.Property("ContactFullName") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactMobilePhone") - .HasColumnType("text"); - - b.Property("ContactTitle") - .HasColumnType("text"); - - b.Property("ContactType") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactWorkPhone") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationContact", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccountCodingId") - .HasColumnType("uuid"); - - b.Property("ApiKey") - .HasColumnType("text"); - - b.Property("ApplicationFormDescription") - .HasColumnType("text"); - - b.Property("ApplicationFormName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("AttemptedConnectionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AutomaticallyGenerateAIAnalysis") - .HasColumnType("boolean"); - - b.Property("AvailableChefsFields") - .HasColumnType("text"); - - b.Property("Category") - .HasColumnType("text"); - - b.Property("ChefsApplicationFormGuid") - .HasColumnType("text"); - - b.Property("ChefsCriteriaFormGuid") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ConnectionHttpStatus") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DefaultPaymentGroup") - .HasColumnType("integer"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ElectoralDistrictAddressType") - .HasColumnType("integer"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormHierarchy") - .HasColumnType("integer"); - - b.Property("IntakeId") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("IsDirectApproval") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("ManuallyInitiateAIAnalysis") - .HasColumnType("boolean"); - - b.Property("ParentFormId") - .HasColumnType("uuid"); - - b.Property("Payable") - .HasColumnType("boolean"); - - b.Property("PaymentApprovalThreshold") - .HasColumnType("numeric"); - - b.Property("Prefix") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PreventPayment") - .HasColumnType("boolean"); - - b.Property("PublishRenewalLink") - .HasColumnType("boolean"); - - b.Property("ScoresheetId") - .HasColumnType("uuid"); - - b.Property("SuffixType") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Version") - .HasColumnType("integer"); - - b.ComplexProperty(typeof(Dictionary), "RenewalLink", "Unity.GrantManager.Applications.ApplicationForm.RenewalLink#ExternalLink", b1 => - { - b1.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b1.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("Uri") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - }); - - b.HasKey("Id"); - - b.HasIndex("IntakeId"); - - b.HasIndex("ParentFormId"); - - b.HasIndex("TenantId", "IsDeleted") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("ApplicationForms", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormVersionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChefsSubmissionGuid") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormVersionId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OidcSub") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("Submission") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationFormId"); - - b.ToTable("ApplicationFormSubmissions", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("AvailableChefsFields") - .HasColumnType("text"); - - b.Property("ChefsApplicationFormGuid") - .HasColumnType("text"); - - b.Property("ChefsFormVersionGuid") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormSchema") - .HasColumnType("jsonb"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Published") - .HasColumnType("boolean"); - - b.Property("ReportColumns") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportKeys") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportViewName") - .IsRequired() - .HasColumnType("text"); - - b.Property("SubmissionHeaderMapping") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationFormId"); - - b.ToTable("ApplicationFormVersion", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LinkType") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("Related"); - - b.Property("LinkedApplicationId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationLinks", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExternalStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("InternalStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("NotifiedStatus") - .HasColumnType("text"); - - b.Property("StatusCode") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("StatusCode") - .IsUnique(); - - b.ToTable("ApplicationStatuses", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TagId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("TagId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationTags", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("AssessmentId"); - - b.ToTable("AssessmentAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("AuditDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AuditNote") - .HasColumnType("text"); - - b.Property("AuditStatus") - .HasColumnType("text"); - - b.Property("AuditTrackingNumber") - .HasColumnType("text"); - - b.Property("AuditorName") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("AuditHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApprovedAmount") - .HasColumnType("numeric"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FundingNotes") - .HasColumnType("text"); - - b.Property("FundingYear") - .HasColumnType("text"); - - b.Property("GrantCategory") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OneTimeConsideration") - .HasColumnType("numeric"); - - b.Property("PaidDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ReconsiderationAmount") - .HasColumnType("numeric"); - - b.Property("RenewedFunding") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TotalGrantAmount") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("FundingHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IssueDescription") - .HasColumnType("text"); - - b.Property("IssueHeading") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("ResolutionNote") - .HasColumnType("text"); - - b.Property("Resolved") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Year") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("IssueTrackings", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FiscalYear") - .HasColumnType("text"); - - b.Property("IncompleteReport") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("Outstanding") - .HasColumnType("boolean"); - - b.Property("ReportDate") - .HasColumnType("timestamp without time zone"); - - b.Property("SignedOff") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("ReportsHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ApprovalRecommended") - .HasColumnType("boolean"); - - b.Property("AssessorId") - .HasColumnType("uuid"); - - b.Property("CleanGrowth") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("EconomicImpact") - .HasColumnType("integer"); - - b.Property("EndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FinancialAnalysis") - .HasColumnType("integer"); - - b.Property("InclusiveGrowth") - .HasColumnType("integer"); - - b.Property("IsAiAssessment") - .HasColumnType("boolean"); - - b.Property("IsComplete") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("AssessorId"); - - b.ToTable("Assessments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("CommenterId"); - - b.ToTable("ApplicantComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("CommenterId"); - - b.ToTable("ApplicationComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("AssessmentId"); - - b.HasIndex("CommenterId"); - - b.ToTable("AssessmentComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("HomePhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MobilePhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("WorkPhoneExtension") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WorkPhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.ToTable("Contacts", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContactId") - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsPrimary") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("RelatedEntityId") - .HasColumnType("uuid"); - - b.Property("RelatedEntityType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Role") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("RelatedEntityType", "RelatedEntityId"); - - b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); - - b.ToTable("ContactLinks", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Tags", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Badge") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FullName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OidcDisplayName") - .IsRequired() - .HasColumnType("text"); - - b.Property("OidcSub") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("OidcSub"); - - b.HasIndex("TenantId"); - - b.ToTable("Persons", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Budget") - .HasColumnType("double precision"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IntakeName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("StartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Intakes", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationStatus") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("ApplicationStatusId") - .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("DateField") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EmailTemplateId") - .HasColumnType("uuid"); - - b.Property("EventType") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormId") - .HasColumnType("uuid"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("RecipientCategory") - .HasColumnType("text"); - - b.Property("RecipientIdentifier") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TriggerDetail") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("TriggerType") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Id"); - - b.HasIndex("TenantId"); - - b.ToTable("ScheduledNotifications", "Notifications"); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone"); - - b.Property("CreatorId") - .HasColumnType("uuid"); - - b.Property("DateField") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NotificationSentDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ScheduledNotificationId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("CreationTime"); - - b.HasIndex("ScheduledNotificationId"); - - b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") - .IsUnique(); - - b.ToTable("ScheduledNotificationTracking", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Type") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EmailGroups", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("GroupId"); - - b.ToTable("EmailGroupUsers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("BCC") - .IsRequired() - .HasColumnType("text"); - - b.Property("Body") - .IsRequired() - .HasColumnType("text"); - - b.Property("BodyType") - .IsRequired() - .HasColumnType("text"); - - b.Property("CC") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChesHttpStatusCode") - .HasColumnType("text"); - - b.Property("ChesMsgId") - .HasColumnType("uuid"); - - b.Property("ChesResponse") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChesStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("EmailType") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FromAddress") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestIds") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .IsRequired() - .HasColumnType("text"); - - b.Property("Recipient") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("RetryAttempts") - .HasColumnType("integer"); - - b.Property("ScheduledNotificationId") - .HasColumnType("uuid"); - - b.Property("SendOnDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("SentDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("Status") - .IsRequired() - .HasColumnType("text"); - - b.Property("Subject") - .IsRequired() - .HasColumnType("text"); - - b.Property("Tag") - .IsRequired() - .HasColumnType("text"); - - b.Property("TemplateName") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("ToAddress") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EmailLogs", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContentType") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("EmailLogId") - .HasColumnType("uuid"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("FileSize") - .HasColumnType("bigint"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OriginTemplateId") - .HasColumnType("uuid"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TemplateId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("EmailLogId"); - - b.HasIndex("S3ObjectKey"); - - b.HasIndex("TemplateId"); - - b.ToTable("EmailLogAttachments", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BodyHTML") - .IsRequired() - .HasColumnType("text"); - - b.Property("BodyText") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("RecipientCategory") - .HasColumnType("text"); - - b.Property("RecipientIdentifier") - .HasColumnType("text"); - - b.Property("SendFrom") - .IsRequired() - .HasColumnType("text"); - - b.Property("Subject") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("EmailTemplates", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastName") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Subscribers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("SubscriptionGroups", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("SubscriberId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("GroupId"); - - b.HasIndex("SubscriberId"); - - b.ToTable("SubscriptionGroupSubscribers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MapTo") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Token") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("TemplateVariables", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("InternalName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Triggers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("SubscriptionGroupId") - .HasColumnType("uuid"); - - b.Property("TemplateId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TriggerId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("SubscriptionGroupId"); - - b.HasIndex("TemplateId"); - - b.HasIndex("TriggerId"); - - b.ToTable("TriggerSubscriptions", "Notifications"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Description") - .HasMaxLength(35) - .HasColumnType("character varying(35)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MinistryClient") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("Responsibility") - .IsRequired() - .HasColumnType("text"); - - b.Property("ServiceLine") - .IsRequired() - .HasColumnType("text"); - - b.Property("Stob") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("AccountCodings", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DefaultAccountCodingId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentIdPrefix") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("PaymentConfigurations", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DecisionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("DecisionUserId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestId") - .HasColumnType("uuid"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("PaymentRequestId"); - - b.ToTable("ExpenseApprovals", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccountCodingId") - .HasColumnType("uuid"); - - b.Property("Amount") - .HasColumnType("numeric"); - - b.Property("BatchName") - .IsRequired() - .HasColumnType("text"); - - b.Property("BatchNumber") - .HasColumnType("numeric"); - - b.Property("CancelledBy") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("CancelledBy"); - - b.Property("CancelledById") - .HasColumnType("uuid") - .HasColumnName("CancelledById"); - - b.Property("CancelledOn") - .HasColumnType("timestamp without time zone") - .HasColumnName("CancelledOn"); - - b.Property("CasHttpStatusCode") - .HasColumnType("integer"); - - b.Property("CasResponse") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContractNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("CorrelationProvider") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FsbApNotified") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("FsbNotificationEmailLogId") - .HasColumnType("uuid"); - - b.Property("FsbNotificationSentDate") - .HasColumnType("timestamp without time zone"); - - b.Property("InvoiceNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("InvoiceStatus") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("IsRecon") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("PayeeName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PaymentDate") - .HasColumnType("text"); - - b.Property("PaymentNumber") - .HasColumnType("text"); - - b.Property("PaymentStatus") - .HasColumnType("text"); - - b.Property("ReferenceNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("RequesterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("SiteId") - .HasColumnType("uuid"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmissionConfirmationCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("SupplierName") - .HasColumnType("text"); - - b.Property("SupplierNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("AccountCodingId"); - - b.HasIndex("CorrelationId"); - - b.HasIndex("CreationTime"); - - b.HasIndex("FsbNotificationEmailLogId"); - - b.HasIndex("ReferenceNumber") - .IsUnique(); - - b.HasIndex("SiteId"); - - b.HasIndex("Status"); - - b.HasIndex("TenantId", "CreationTime") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("PaymentRequests", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestId") - .HasColumnType("uuid"); - - b.Property("TagId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("PaymentRequestId"); - - b.HasIndex("TagId"); - - b.ToTable("PaymentTags", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Threshold") - .HasColumnType("numeric"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.ToTable("PaymentThresholds", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AddressLine1") - .HasColumnType("text"); - - b.Property("AddressLine2") - .HasColumnType("text"); - - b.Property("AddressLine3") - .HasColumnType("text"); - - b.Property("BankAccount") - .HasColumnType("text"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EFTAdvicePref") - .HasColumnType("text"); - - b.Property("EmailAddress") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastUpdatedInCas") - .HasColumnType("timestamp without time zone"); - - b.Property("MarkDeletedInUse") - .HasColumnType("boolean"); - - b.Property("Number") - .IsRequired() - .HasColumnType("text"); - - b.Property("PaymentGroup") - .HasColumnType("integer"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("SiteProtected") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("text"); - - b.Property("SupplierId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("SupplierId"); - - b.ToTable("Sites", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BusinessNumber") - .HasColumnType("text"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastUpdatedInCAS") - .HasColumnType("timestamp without time zone"); - - b.Property("MailingAddress") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Number") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("SIN") - .HasColumnType("text"); - - b.Property("StandardIndustryClassification") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("text"); - - b.Property("Subcategory") - .HasColumnType("text"); - - b.Property("SupplierProtected") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Suppliers", "Payments"); - }); - - modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("CorrelationProvider") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Mapping") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("RoleStatus") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("ViewName") - .IsRequired() - .HasColumnType("text"); - - b.Property("ViewStatus") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("ReportColumnsMaps", "Reporting"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") - .WithMany("Instances") - .HasForeignKey("ScoresheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scoresheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") - .WithMany("Answers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) - .WithMany("Answers") - .HasForeignKey("ScoresheetInstanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") - .WithMany("Fields") - .HasForeignKey("SectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Section"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") - .WithMany("Sections") - .HasForeignKey("ScoresheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scoresheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => - { - b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) - .WithMany("Values") - .HasForeignKey("WorksheetInstanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") - .WithMany("Links") - .HasForeignKey("WorksheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Worksheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") - .WithMany("Fields") - .HasForeignKey("SectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Section"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") - .WithMany("Sections") - .HasForeignKey("WorksheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Worksheet"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") - .WithMany("ApplicantAddresses") - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicantAddresses") - .HasForeignKey("ApplicationId"); - - b.Navigation("Applicant"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithOne("ApplicantAgent") - .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") - .WithMany("Applications") - .HasForeignKey("ApplicationStatusId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("Applicant"); - - b.Navigation("ApplicationForm"); - - b.Navigation("ApplicationStatus"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicationAssignments") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") - .WithMany() - .HasForeignKey("AssigneeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Application"); - - b.Navigation("Assignee"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => - { - b.HasOne("Unity.GrantManager.Intakes.Intake", null) - .WithMany() - .HasForeignKey("IntakeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ParentFormId") - .OnDelete(DeleteBehavior.NoAction); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => - { - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany("ApplicationLinks") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicationTags") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Application"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => - { - b.HasOne("Unity.GrantManager.Assessments.Assessment", null) - .WithMany() - .HasForeignKey("AssessmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("Assessments") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("AssessorId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => - { - b.HasOne("Unity.GrantManager.Assessments.Assessment", null) - .WithMany() - .HasForeignKey("AssessmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => - { - b.HasOne("Unity.GrantManager.Contacts.Contact", null) - .WithMany() - .HasForeignKey("ContactId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) - .WithMany() - .HasForeignKey("ScheduledNotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => - { - b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => - { - b.HasOne("Unity.Notifications.Emails.EmailLog", null) - .WithMany() - .HasForeignKey("EmailLogId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) - .WithMany() - .HasForeignKey("TemplateId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => - { - b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") - .WithMany() - .HasForeignKey("GroupId"); - - b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") - .WithMany() - .HasForeignKey("SubscriberId"); - - b.Navigation("Subscriber"); - - b.Navigation("SubscriptionGroup"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => - { - b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") - .WithMany() - .HasForeignKey("SubscriptionGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") - .WithMany() - .HasForeignKey("TemplateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") - .WithMany() - .HasForeignKey("TriggerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EmailTemplate"); - - b.Navigation("SubscriptionGroup"); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => - { - b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") - .WithMany("ExpenseApprovals") - .HasForeignKey("PaymentRequestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("PaymentRequest"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") - .WithMany() - .HasForeignKey("AccountCodingId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") - .WithMany() - .HasForeignKey("SiteId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("AccountCoding"); - - b.Navigation("Site"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => - { - b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) - .WithMany("PaymentTags") - .HasForeignKey("PaymentRequestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => - { - b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") - .WithMany("Sites") - .HasForeignKey("SupplierId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Supplier"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => - { - b.Navigation("Answers"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => - { - b.Navigation("Answers"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => - { - b.Navigation("Instances"); - - b.Navigation("Sections"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => - { - b.Navigation("Fields"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => - { - b.Navigation("Values"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => - { - b.Navigation("Links"); - - b.Navigation("Sections"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => - { - b.Navigation("Fields"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => - { - b.Navigation("ApplicantAddresses"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.Navigation("ApplicantAddresses"); - - b.Navigation("ApplicantAgent"); - - b.Navigation("ApplicationAssignments"); - - b.Navigation("ApplicationLinks"); - - b.Navigation("ApplicationTags"); - - b.Navigation("Assessments"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => - { - b.Navigation("Applications"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.Navigation("ExpenseApprovals"); - - b.Navigation("PaymentTags"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => - { - b.Navigation("Sites"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs deleted file mode 100644 index 24e60223cb..0000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260812224928_AB33234_RenewalLink.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Unity.GrantManager.Migrations.TenantMigrations -{ - /// - public partial class AB33234_RenewalLink : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PublishRenewalLink", - table: "ApplicationForms", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "RenewalLink_Description", - table: "ApplicationForms", - type: "character varying(512)", - maxLength: 512, - nullable: true); - - migrationBuilder.AddColumn( - name: "RenewalLink_Title", - table: "ApplicationForms", - type: "character varying(255)", - maxLength: 255, - nullable: true); - - migrationBuilder.AddColumn( - name: "RenewalLink_Uri", - table: "ApplicationForms", - type: "character varying(2048)", - maxLength: 2048, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PublishRenewalLink", - table: "ApplicationForms"); - - migrationBuilder.DropColumn( - name: "RenewalLink_Description", - table: "ApplicationForms"); - - migrationBuilder.DropColumn( - name: "RenewalLink_Title", - table: "ApplicationForms"); - - migrationBuilder.DropColumn( - name: "RenewalLink_Uri", - table: "ApplicationForms"); - } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 0450674d8f..49f2570401 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1802,9 +1801,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PreventPayment") .HasColumnType("boolean"); - b.Property("PublishRenewalLink") - .HasColumnType("boolean"); - b.Property("ScoresheetId") .HasColumnType("uuid"); @@ -1818,24 +1814,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); - b.ComplexProperty(typeof(Dictionary), "RenewalLink", "Unity.GrantManager.Applications.ApplicationForm.RenewalLink#ExternalLink", b1 => - { - b1.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b1.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("Uri") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - }); - b.HasKey("Id"); b.HasIndex("IntakeId"); From b09d4b44bcd7b7cd20c859eb8ebfc506d70b03f3 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:04:12 -0700 Subject: [PATCH 049/121] [AB#33234] Add ExternalLinks collection to DbMigration --- .../ApplicantProfile/ExternalLink.cs | 7 + .../ApplicantProfile/ExternalLinkType.cs | 7 + .../Applications/ApplicationForm.cs | 4 +- .../GrantTenantDbContext.cs | 17 +- ...4_ApplicantPortalExternalLinks.Designer.cs | 5306 +++++++++++++++++ ...06_AB33234_ApplicantPortalExternalLinks.cs | 29 + .../GrantTenantDbContextModelSnapshot.cs | 33 +- 7 files changed, 5388 insertions(+), 15 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs 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 index 0c5c29b668..703406c0a9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -15,6 +15,13 @@ public class ExternalLink [MaxLength(2048)] public required string Uri { get; set; } + /// + /// Gets or sets the type of the external link. + /// + public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Other; + public bool Publish { get; set; } = false; + public int Order { get; set; } = -1; + /// /// Gets or sets the title of the external link. /// 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..224552cf16 --- /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 +{ + Other = 1, + Renewal = 2 +} 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 5d32c560a3..6c1b8d0711 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -33,9 +33,7 @@ public class ApplicationForm : FullAuditedAggregateRoot, IMultiTenant public FormHierarchyType? FormHierarchy { get; set; } public Guid? ParentFormId { get; set; } public bool IsDirectApproval { get; set; } = false; - - public bool PublishRenewalLink { get; set; } - public ExternalLink? RenewalLink { get; set; } + public List ExternalLinks { get; set; } = []; public bool AutomaticallyGenerateAIAnalysis { get; set; } = false; public bool ManuallyInitiateAIAnalysis { get; set; } = false; 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/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs new file mode 100644 index 0000000000..6dec589832 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs @@ -0,0 +1,5306 @@ +// +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("20260813010006_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("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => + { + b1.IsRequired(); + + b1.Property("Description") + .IsRequired(); + + b1.Property("ExternalLinkType"); + + b1.Property("Order"); + + b1.Property("Publish"); + + b1.Property("Title") + .IsRequired(); + + b1.Property("Uri") + .IsRequired(); + + b1 + .ToJson("ExternalLinks") + .HasColumnType("jsonb"); + }); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .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("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Module") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs new file mode 100644 index 0000000000..9dc4ded7f6 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33234_ApplicantPortalExternalLinks : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExternalLinks", + table: "ApplicationForms", + type: "jsonb", + nullable: false, + defaultValue: "{}"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExternalLinks", + table: "ApplicationForms"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 49f2570401..4f1c5a6531 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1814,6 +1815,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); + b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => + { + b1.IsRequired(); + + b1.Property("Description") + .IsRequired(); + + b1.Property("ExternalLinkType"); + + b1.Property("Order"); + + b1.Property("Publish"); + + b1.Property("Title") + .IsRequired(); + + b1.Property("Uri") + .IsRequired(); + + b1 + .ToJson("ExternalLinks") + .HasColumnType("jsonb"); + }); + b.HasKey("Id"); b.HasIndex("IntakeId"); @@ -3114,10 +3139,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsActive") .HasColumnType("boolean"); - b.Property("Module") - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("boolean") @@ -3132,6 +3153,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("LastModifierId"); + b.Property("Module") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + b.Property("RecipientCategory") .HasColumnType("text"); From 869c87dc554e5e7df76b83d977cccce4d7981748 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:20:08 -0700 Subject: [PATCH 050/121] [AB#33234] Map External Links to Submission Info Data Provider --- .../ProfileData/ExternalLinkDto.cs | 16 ++++--------- .../ProfileData/SubmissionInfoItemDto.cs | 2 ++ .../SubmissionInfoDataProvider.cs | 24 +++++++++++++++++-- .../ApplicantProfile/ExternalLinkType.cs | 2 +- 4 files changed, 29 insertions(+), 15 deletions(-) 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 index 1a21d149b6..99fe106a85 100644 --- 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 @@ -5,18 +5,10 @@ /// public class ExternalLinkDto { - /// - /// Gets or sets the URL of the external link. - /// - public string Uri { get; set; } = string.Empty; - - /// - /// Gets or sets the title of the external link. - /// + public required string Uri { get; set; } + public ExternalLinkType ExternalLinkType { get; set; } + public bool Publish { get; set; } = false; + public int Order { get; set; } = -1; public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets the description of the external link. - /// public string Description { get; set; } = string.Empty; } 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 815a618d75..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,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Unity.GrantManager.ApplicantProfile.ProfileData; @@ -12,4 +13,5 @@ public class SubmissionInfoItemDto 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/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs index d56d98fa28..140c755dd4 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,13 @@ 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 = form.ExternalLinks + .FirstOrDefault(x => x.Publish && x.ExternalLinkType == ExternalLinkType.Renewal), + RelatedLinks = form.ExternalLinks + .Where(x => x.Publish && x.ExternalLinkType == ExternalLinkType.Related) + .OrderBy(x => x.Order) + .ThenBy(x => x.Title) }).ToListAsync(); dto.Submissions.AddRange(results.Select(s => new SubmissionInfoItemDto @@ -80,13 +86,27 @@ 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 is null ? null : MapExternalLink(s.RenewalLink), + RelatedLinks = [.. s.RelatedLinks.Select(MapExternalLink)] })); } return dto; } + private static ExternalLinkDto MapExternalLink(ExternalLink link) + { + return new ExternalLinkDto + { + Uri = link.Uri, + ExternalLinkType = link.ExternalLinkType, + Order = link.Order, + Title = link.Title, + Description = link.Description + }; + } + /// /// Derives the CHEFS form view URL from the INTAKE_API_BASE dynamic URL setting. /// e.g. https://chefs-dev.apps.silver.devops.gov.bc.ca/app/api/v1 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 index 224552cf16..4136ccd68f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinkType.cs @@ -2,6 +2,6 @@ public enum ExternalLinkType { - Other = 1, + Related = 1, Renewal = 2 } From 95c825eef3b2df8cd6bc6794ca8fd89422d09efb Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:21:06 -0700 Subject: [PATCH 051/121] [AB#33234] Fix ExternalLinkType reference --- .../ApplicantProfile/ExternalLink.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 703406c0a9..4369b17d0f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -18,7 +18,7 @@ public class ExternalLink /// /// Gets or sets the type of the external link. /// - public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Other; + public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Related; public bool Publish { get; set; } = false; public int Order { get; set; } = -1; From 080403b47be3a276e55ca9a4f38ab5951a2aea65 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:22:02 -0700 Subject: [PATCH 052/121] [AB#33234] Reset migration to resolve conflict --- ...4_ApplicantPortalExternalLinks.Designer.cs | 5306 ----------------- ...06_AB33234_ApplicantPortalExternalLinks.cs | 29 - .../GrantTenantDbContextModelSnapshot.cs | 25 - 3 files changed, 5360 deletions(-) delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs deleted file mode 100644 index 6dec589832..0000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.Designer.cs +++ /dev/null @@ -1,5306 +0,0 @@ -// -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("20260813010006_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("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IdentityEmail") - .HasColumnType("text"); - - b.Property("IdentityName") - .HasColumnType("text"); - - b.Property("IdentityProvider") - .HasColumnType("text"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsConfirmed") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OidcSubUser") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("Phone2") - .HasColumnType("text"); - - b.Property("Phone2Extension") - .HasColumnType("text"); - - b.Property("PhoneExtension") - .HasColumnType("text"); - - b.Property("RoleForApplicant") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationId") - .IsUnique(); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicantAgents", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("ApplicantAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AIAnalysis") - .HasColumnType("text"); - - b.Property("AIScoresheetAnswers") - .HasColumnType("jsonb"); - - b.Property("Acquisition") - .HasColumnType("text"); - - b.Property("ApplicantElectoralDistrict") - .HasColumnType("text"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("ApplicationStatusId") - .HasColumnType("uuid"); - - b.Property("ApprovedAmount") - .HasColumnType("numeric"); - - b.Property("AssessmentResultDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AssessmentResultStatus") - .HasColumnType("text"); - - b.Property("AssessmentStartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Community") - .HasColumnType("text"); - - b.Property("CommunityPopulation") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContractExecutionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ContractNumber") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeclineRational") - .HasColumnType("text"); - - b.Property("DefaultSiteId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("DueDate") - .HasColumnType("timestamp without time zone"); - - b.Property("DueDiligenceStatus") - .HasColumnType("text"); - - b.Property("EconomicRegion") - .HasColumnType("text"); - - b.Property("ElectoralDistrict") - .HasColumnType("text"); - - b.Property("ExternalStatusVisibility") - .HasColumnType("boolean"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FinalDecisionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Forestry") - .HasColumnType("text"); - - b.Property("ForestryFocus") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LikelihoodOfFunding") - .HasColumnType("text"); - - b.Property("Notes") - .HasColumnType("text"); - - b.Property("NotificationDate") - .HasColumnType("timestamp without time zone"); - - b.Property("OwnerId") - .HasColumnType("uuid"); - - b.Property("Payload") - .HasColumnType("jsonb"); - - b.Property("PercentageTotalProjectBudget") - .HasColumnType("double precision"); - - b.Property("Place") - .HasColumnType("text"); - - b.Property("ProjectEndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ProjectFundingTotal") - .HasColumnType("numeric"); - - b.Property("ProjectName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("ProjectStartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ProjectSummary") - .HasColumnType("text"); - - b.Property("ProposalDate") - .HasColumnType("timestamp without time zone"); - - b.Property("RecommendedAmount") - .HasColumnType("numeric"); - - b.Property("ReferenceNo") - .IsRequired() - .HasColumnType("text"); - - b.Property("RegionalDistrict") - .HasColumnType("text"); - - b.Property("RequestedAmount") - .HasColumnType("numeric"); - - b.Property("RiskRanking") - .HasColumnType("text"); - - b.Property("SigningAuthorityBusinessPhone") - .HasColumnType("text"); - - b.Property("SigningAuthorityCellPhone") - .HasColumnType("text"); - - b.Property("SigningAuthorityEmail") - .HasColumnType("text"); - - b.Property("SigningAuthorityFullName") - .HasColumnType("text"); - - b.Property("SigningAuthorityTitle") - .HasColumnType("text"); - - b.Property("SubStatus") - .HasColumnType("text"); - - b.Property("SubmissionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TotalProjectBudget") - .HasColumnType("numeric"); - - b.Property("TotalScore") - .HasColumnType("integer"); - - b.Property("UnityApplicationId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationFormId"); - - b.HasIndex("ApplicationStatusId"); - - b.HasIndex("OwnerId"); - - b.HasIndex("ReferenceNo"); - - b.HasIndex("TenantId", "SubmissionDate") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("Applications", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("AssigneeId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Duty") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("AssigneeId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationAssignments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AISummary") - .HasColumnType("text"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChefsFileId") - .HasColumnType("text"); - - b.Property("ChefsSubmissionId") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationChefsFileAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContactEmail") - .HasColumnType("text"); - - b.Property("ContactFullName") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactMobilePhone") - .HasColumnType("text"); - - b.Property("ContactTitle") - .HasColumnType("text"); - - b.Property("ContactType") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactWorkPhone") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("ApplicationContact", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccountCodingId") - .HasColumnType("uuid"); - - b.Property("ApiKey") - .HasColumnType("text"); - - b.Property("ApplicationFormDescription") - .HasColumnType("text"); - - b.Property("ApplicationFormName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("AttemptedConnectionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AutomaticallyGenerateAIAnalysis") - .HasColumnType("boolean"); - - b.Property("AvailableChefsFields") - .HasColumnType("text"); - - b.Property("Category") - .HasColumnType("text"); - - b.Property("ChefsApplicationFormGuid") - .HasColumnType("text"); - - b.Property("ChefsCriteriaFormGuid") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ConnectionHttpStatus") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DefaultPaymentGroup") - .HasColumnType("integer"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ElectoralDistrictAddressType") - .HasColumnType("integer"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormHierarchy") - .HasColumnType("integer"); - - b.Property("IntakeId") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("IsDirectApproval") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("ManuallyInitiateAIAnalysis") - .HasColumnType("boolean"); - - b.Property("ParentFormId") - .HasColumnType("uuid"); - - b.Property("Payable") - .HasColumnType("boolean"); - - b.Property("PaymentApprovalThreshold") - .HasColumnType("numeric"); - - b.Property("Prefix") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PreventPayment") - .HasColumnType("boolean"); - - b.Property("ScoresheetId") - .HasColumnType("uuid"); - - b.Property("SuffixType") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Version") - .HasColumnType("integer"); - - b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => - { - b1.IsRequired(); - - b1.Property("Description") - .IsRequired(); - - b1.Property("ExternalLinkType"); - - b1.Property("Order"); - - b1.Property("Publish"); - - b1.Property("Title") - .IsRequired(); - - b1.Property("Uri") - .IsRequired(); - - b1 - .ToJson("ExternalLinks") - .HasColumnType("jsonb"); - }); - - b.HasKey("Id"); - - b.HasIndex("IntakeId"); - - b.HasIndex("ParentFormId"); - - b.HasIndex("TenantId", "IsDeleted") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("ApplicationForms", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("ApplicationFormVersionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChefsSubmissionGuid") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormVersionId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OidcSub") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("Submission") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("ApplicationFormId"); - - b.ToTable("ApplicationFormSubmissions", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationFormId") - .HasColumnType("uuid"); - - b.Property("AvailableChefsFields") - .HasColumnType("text"); - - b.Property("ChefsApplicationFormGuid") - .HasColumnType("text"); - - b.Property("ChefsFormVersionGuid") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormSchema") - .HasColumnType("jsonb"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Published") - .HasColumnType("boolean"); - - b.Property("ReportColumns") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportKeys") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReportViewName") - .IsRequired() - .HasColumnType("text"); - - b.Property("SubmissionHeaderMapping") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Version") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationFormId"); - - b.ToTable("ApplicationFormVersion", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LinkType") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("Related"); - - b.Property("LinkedApplicationId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationLinks", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExternalStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("InternalStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("NotifiedStatus") - .HasColumnType("text"); - - b.Property("StatusCode") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("StatusCode") - .IsUnique(); - - b.ToTable("ApplicationStatuses", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TagId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("TagId"); - - b.HasIndex("TenantId", "ApplicationId"); - - b.ToTable("ApplicationTags", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("AssessmentId"); - - b.ToTable("AssessmentAttachments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("AuditDate") - .HasColumnType("timestamp without time zone"); - - b.Property("AuditNote") - .HasColumnType("text"); - - b.Property("AuditStatus") - .HasColumnType("text"); - - b.Property("AuditTrackingNumber") - .HasColumnType("text"); - - b.Property("AuditorName") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("AuditHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApprovedAmount") - .HasColumnType("numeric"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FundingNotes") - .HasColumnType("text"); - - b.Property("FundingYear") - .HasColumnType("text"); - - b.Property("GrantCategory") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OneTimeConsideration") - .HasColumnType("numeric"); - - b.Property("PaidDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ReconsiderationAmount") - .HasColumnType("numeric"); - - b.Property("RenewedFunding") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TotalGrantAmount") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("FundingHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IssueDescription") - .HasColumnType("text"); - - b.Property("IssueHeading") - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("ResolutionNote") - .HasColumnType("text"); - - b.Property("Resolved") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Year") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("IssueTrackings", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FiscalYear") - .HasColumnType("text"); - - b.Property("IncompleteReport") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("Outstanding") - .HasColumnType("boolean"); - - b.Property("ReportDate") - .HasColumnType("timestamp without time zone"); - - b.Property("SignedOff") - .HasColumnType("boolean"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.ToTable("ReportsHistories", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ApprovalRecommended") - .HasColumnType("boolean"); - - b.Property("AssessorId") - .HasColumnType("uuid"); - - b.Property("CleanGrowth") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("EconomicImpact") - .HasColumnType("integer"); - - b.Property("EndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FinancialAnalysis") - .HasColumnType("integer"); - - b.Property("InclusiveGrowth") - .HasColumnType("integer"); - - b.Property("IsAiAssessment") - .HasColumnType("boolean"); - - b.Property("IsComplete") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("AssessorId"); - - b.ToTable("Assessments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicantId"); - - b.HasIndex("CommenterId"); - - b.ToTable("ApplicantComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("CommenterId"); - - b.ToTable("ApplicationComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("Comment") - .IsRequired() - .HasColumnType("text"); - - b.Property("CommenterId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PinDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("AssessmentId"); - - b.HasIndex("CommenterId"); - - b.ToTable("AssessmentComments", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("HomePhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MobilePhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("WorkPhoneExtension") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WorkPhoneNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id"); - - b.ToTable("Contacts", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContactId") - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsPrimary") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("RelatedEntityId") - .HasColumnType("uuid"); - - b.Property("RelatedEntityType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Role") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("RelatedEntityType", "RelatedEntityId"); - - b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); - - b.ToTable("ContactLinks", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Tags", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Badge") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FullName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OidcDisplayName") - .IsRequired() - .HasColumnType("text"); - - b.Property("OidcSub") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("OidcSub"); - - b.HasIndex("TenantId"); - - b.ToTable("Persons", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Budget") - .HasColumnType("double precision"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EndDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IntakeName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("StartDate") - .HasColumnType("timestamp without time zone"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Intakes", (string)null); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationStatus") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("ApplicationStatusId") - .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("DateField") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EmailTemplateId") - .HasColumnType("uuid"); - - b.Property("EventType") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FormId") - .HasColumnType("uuid"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Module") - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("RecipientCategory") - .HasColumnType("text"); - - b.Property("RecipientIdentifier") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TriggerDetail") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("TriggerType") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Id"); - - b.HasIndex("TenantId"); - - b.ToTable("ScheduledNotifications", "Notifications"); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone"); - - b.Property("CreatorId") - .HasColumnType("uuid"); - - b.Property("DateField") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NotificationSentDate") - .HasColumnType("timestamp without time zone"); - - b.Property("ScheduledNotificationId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("CreationTime"); - - b.HasIndex("ScheduledNotificationId"); - - b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") - .IsUnique(); - - b.ToTable("ScheduledNotificationTracking", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Type") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EmailGroups", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("GroupId"); - - b.ToTable("EmailGroupUsers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicantId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("AssessmentId") - .HasColumnType("uuid"); - - b.Property("BCC") - .IsRequired() - .HasColumnType("text"); - - b.Property("Body") - .IsRequired() - .HasColumnType("text"); - - b.Property("BodyType") - .IsRequired() - .HasColumnType("text"); - - b.Property("CC") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChesHttpStatusCode") - .HasColumnType("text"); - - b.Property("ChesMsgId") - .HasColumnType("uuid"); - - b.Property("ChesResponse") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChesStatus") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("EmailType") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FromAddress") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestIds") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .IsRequired() - .HasColumnType("text"); - - b.Property("Recipient") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("RetryAttempts") - .HasColumnType("integer"); - - b.Property("ScheduledNotificationId") - .HasColumnType("uuid"); - - b.Property("SendOnDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("SentDateTime") - .HasColumnType("timestamp without time zone"); - - b.Property("Status") - .IsRequired() - .HasColumnType("text"); - - b.Property("Subject") - .IsRequired() - .HasColumnType("text"); - - b.Property("Tag") - .IsRequired() - .HasColumnType("text"); - - b.Property("TemplateName") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("ToAddress") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EmailLogs", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContentType") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DisplayName") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("EmailLogId") - .HasColumnType("uuid"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FileName") - .HasColumnType("text"); - - b.Property("FileSize") - .HasColumnType("bigint"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("OriginTemplateId") - .HasColumnType("uuid"); - - b.Property("S3ObjectKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("TemplateId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Time") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("EmailLogId"); - - b.HasIndex("S3ObjectKey"); - - b.HasIndex("TemplateId"); - - b.ToTable("EmailLogAttachments", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BodyHTML") - .IsRequired() - .HasColumnType("text"); - - b.Property("BodyText") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("RecipientCategory") - .HasColumnType("text"); - - b.Property("RecipientIdentifier") - .HasColumnType("text"); - - b.Property("SendFrom") - .IsRequired() - .HasColumnType("text"); - - b.Property("Subject") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("EmailTemplates", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastName") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Subscribers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("SubscriptionGroups", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("SubscriberId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("GroupId"); - - b.HasIndex("SubscriberId"); - - b.ToTable("SubscriptionGroupSubscribers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MapTo") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Token") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("TemplateVariables", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("InternalName") - .IsRequired() - .HasColumnType("text"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Triggers", "Notifications"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("SubscriptionGroupId") - .HasColumnType("uuid"); - - b.Property("TemplateId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("TriggerId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("SubscriptionGroupId"); - - b.HasIndex("TemplateId"); - - b.HasIndex("TriggerId"); - - b.ToTable("TriggerSubscriptions", "Notifications"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("Description") - .HasMaxLength(35) - .HasColumnType("character varying(35)"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("MinistryClient") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("Responsibility") - .IsRequired() - .HasColumnType("text"); - - b.Property("ServiceLine") - .IsRequired() - .HasColumnType("text"); - - b.Property("Stob") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("AccountCodings", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DefaultAccountCodingId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentIdPrefix") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("PaymentConfigurations", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DecisionDate") - .HasColumnType("timestamp without time zone"); - - b.Property("DecisionUserId") - .HasColumnType("uuid"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestId") - .HasColumnType("uuid"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("PaymentRequestId"); - - b.ToTable("ExpenseApprovals", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccountCodingId") - .HasColumnType("uuid"); - - b.Property("Amount") - .HasColumnType("numeric"); - - b.Property("BatchName") - .IsRequired() - .HasColumnType("text"); - - b.Property("BatchNumber") - .HasColumnType("numeric"); - - b.Property("CancelledBy") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("CancelledBy"); - - b.Property("CancelledById") - .HasColumnType("uuid") - .HasColumnName("CancelledById"); - - b.Property("CancelledOn") - .HasColumnType("timestamp without time zone") - .HasColumnName("CancelledOn"); - - b.Property("CasHttpStatusCode") - .HasColumnType("integer"); - - b.Property("CasResponse") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("ContractNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("CorrelationProvider") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("FsbApNotified") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("FsbNotificationEmailLogId") - .HasColumnType("uuid"); - - b.Property("FsbNotificationSentDate") - .HasColumnType("timestamp without time zone"); - - b.Property("InvoiceNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("InvoiceStatus") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("IsRecon") - .HasColumnType("boolean"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("PayeeName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PaymentDate") - .HasColumnType("text"); - - b.Property("PaymentNumber") - .HasColumnType("text"); - - b.Property("PaymentStatus") - .HasColumnType("text"); - - b.Property("ReferenceNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("RequesterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("SiteId") - .HasColumnType("uuid"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmissionConfirmationCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("SupplierName") - .HasColumnType("text"); - - b.Property("SupplierNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("AccountCodingId"); - - b.HasIndex("CorrelationId"); - - b.HasIndex("CreationTime"); - - b.HasIndex("FsbNotificationEmailLogId"); - - b.HasIndex("ReferenceNumber") - .IsUnique(); - - b.HasIndex("SiteId"); - - b.HasIndex("Status"); - - b.HasIndex("TenantId", "CreationTime") - .HasFilter("\"IsDeleted\" = false"); - - b.ToTable("PaymentRequests", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("PaymentRequestId") - .HasColumnType("uuid"); - - b.Property("TagId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("PaymentRequestId"); - - b.HasIndex("TagId"); - - b.ToTable("PaymentTags", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("Threshold") - .HasColumnType("numeric"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.ToTable("PaymentThresholds", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AddressLine1") - .HasColumnType("text"); - - b.Property("AddressLine2") - .HasColumnType("text"); - - b.Property("AddressLine3") - .HasColumnType("text"); - - b.Property("BankAccount") - .HasColumnType("text"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("EFTAdvicePref") - .HasColumnType("text"); - - b.Property("EmailAddress") - .HasColumnType("text"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastUpdatedInCas") - .HasColumnType("timestamp without time zone"); - - b.Property("MarkDeletedInUse") - .HasColumnType("boolean"); - - b.Property("Number") - .IsRequired() - .HasColumnType("text"); - - b.Property("PaymentGroup") - .HasColumnType("integer"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("SiteProtected") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("text"); - - b.Property("SupplierId") - .HasColumnType("uuid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.HasIndex("SupplierId"); - - b.ToTable("Sites", "Payments"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BusinessNumber") - .HasColumnType("text"); - - b.Property("City") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uuid") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("DeletionTime"); - - b.Property("ExtraProperties") - .IsRequired() - .HasColumnType("text") - .HasColumnName("ExtraProperties"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("LastUpdatedInCAS") - .HasColumnType("timestamp without time zone"); - - b.Property("MailingAddress") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Number") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("SIN") - .HasColumnType("text"); - - b.Property("StandardIndustryClassification") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("text"); - - b.Property("Subcategory") - .HasColumnType("text"); - - b.Property("SupplierProtected") - .HasColumnType("text"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.HasKey("Id"); - - b.ToTable("Suppliers", "Payments"); - }); - - modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("CorrelationProvider") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uuid") - .HasColumnName("CreatorId"); - - b.Property("LastModificationTime") - .HasColumnType("timestamp without time zone") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uuid") - .HasColumnName("LastModifierId"); - - b.Property("Mapping") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("RoleStatus") - .HasColumnType("integer"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("TenantId"); - - b.Property("ViewName") - .IsRequired() - .HasColumnType("text"); - - b.Property("ViewStatus") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("ReportColumnsMaps", "Reporting"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") - .WithMany("Instances") - .HasForeignKey("ScoresheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scoresheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") - .WithMany("Answers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) - .WithMany("Answers") - .HasForeignKey("ScoresheetInstanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") - .WithMany("Fields") - .HasForeignKey("SectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Section"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => - { - b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") - .WithMany("Sections") - .HasForeignKey("ScoresheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scoresheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => - { - b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) - .WithMany("Values") - .HasForeignKey("WorksheetInstanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") - .WithMany("Links") - .HasForeignKey("WorksheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Worksheet"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") - .WithMany("Fields") - .HasForeignKey("SectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Section"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => - { - b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") - .WithMany("Sections") - .HasForeignKey("WorksheetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Worksheet"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") - .WithMany("ApplicantAddresses") - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicantAddresses") - .HasForeignKey("ApplicationId"); - - b.Navigation("Applicant"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithOne("ApplicantAgent") - .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") - .WithMany("Applications") - .HasForeignKey("ApplicationStatusId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("Applicant"); - - b.Navigation("ApplicationForm"); - - b.Navigation("ApplicationStatus"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicationAssignments") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") - .WithMany() - .HasForeignKey("AssigneeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Application"); - - b.Navigation("Assignee"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => - { - b.HasOne("Unity.GrantManager.Intakes.Intake", null) - .WithMany() - .HasForeignKey("IntakeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ParentFormId") - .OnDelete(DeleteBehavior.NoAction); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => - { - b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) - .WithMany() - .HasForeignKey("ApplicationFormId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany("ApplicationLinks") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("ApplicationTags") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Application"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => - { - b.HasOne("Unity.GrantManager.Assessments.Assessment", null) - .WithMany() - .HasForeignKey("AssessmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId"); - }); - - modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", "Application") - .WithMany("Assessments") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("AssessorId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => - { - b.HasOne("Unity.GrantManager.Applications.Applicant", null) - .WithMany() - .HasForeignKey("ApplicantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => - { - b.HasOne("Unity.GrantManager.Assessments.Assessment", null) - .WithMany() - .HasForeignKey("AssessmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Identity.Person", null) - .WithMany() - .HasForeignKey("CommenterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => - { - b.HasOne("Unity.GrantManager.Contacts.Contact", null) - .WithMany() - .HasForeignKey("ContactId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => - { - b.HasOne("Unity.GrantManager.Applications.Application", null) - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) - .WithMany() - .HasForeignKey("ScheduledNotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => - { - b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => - { - b.HasOne("Unity.Notifications.Emails.EmailLog", null) - .WithMany() - .HasForeignKey("EmailLogId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) - .WithMany() - .HasForeignKey("TemplateId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => - { - b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") - .WithMany() - .HasForeignKey("GroupId"); - - b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") - .WithMany() - .HasForeignKey("SubscriberId"); - - b.Navigation("Subscriber"); - - b.Navigation("SubscriptionGroup"); - }); - - modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => - { - b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") - .WithMany() - .HasForeignKey("SubscriptionGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") - .WithMany() - .HasForeignKey("TemplateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") - .WithMany() - .HasForeignKey("TriggerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EmailTemplate"); - - b.Navigation("SubscriptionGroup"); - - b.Navigation("Trigger"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => - { - b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") - .WithMany("ExpenseApprovals") - .HasForeignKey("PaymentRequestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("PaymentRequest"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") - .WithMany() - .HasForeignKey("AccountCodingId") - .OnDelete(DeleteBehavior.NoAction); - - b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") - .WithMany() - .HasForeignKey("SiteId") - .OnDelete(DeleteBehavior.NoAction); - - b.Navigation("AccountCoding"); - - b.Navigation("Site"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => - { - b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) - .WithMany("PaymentTags") - .HasForeignKey("PaymentRequestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.NoAction) - .IsRequired(); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => - { - b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") - .WithMany("Sites") - .HasForeignKey("SupplierId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Supplier"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => - { - b.Navigation("Answers"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => - { - b.Navigation("Answers"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => - { - b.Navigation("Instances"); - - b.Navigation("Sections"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => - { - b.Navigation("Fields"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => - { - b.Navigation("Values"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => - { - b.Navigation("Links"); - - b.Navigation("Sections"); - }); - - modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => - { - b.Navigation("Fields"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => - { - b.Navigation("ApplicantAddresses"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => - { - b.Navigation("ApplicantAddresses"); - - b.Navigation("ApplicantAgent"); - - b.Navigation("ApplicationAssignments"); - - b.Navigation("ApplicationLinks"); - - b.Navigation("ApplicationTags"); - - b.Navigation("Assessments"); - }); - - modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => - { - b.Navigation("Applications"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => - { - b.Navigation("ExpenseApprovals"); - - b.Navigation("PaymentTags"); - }); - - modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => - { - b.Navigation("Sites"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs deleted file mode 100644 index 9dc4ded7f6..0000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813010006_AB33234_ApplicantPortalExternalLinks.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Unity.GrantManager.Migrations.TenantMigrations -{ - /// - public partial class AB33234_ApplicantPortalExternalLinks : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ExternalLinks", - table: "ApplicationForms", - type: "jsonb", - nullable: false, - defaultValue: "{}"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ExternalLinks", - table: "ApplicationForms"); - } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 4f1c5a6531..c0767c94b0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1815,30 +1814,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); - b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => - { - b1.IsRequired(); - - b1.Property("Description") - .IsRequired(); - - b1.Property("ExternalLinkType"); - - b1.Property("Order"); - - b1.Property("Publish"); - - b1.Property("Title") - .IsRequired(); - - b1.Property("Uri") - .IsRequired(); - - b1 - .ToJson("ExternalLinks") - .HasColumnType("jsonb"); - }); - b.HasKey("Id"); b.HasIndex("IntakeId"); From 498b766c9c8febdaf0758ba932b3d60a0192618d Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:41:30 -0700 Subject: [PATCH 053/121] [AB#33234] Refactor ExternalLink --- .../ProfileData/ExternalLinkDto.cs | 6 ++--- .../SubmissionInfoDataProvider.cs | 4 ++-- .../ApplicantProfile/ExternalLink.cs | 22 +++++-------------- 3 files changed, 10 insertions(+), 22 deletions(-) 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 index 99fe106a85..b9e17dffc4 100644 --- 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 @@ -6,9 +6,9 @@ public class ExternalLinkDto { public required string Uri { get; set; } - public ExternalLinkType ExternalLinkType { get; set; } - public bool Publish { get; set; } = false; - public int Order { get; set; } = -1; 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/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs index 140c755dd4..a00f66f727 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 @@ -71,9 +71,9 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id ? status.NotifiedStatus ?? status.ExternalStatus : status.ExternalStatus, RenewalLink = form.ExternalLinks - .FirstOrDefault(x => x.Publish && x.ExternalLinkType == ExternalLinkType.Renewal), + .FirstOrDefault(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal), RelatedLinks = form.ExternalLinks - .Where(x => x.Publish && x.ExternalLinkType == ExternalLinkType.Related) + .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Related) .OrderBy(x => x.Order) .ThenBy(x => x.Title) }).ToListAsync(); 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 index 4369b17d0f..1f8f7c36fa 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -4,33 +4,21 @@ namespace Unity.GrantManager.ApplicantProfile; /// -/// Represents a link to be used within the Applicant Portal, including the URL, title, and description. +/// Represents a link to be used within the Applicant Portal, including the URI, title, and description. /// [ComplexType] public class ExternalLink { - /// - /// Gets or sets the URL of the external link. - /// [MaxLength(2048)] public required string Uri { get; set; } - /// - /// Gets or sets the type of the external link. - /// - public ExternalLinkType ExternalLinkType { get; set; } = ExternalLinkType.Related; - public bool Publish { get; set; } = false; - public int Order { get; set; } = -1; - - /// - /// Gets or sets the title of the external link. - /// [MaxLength(255)] public string Title { get; set; } = string.Empty; - /// - /// Gets or sets the description of the external link. - /// [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 From e24e7c8cb0248354fb3303c6ea0ff3b46fb80d62 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:44:22 -0700 Subject: [PATCH 054/121] [AB#33234] Add EligibleForRenewal to Application --- .../GrantApplications/GrantApplicationDto.cs | 1 + .../SubmissionInfoDataProvider.cs | 4 +- .../Applications/Application.cs | 2 + ...4_ApplicantPortalExternalLinks.Designer.cs | 5309 +++++++++++++++++ ...19_AB33234_ApplicantPortalExternalLinks.cs | 40 + .../GrantTenantDbContextModelSnapshot.cs | 28 + 6 files changed, 5382 insertions(+), 2 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs 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 a00f66f727..74a88e92cc 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 @@ -70,8 +70,8 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id Status = application.ExternalStatusVisibility ? status.NotifiedStatus ?? status.ExternalStatus : status.ExternalStatus, - RenewalLink = form.ExternalLinks - .FirstOrDefault(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal), + RenewalLink = application.EligibleForRenewal ? form.ExternalLinks + .FirstOrDefault(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal) : null, RelatedLinks = form.ExternalLinks .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Related) .OrderBy(x => x.Order) 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.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("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("EligibleForRenewal") + .HasColumnType("boolean"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => + { + b1.IsRequired(); + + b1.Property("Description") + .IsRequired(); + + b1.Property("ExternalLinkType"); + + b1.Property("Order"); + + b1.Property("Published"); + + b1.Property("Title") + .IsRequired(); + + b1.Property("Uri") + .IsRequired(); + + b1 + .ToJson("ExternalLinks") + .HasColumnType("jsonb"); + }); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .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("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Module") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs new file mode 100644 index 0000000000..4cad4aac8c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33234_ApplicantPortalExternalLinks : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EligibleForRenewal", + table: "Applications", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "ExternalLinks", + table: "ApplicationForms", + type: "jsonb", + nullable: false, + defaultValue: "{}"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EligibleForRenewal", + table: "Applications"); + + migrationBuilder.DropColumn( + name: "ExternalLinks", + table: "ApplicationForms"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index c0767c94b0..c7e9cebb02 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1294,6 +1295,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ElectoralDistrict") .HasColumnType("text"); + b.Property("EligibleForRenewal") + .HasColumnType("boolean"); + b.Property("ExternalStatusVisibility") .HasColumnType("boolean"); @@ -1814,6 +1818,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); + b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => + { + b1.IsRequired(); + + b1.Property("Description") + .IsRequired(); + + b1.Property("ExternalLinkType"); + + b1.Property("Order"); + + b1.Property("Published"); + + b1.Property("Title") + .IsRequired(); + + b1.Property("Uri") + .IsRequired(); + + b1 + .ToJson("ExternalLinks") + .HasColumnType("jsonb"); + }); + b.HasKey("Id"); b.HasIndex("IntakeId"); From b0251e5f02d2e65ec7802adff0a8baeecfc39700 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:22:56 -0700 Subject: [PATCH 055/121] [AB#33234] Optimize ExternalLink queries to DB --- .../SubmissionInfoDataProvider.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) 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 74a88e92cc..c678a87459 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 @@ -71,11 +71,28 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id ? status.NotifiedStatus ?? status.ExternalStatus : status.ExternalStatus, RenewalLink = application.EligibleForRenewal ? form.ExternalLinks - .FirstOrDefault(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal) : null, + .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) .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 @@ -87,26 +104,14 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id ReferenceNo = s.ReferenceNo, Type = s.FormName, Status = s.Status, - RenewalLink = s.RenewalLink is null ? null : MapExternalLink(s.RenewalLink), - RelatedLinks = [.. s.RelatedLinks.Select(MapExternalLink)] + RenewalLink = s.RenewalLink, + RelatedLinks = [.. s.RelatedLinks] })); } return dto; } - private static ExternalLinkDto MapExternalLink(ExternalLink link) - { - return new ExternalLinkDto - { - Uri = link.Uri, - ExternalLinkType = link.ExternalLinkType, - Order = link.Order, - Title = link.Title, - Description = link.Description - }; - } - /// /// Derives the CHEFS form view URL from the INTAKE_API_BASE dynamic URL setting. /// e.g. https://chefs-dev.apps.silver.devops.gov.bc.ca/app/api/v1 From af5728bbb14598c0173029d9d7ac4baeb47307f2 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:24:42 -0700 Subject: [PATCH 056/121] [AB#33234] Related Links should sort ascending except for negatives --- .../DataProviders/SubmissionInfoDataProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c678a87459..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 @@ -83,7 +83,7 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id .FirstOrDefault() : null, RelatedLinks = form.ExternalLinks .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Related) - .OrderBy(x => x.Order) + .OrderBy(x => x.Order == -1 ? int.MaxValue : x.Order) // Links without an order default to -1 .ThenBy(x => x.Title) .Select(x => new ExternalLinkDto { From f3edea2724b68373798aaa389b9f13d85642aac4 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:52:21 -0700 Subject: [PATCH 057/121] [AB#33234] Add backend ExternalLink AppService and Validation --- .../ApplicationForms/ApplicationFormDto.cs | 2 + .../ApplicationForms/ExternalLinkConfigDto.cs | 34 +++++++++++ .../ExternalLinkUriValidator.cs | 20 +++++++ .../ExternalLinksConfigDto.cs | 56 +++++++++++++++++++ .../IApplicationFormAppService.cs | 1 + .../ApplicationFormAppService.cs | 26 +++++++++ .../GrantManagerApplicationMapperlyProfile.cs | 2 + .../GrantManagerDomainErrorCodes.cs | 6 ++ .../Localization/GrantManager/en.json | 22 ++++++++ .../Applications/ApplicationForm.cs | 50 +++++++++++++++++ .../ApplicationFormConfigWidget.cs | 22 +++++++- .../ApplicationFormConfigWidgetViewModel.cs | 7 +++ .../RelatedLinkItemViewModel.cs | 9 +++ 13 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkConfigDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinkUriValidator.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/RelatedLinkItemViewModel.cs 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/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/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/ApplicationForm.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs index 6c1b8d0711..c525c1c6d7 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -4,6 +4,7 @@ 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; @@ -82,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.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs index d24d12afb0..f8fc97f7f7 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Unity.GrantManager.ApplicantProfile; using Unity.GrantManager.ApplicationForms; using Unity.GrantManager.Applications; using Volo.Abp.AspNetCore.Mvc; @@ -29,6 +30,20 @@ public async Task InvokeAsync(string? configType, Applicat { await Task.CompletedTask; + var externalLinks = applicationForm?.ExternalLinks ?? []; + var renewalLink = externalLinks.FirstOrDefault(x => x.ExternalLinkType == ExternalLinkType.Renewal); + var relatedLinks = externalLinks + .Where(x => x.ExternalLinkType == ExternalLinkType.Related) + .OrderBy(x => x.Order == -1 ? int.MaxValue : x.Order) + .Select(x => new RelatedLinkItemViewModel + { + Uri = x.Uri, + Title = x.Title, + Description = x.Description, + Published = x.Published + }) + .ToList(); + var viewModel = new ApplicationFormConfigWidgetViewModel() { ConfigType = configType, @@ -37,7 +52,12 @@ public async Task InvokeAsync(string? configType, Applicat ElectoralDistrictAddressTypes = LoadElectoralAddressOptions(), Prefix = applicationForm?.Prefix, SuffixType = applicationForm?.SuffixType, - SuffixTypes = LoadSuffixOptions() + SuffixTypes = LoadSuffixOptions(), + RenewalLinkUri = renewalLink?.Uri ?? string.Empty, + RenewalLinkTitle = renewalLink?.Title ?? string.Empty, + RenewalLinkPublished = renewalLink?.Published ?? false, + ApplicantMessage = renewalLink?.Description ?? string.Empty, + RelatedLinks = relatedLinks }; return View(viewModel); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidgetViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidgetViewModel.cs index d44e05e758..a893aa5574 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidgetViewModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidgetViewModel.cs @@ -18,6 +18,12 @@ public class ApplicationFormConfigWidgetViewModel public SuffixConfigType? SuffixType { get; set; } public List SuffixTypes { get; set; } = []; + public string RenewalLinkUri { get; set; } = string.Empty; + public string RenewalLinkTitle { get; set; } = string.Empty; + public bool RenewalLinkPublished { get; set; } + public string ApplicantMessage { get; set; } = string.Empty; + public List RelatedLinks { get; set; } = []; + public static List FormatOptionsList(Dictionary optionsList) { List optionsFormattedList = new(); @@ -29,3 +35,4 @@ public static List FormatOptionsList(Dictionary } } + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/RelatedLinkItemViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/RelatedLinkItemViewModel.cs new file mode 100644 index 0000000000..57a9e0a9c6 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/RelatedLinkItemViewModel.cs @@ -0,0 +1,9 @@ +namespace Unity.GrantManager.Web.Views.Shared.Components.ApplicationFormConfigWidget; + +public class RelatedLinkItemViewModel +{ + public string Uri { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public bool Published { get; set; } +} From d7eea40357218793b4fda4fd28b3df6dd8086cc5 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 11:58:55 -0700 Subject: [PATCH 058/121] AB#33821 - Added legal disclaimer modal --- .../Localization/AI/en.json | 5 +++ .../Settings/LegalDisclaimerModal.cshtml | 22 +++++++++++++ .../Settings/LegalDisclaimerModal.cshtml.cs | 12 +++++++ .../AISettingGroup/AISettingViewComponent.cs | 1 + .../Views/Settings/AISettingGroup/Default.js | 31 ++++++++++++++----- .../AIConfigurationViewComponent.cs | 2 ++ .../Components/AIConfiguration/Default.js | 19 +++++++++--- .../Views/Shared/Scripts/AiLegalDisclaimer.js | 23 ++++++++++++++ 8 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml create mode 100644 applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml.cs create mode 100644 applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Scripts/AiLegalDisclaimer.js diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json index 88d3d02728..7d558af0f3 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json @@ -26,6 +26,11 @@ "Setting:AI.AutomaticGenerationEnabled": "Automatically Generate AI Analysis", "Setting:AI.ManualGenerationEnabled": "Manually Initiate AI Analysis", + "LegalDisclaimer:Title": "Legal Disclaimer", + "LegalDisclaimer:Body": "The Platform does not provide legal, financial, or policy advice, and may not be relied on to determine program eligibility or make final decisions in place of the user, who will be solely responsible for the use of the Platform, including the review and validation of Unity AI outputs before use.", + "LegalDisclaimer:Confirm": "I Understand and Agree", + "LegalDisclaimer:Confirming": "Confirming...", + "AI:AttachmentSummariesDisabled": "AI attachment summaries are not enabled.", "AI:ApplicationAnalysisDisabled": "AI application analysis is not enabled.", "AI:ScoringDisabled": "AI scoring is not enabled.", diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml new file mode 100644 index 0000000000..9c31c63ff6 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml @@ -0,0 +1,22 @@ +@page +@using Unity.AI.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model Unity.AI.Web.Pages.Settings.LegalDisclaimerModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} + +
+ + + + @L["LegalDisclaimer:Body"].Value + + + + + + +
diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml.cs new file mode 100644 index 0000000000..5c4b9dfa2e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/Settings/LegalDisclaimerModal.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.AI.Web.Pages.Settings; + +public class LegalDisclaimerModalModel : AbpPageModel +{ + public IActionResult OnPost() + { + return NoContent(); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs index 9400f61c24..921df10f0f 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs @@ -31,6 +31,7 @@ public class AISettingScriptBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { + context.Files.Add("/Views/Shared/Scripts/AILegalDisclaimer.js"); context.Files.Add("/Views/Settings/AISettingGroup/Default.js"); } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js index b956d5bfe6..c30e3be2dc 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js @@ -7,12 +7,32 @@ $(function () { let initialFormState = uiElements.settingForm.serialize(); + let lastSavedValues = { + automaticGenerationEnabled: $('#AutomaticGenerationEnabled').is(':checked'), + manualGenerationEnabled: $('#ManualGenerationEnabled').is(':checked') + }; + function checkFormChanges() { let isFormChanged = uiElements.settingForm.serialize() !== initialFormState; uiElements.saveButton.prop('disabled', !isFormChanged); uiElements.discardButton.prop('disabled', !isFormChanged); } + function saveSettings(automaticEnabled, manualEnabled) { + unity.aI.settings.aIConfiguration.updateTenantConfiguration({ + automaticGenerationEnabled: automaticEnabled, + manualGenerationEnabled: manualEnabled + }).then(function () { + lastSavedValues = { + automaticGenerationEnabled: automaticEnabled, + manualGenerationEnabled: manualEnabled + }; + $(document).trigger('AbpSettingSaved'); + initialFormState = uiElements.settingForm.serialize(); + checkFormChanges(); + }); + } + uiElements.settingForm.on('change', function () { checkFormChanges(); }); @@ -22,14 +42,11 @@ $(function () { const automaticEnabled = $('#AutomaticGenerationEnabled').is(':checked'); const manualEnabled = $('#ManualGenerationEnabled').is(':checked'); + const turningOn = (automaticEnabled && !lastSavedValues.automaticGenerationEnabled) || + (manualEnabled && !lastSavedValues.manualGenerationEnabled); - unity.aI.settings.aIConfiguration.updateTenantConfiguration({ - automaticGenerationEnabled: automaticEnabled, - manualGenerationEnabled: manualEnabled - }).then(function () { - $(document).trigger('AbpSettingSaved'); - initialFormState = uiElements.settingForm.serialize(); - checkFormChanges(); + unity.aI.legalDisclaimer.confirmIfNeeded(turningOn, function () { + saveSettings(automaticEnabled, manualEnabled); }); }); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs index a2de10dd7a..27eb12ee13 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs @@ -37,6 +37,8 @@ public class AIConfigurationScriptBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { + context.Files + .Add("/Views/Shared/Scripts/AILegalDisclaimer.js"); context.Files .Add("/Views/Shared/Components/AIConfiguration/Default.js"); } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js index 8527f3422b..fd9fbde05b 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/Default.js @@ -28,21 +28,32 @@ $(function () { } function handleSave() { + const automaticEnabled = UIElements.automaticCheckbox.is(':checked'); + const manualEnabled = UIElements.manualCheckbox.is(':checked'); + const turningOn = (automaticEnabled && !lastSavedAIValues.automaticallyGenerateAIAnalysis) || + (manualEnabled && !lastSavedAIValues.manuallyInitiateAIAnalysis); + + unity.aI.legalDisclaimer.confirmIfNeeded(turningOn, function () { + saveAiConfig(automaticEnabled, manualEnabled); + }); + } + + function saveAiConfig(automaticEnabled, manualEnabled) { UIElements.btnSave.prop('disabled', true); abp.ajax({ url: `/api/app/application-form/${UIElements.formId.val()}/ai-config`, type: 'PATCH', data: JSON.stringify({ - automaticallyGenerateAIAnalysis: UIElements.automaticCheckbox.is(':checked'), - manuallyInitiateAIAnalysis: UIElements.manualCheckbox.is(':checked') + automaticallyGenerateAIAnalysis: automaticEnabled, + manuallyInitiateAIAnalysis: manualEnabled }), contentType: 'application/json' }) .done(function () { lastSavedAIValues = { - automaticallyGenerateAIAnalysis: UIElements.automaticCheckbox.is(':checked'), - manuallyInitiateAIAnalysis: UIElements.manualCheckbox.is(':checked') + automaticallyGenerateAIAnalysis: automaticEnabled, + manuallyInitiateAIAnalysis: manualEnabled }; abp.notify.success('AI configuration saved successfully.'); }) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Scripts/AiLegalDisclaimer.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Scripts/AiLegalDisclaimer.js new file mode 100644 index 0000000000..6bdbe283ce --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Scripts/AiLegalDisclaimer.js @@ -0,0 +1,23 @@ +(function () { + window.unity = window.unity || {}; + unity.aI = unity.aI || {}; + + unity.aI.legalDisclaimer = { + confirmIfNeeded: function (turningOn, onConfirmed) { + if (!turningOn) { + onConfirmed(); + return; + } + + const modal = new abp.ModalManager({ + viewUrl: abp.appPath + 'Settings/LegalDisclaimerModal' + }); + + modal.onResult(function () { + onConfirmed(); + }); + + modal.open(); + } + }; +})(); From 8788800bbdedaa57b651d3af32a2a38710cab35d Mon Sep 17 00:00:00 2001 From: aurelio-aot Date: Thu, 13 Aug 2026 12:04:54 -0700 Subject: [PATCH 059/121] AB#33553: Wrong Tenant Error Page --- .../EmailNotificationService.cs | 12 ++++++++-- .../Localization/GrantManager/en.json | 7 +++++- .../Pages/Applicants/Details.cshtml.cs | 18 +++++++++++++++ .../Unity.GrantManager.Web/Pages/Error.cshtml | 21 +++++++++++++++++- .../Pages/Error.cshtml.cs | 6 ++++- .../Pages/GrantApplications/Details.cshtml.cs | 22 ++++++++++++++++++- 6 files changed, 80 insertions(+), 6 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs index f350797571..f5f7b3224d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs @@ -110,9 +110,17 @@ public async Task SendCommentNotification(EmailCommentDto i string commentLink = input.CommentType switch { Comments.CommentType.ApplicationComment or Comments.CommentType.AssessmentComment => - QueryHelpers.AddQueryString($"{baseUrl}/GrantApplications/Details", "ApplicationId", input.OwnerId), + QueryHelpers.AddQueryString($"{baseUrl}/GrantApplications/Details", new Dictionary + { + ["ApplicationId"] = input.OwnerId, + ["TenantId"] = CurrentTenant.Id?.ToString() + }), Comments.CommentType.ApplicantComment => - QueryHelpers.AddQueryString($"{baseUrl}/GrantApplicants/Details", "ApplicantId", input.OwnerId), + QueryHelpers.AddQueryString($"{baseUrl}/GrantApplicants/Details", new Dictionary + { + ["ApplicantId"] = input.OwnerId, + ["TenantId"] = CurrentTenant.Id?.ToString() + }), _ => throw new InvalidOperationException("Invalid comment type.") }; 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..843cb70ef7 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 @@ -616,6 +616,11 @@ "DataTable:ContextMenu:Copy": "Copy", "DataTable:ContextMenu:CopiedToClipboard": "Copied to clipboard", "DataTable:ContextMenu:Filter": "Filter", - "DataTable:ContextMenu:ClearFilter": "Clear Filters" + "DataTable:ContextMenu:ClearFilter": "Clear Filters", + + "WrongTenantError:Title": "An Error Occurred", + "WrongTenantError:ApplicationTenant": "This application is in Tenant: {0}", + "WrongTenantError:CurrentTenant": "You are currently in Tenant: {0}", + "WrongTenantError:Instructions": "Please click on the Profile menu at the top right of the corner, click on Switch Grant Programs, and select the correct Tenant before proceeding to view the link." } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Details.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Details.cshtml.cs index bd48ebec63..c3e28bf1d6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Details.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Applicants/Details.cshtml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.Permissions; +using Volo.Abp.TenantManagement; using Volo.Abp.Users; namespace Unity.GrantManager.Web.Pages.Applicants @@ -14,6 +15,7 @@ public class DetailsModel : GrantManagerPageModel { private readonly IApplicantRepository _applicantRepository; private readonly IApplicationRepository _applicationRepository; + private readonly ITenantRepository _tenantRepository; [BindProperty(SupportsGet = true)] public Guid ApplicantId { get; set; } @@ -21,6 +23,9 @@ public class DetailsModel : GrantManagerPageModel [BindProperty(SupportsGet = true)] public Guid? ApplicationId { get; set; } = null; + [BindProperty(SupportsGet = true)] + public Guid? TenantId { get; set; } + public Applicant? Applicant { get; set; } public bool ApplicantIsDeleted { get; set; } public string ApplicantDisplayName { get; set; } = string.Empty; @@ -34,11 +39,13 @@ public class DetailsModel : GrantManagerPageModel public DetailsModel( IApplicantRepository applicantRepository, IApplicationRepository applicationRepository, + ITenantRepository tenantRepository, ICurrentUser currentUser, IConfiguration configuration) { _applicantRepository = applicantRepository; _applicationRepository = applicationRepository; + _tenantRepository = tenantRepository; CurrentUserId = currentUser.Id; CurrentUserName = currentUser.SurName + ", " + currentUser.Name; AllowedFileTypes = configuration["S3:AllowedFileTypes"] ?? ""; @@ -47,6 +54,17 @@ public DetailsModel( public async Task OnGetAsync() { + if (TenantId.HasValue && TenantId.Value != CurrentTenant.Id) + { + var applicationTenant = await _tenantRepository.FindAsync(TenantId.Value); + return RedirectToPage("/Error", new + { + httpStatusCode = 409, + applicationTenantName = applicationTenant?.Name ?? TenantId.Value.ToString(), + currentTenantName = CurrentTenant.Name ?? "Host" + }); + } + // Resolve ApplicantId from ApplicationId if needed if (ApplicantId == Guid.Empty && ApplicationId.HasValue) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml index 80c2d7a984..952ad6afb9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml @@ -1,8 +1,12 @@ @page +@using Microsoft.Extensions.Localization +@using Unity.GrantManager.Localization @model Unity.GrantManager.Web.Pages.ErrorModel +@inject IStringLocalizer L @{ var code = Model.HttpStatusCode; + var isWrongTenant = code == 409; string title; string message; @@ -17,6 +21,10 @@ title = "Access Denied"; message = "You do not have permission to view this page."; break; + case 409: + title = L["WrongTenantError:Title"].Value; + message = string.Empty; + break; case 500: title = "Something Went Wrong"; message = "An unexpected error occurred. Please try again, or contact support if the problem persists."; @@ -31,6 +39,17 @@

@title

-

@message

+ @if (isWrongTenant) + { +

+ @L["WrongTenantError:ApplicationTenant", Model.ApplicationTenantName]
+ @L["WrongTenantError:CurrentTenant", Model.CurrentTenantName] +

+

@L["WrongTenantError:Instructions"]

+ } + else + { +

@message

+ }
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml.cs index 246f000a8d..aefc0dae49 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/Error.cshtml.cs @@ -5,9 +5,13 @@ namespace Unity.GrantManager.Web.Pages; public class ErrorModel : PageModel { public int HttpStatusCode { get; private set; } + public string? ApplicationTenantName { get; private set; } + public string? CurrentTenantName { get; private set; } - public void OnGet(int httpStatusCode = 0) + public void OnGet(int httpStatusCode = 0, string? applicationTenantName = null, string? currentTenantName = null) { HttpStatusCode = httpStatusCode;//HTTP Status Code + ApplicationTenantName = applicationTenantName; + CurrentTenantName = currentTenantName; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs index f01f08326a..223565f1e8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs @@ -21,6 +21,7 @@ using Unity.Modules.Shared.Specializations; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.Features; +using Volo.Abp.TenantManagement; using Volo.Abp.Users; namespace Unity.GrantManager.Web.Pages.GrantApplications @@ -33,6 +34,7 @@ public class DetailsModel : AbpPageModel private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; private readonly IScoresheetRepository _scoresheetRepository; private readonly IFeatureChecker _featureChecker; + private readonly ITenantRepository _tenantRepository; protected readonly IZoneManagementAppService _zoneManagementAppService; [BindProperty(SupportsGet = true)] @@ -48,6 +50,9 @@ public class DetailsModel : AbpPageModel [BindProperty(SupportsGet = true)] public Guid ApplicationId { get; set; } + [BindProperty(SupportsGet = true)] + public Guid? TenantId { get; set; } + [BindProperty(SupportsGet = true)] public Guid ApplicationFormVersionId { get; set; } @@ -94,6 +99,7 @@ public DetailsModel( IApplicationFormVersionAppService applicationFormVersionAppService, IScoresheetRepository scoresheetRepository, IFeatureChecker featureChecker, + ITenantRepository tenantRepository, ICurrentUser currentUser, IConfiguration configuration, IZoneManagementAppService zoneManagementAppService) @@ -103,6 +109,7 @@ public DetailsModel( _featureChecker = featureChecker; _applicationFormVersionAppService = applicationFormVersionAppService; _scoresheetRepository = scoresheetRepository; + _tenantRepository = tenantRepository; _zoneManagementAppService = zoneManagementAppService; CurrentUserId = currentUser.Id; @@ -113,8 +120,19 @@ public DetailsModel( TotalEmailAttachmentMaxFileSize = configuration["S3:EmailAttachmentsTotalMaxFileSize"] ?? "25"; } - public async Task OnGetAsync() + public async Task OnGetAsync() { + if (TenantId.HasValue && TenantId.Value != CurrentTenant.Id) + { + var applicationTenant = await _tenantRepository.FindAsync(TenantId.Value); + return RedirectToPage("/Error", new + { + httpStatusCode = 409, + applicationTenantName = applicationTenant?.Name ?? TenantId.Value.ToString(), + currentTenantName = CurrentTenant.Name ?? "Host" + }); + } + if (await _featureChecker.IsEnabledAsync(SpecializationConsts.Onboarding)) { ViewData["ActiveNavHref"] = "/TenantManagement/Onboarding"; @@ -152,6 +170,8 @@ public async Task OnGetAsync() ArgumentNullException.ThrowIfNull(applicationForm); ApplicationScoresheetSchemaJson = await GetApplicationScoresheetSchemaJsonAsync(applicationForm); ApplicationFormSubmissionData = applicationFormSubmission.Submission; + + return Page(); } public async Task OnPostAsync() From 19d81b6db20d4a7d6fd9f35033bf3bb0ef0f4fa7 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 12:19:35 -0700 Subject: [PATCH 060/121] AB#33821 - Fixed camelCase file refrence --- .../Views/Settings/AISettingGroup/AISettingViewComponent.cs | 2 +- .../Components/AIConfiguration/AIConfigurationViewComponent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs index 921df10f0f..c4bc8f063c 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs @@ -31,7 +31,7 @@ public class AISettingScriptBundleContributor : BundleContributor { public override void ConfigureBundle(BundleConfigurationContext context) { - context.Files.Add("/Views/Shared/Scripts/AILegalDisclaimer.js"); + context.Files.Add("/Views/Shared/Scripts/AiLegalDisclaimer.js"); context.Files.Add("/Views/Settings/AISettingGroup/Default.js"); } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs index 27eb12ee13..87e6120012 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Shared/Components/AIConfiguration/AIConfigurationViewComponent.cs @@ -38,7 +38,7 @@ public class AIConfigurationScriptBundleContributor : BundleContributor public override void ConfigureBundle(BundleConfigurationContext context) { context.Files - .Add("/Views/Shared/Scripts/AILegalDisclaimer.js"); + .Add("/Views/Shared/Scripts/AiLegalDisclaimer.js"); context.Files .Add("/Views/Shared/Components/AIConfiguration/Default.js"); } From 5584ae628bba5377586d351117421d099fffe8ac Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Thu, 13 Aug 2026 12:38:36 -0700 Subject: [PATCH 061/121] AB#34091 fix the reporting delete module overlay --- .../src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css index 1567fe83c5..870d90080c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/unity-styles.css @@ -367,11 +367,6 @@ thead input { background-color: var(--bc-colors-blue-background, #38598A); } -.abp-widget-wrapper { - top: 0; - z-index: 2; -} - td.dt-editable { cursor: pointer; } From 3b230f0a9bc0773c693f7c50a153670d6f9bad66 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:54:02 -0700 Subject: [PATCH 062/121] [AB#33234] Fix ExternalLink default in JSON column --- .../20260813164219_AB33234_ApplicantPortalExternalLinks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs index 4cad4aac8c..db8b26e47f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260813164219_AB33234_ApplicantPortalExternalLinks.cs @@ -22,7 +22,7 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "ApplicationForms", type: "jsonb", nullable: false, - defaultValue: "{}"); + defaultValue: "[]"); } /// From 17544ae86186851e4f30d7284fe8d26f31fc8350 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 13:36:11 -0700 Subject: [PATCH 063/121] AB#33815 - Added backend --- .../Settings/AITenantConfigurationDto.cs | 1 + .../Settings/UpdateAITenantConfigurationDto.cs | 1 + .../Settings/AIConfigurationAppService.cs | 10 +++++++++- .../Settings/AISettingDefinitionProvider.cs | 11 +++++++++++ .../Unity.AI.Domain.Shared/Localization/AI/en.json | 1 + .../src/Unity.AI.Domain.Shared/Settings/AISettings.cs | 1 + 6 files changed, 24 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AITenantConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AITenantConfigurationDto.cs index 10a0d84b3c..f1d60c7c42 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AITenantConfigurationDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/AITenantConfigurationDto.cs @@ -4,4 +4,5 @@ public class AITenantConfigurationDto { public bool AutomaticGenerationEnabled { get; set; } public bool ManualGenerationEnabled { get; set; } + public bool ReportingEnabled { get; set; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAITenantConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAITenantConfigurationDto.cs index bbbed5c2f0..cbe1979f56 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAITenantConfigurationDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Settings/UpdateAITenantConfigurationDto.cs @@ -4,4 +4,5 @@ public class UpdateAITenantConfigurationDto { public bool AutomaticGenerationEnabled { get; set; } public bool ManualGenerationEnabled { get; set; } + public bool ReportingEnabled { get; set; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs index f7bca0af75..0873375fa7 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AIConfigurationAppService.cs @@ -27,7 +27,9 @@ public virtual async Task GetTenantConfigurationAsync( AutomaticGenerationEnabled = await _settingProvider.GetAsync( AISettings.AutomaticGenerationEnabled, defaultValue: false), ManualGenerationEnabled = await _settingProvider.GetAsync( - AISettings.ManualGenerationEnabled, defaultValue: false) + AISettings.ManualGenerationEnabled, defaultValue: false), + ReportingEnabled = await _settingProvider.GetAsync( + AISettings.ReportingEnabled, defaultValue: false) }; } @@ -46,5 +48,11 @@ await _settingManager.SetAsync( input.ManualGenerationEnabled.ToString().ToLowerInvariant(), TenantSettingValueProvider.ProviderName, _currentTenant.Id?.ToString()); + + await _settingManager.SetAsync( + AISettings.ReportingEnabled, + input.ReportingEnabled.ToString().ToLowerInvariant(), + TenantSettingValueProvider.ProviderName, + _currentTenant.Id?.ToString()); } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs index 36a1914729..f7ff46d8dd 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Settings/AISettingDefinitionProvider.cs @@ -29,6 +29,17 @@ public override void Define(ISettingDefinitionContext context) isEncrypted: false) .WithProviders(TenantSettingValueProvider.ProviderName) ); + + context.Add( + new SettingDefinition( + AISettings.ReportingEnabled, + "false", + L("Setting:AI.ReportingEnabled"), + isVisibleToClients: false, + isInherited: false, + isEncrypted: false) + .WithProviders(TenantSettingValueProvider.ProviderName) + ); } private static LocalizableString L(string name) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json index 7d558af0f3..5be408c9d2 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json @@ -25,6 +25,7 @@ "Setting:AI.AutomaticGenerationEnabled": "Automatically Generate AI Analysis", "Setting:AI.ManualGenerationEnabled": "Manually Initiate AI Analysis", + "Setting:AI.ReportingEnabled": "AI Reporting", "LegalDisclaimer:Title": "Legal Disclaimer", "LegalDisclaimer:Body": "The Platform does not provide legal, financial, or policy advice, and may not be relied on to determine program eligibility or make final decisions in place of the user, who will be solely responsible for the use of the Platform, including the review and validation of Unity AI outputs before use.", diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs index d589a02c80..e7fdf2e0da 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Settings/AISettings.cs @@ -4,4 +4,5 @@ public static class AISettings { public const string AutomaticGenerationEnabled = "GrantManager.AI.AutomaticGenerationEnabled"; public const string ManualGenerationEnabled = "GrantManager.AI.ManualGenerationEnabled"; + public const string ReportingEnabled = "GrantManager.AI.ReportingEnabled"; } From 5605b37bb248d3a90b0dec3b4b0bccc9e1d0f9b0 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 13:39:05 -0700 Subject: [PATCH 064/121] AB#33815 - Added UI --- .../AISettingGroup/AISettingViewComponent.cs | 4 +++- .../AISettingGroup/AISettingViewModel.cs | 1 + .../Settings/AISettingGroup/Default.cshtml | 17 ++++++++++++++++- .../Views/Settings/AISettingGroup/Default.js | 17 +++++++++++------ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs index c4bc8f063c..efa1854d6f 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewComponent.cs @@ -21,7 +21,9 @@ public virtual async Task InvokeAsync() AutomaticGenerationEnabled = await settingProvider.GetAsync( AISettings.AutomaticGenerationEnabled, defaultValue: false), ManualGenerationEnabled = await settingProvider.GetAsync( - AISettings.ManualGenerationEnabled, defaultValue: false) + AISettings.ManualGenerationEnabled, defaultValue: false), + ReportingEnabled = await settingProvider.GetAsync( + AISettings.ReportingEnabled, defaultValue: false) }; return View("~/Views/Settings/AISettingGroup/Default.cshtml", model); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs index f7dfa675c4..e8fb44056a 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/AISettingViewModel.cs @@ -4,4 +4,5 @@ public class AISettingViewModel { public bool AutomaticGenerationEnabled { get; set; } public bool ManualGenerationEnabled { get; set; } + public bool ReportingEnabled { get; set; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml index 0a83cf001b..08ff8a60ab 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml @@ -25,7 +25,7 @@
-
+
+ +
+ + +
+ When enabled, users with the AI Reporting permission can access the AI Reporting dashboard. +
+
diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js index c30e3be2dc..9427a6fad3 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.js @@ -9,7 +9,8 @@ $(function () { let lastSavedValues = { automaticGenerationEnabled: $('#AutomaticGenerationEnabled').is(':checked'), - manualGenerationEnabled: $('#ManualGenerationEnabled').is(':checked') + manualGenerationEnabled: $('#ManualGenerationEnabled').is(':checked'), + reportingEnabled: $('#ReportingEnabled').is(':checked') }; function checkFormChanges() { @@ -18,14 +19,16 @@ $(function () { uiElements.discardButton.prop('disabled', !isFormChanged); } - function saveSettings(automaticEnabled, manualEnabled) { + function saveSettings(automaticEnabled, manualEnabled, reportingEnabled) { unity.aI.settings.aIConfiguration.updateTenantConfiguration({ automaticGenerationEnabled: automaticEnabled, - manualGenerationEnabled: manualEnabled + manualGenerationEnabled: manualEnabled, + reportingEnabled: reportingEnabled }).then(function () { lastSavedValues = { automaticGenerationEnabled: automaticEnabled, - manualGenerationEnabled: manualEnabled + manualGenerationEnabled: manualEnabled, + reportingEnabled: reportingEnabled }; $(document).trigger('AbpSettingSaved'); initialFormState = uiElements.settingForm.serialize(); @@ -42,11 +45,13 @@ $(function () { const automaticEnabled = $('#AutomaticGenerationEnabled').is(':checked'); const manualEnabled = $('#ManualGenerationEnabled').is(':checked'); + const reportingEnabled = $('#ReportingEnabled').is(':checked'); const turningOn = (automaticEnabled && !lastSavedValues.automaticGenerationEnabled) || - (manualEnabled && !lastSavedValues.manualGenerationEnabled); + (manualEnabled && !lastSavedValues.manualGenerationEnabled) || + (reportingEnabled && !lastSavedValues.reportingEnabled); unity.aI.legalDisclaimer.confirmIfNeeded(turningOn, function () { - saveSettings(automaticEnabled, manualEnabled); + saveSettings(automaticEnabled, manualEnabled, reportingEnabled); }); }); From c98fbbdc602df32ad37f97e90a431136e4b3d4e1 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 13:41:47 -0700 Subject: [PATCH 065/121] AB#33815 - Gating wiring --- .../src/Unity.AI.Web/Menus/AIMenuContributor.cs | 8 +++++++- .../src/Unity.AI.Web/Pages/AIReporting/Index.cshtml.cs | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs index deba87c054..c857ad4fe7 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs @@ -2,10 +2,12 @@ using Microsoft.Extensions.DependencyInjection; using Unity.AI.Localization; using Unity.AI.Permissions; +using Unity.AI.Settings; using Unity.Modules.Shared.Navigation; using Unity.Modules.Shared.Specializations; using Unity.Modules.Shared.Permissions; using Volo.Abp.Features; +using Volo.Abp.Settings; using Volo.Abp.UI.Navigation; namespace Unity.AI.Web.Menus; @@ -24,6 +26,7 @@ private static async Task ConfigureMainMenuAsync(MenuConfigurationContext contex { var l = context.GetLocalizer(); var featureChecker = context.ServiceProvider.GetRequiredService(); + var settingProvider = context.ServiceProvider.GetRequiredService(); var specializationChecker = context.ServiceProvider.GetRequiredService(); if (!await specializationChecker.IsEnabledAsync(SpecializationConsts.Onboarding)) @@ -37,7 +40,10 @@ await context.AddItemAsync(new ApplicationMenuItem( ).OnlyWhenInRole(IdentityConsts.ITOperationsRoleName)); } - if (await featureChecker.IsEnabledAsync("Unity.AIReporting")) + var reportingEnabled = await featureChecker.IsEnabledAsync("Unity.AIReporting") + && await settingProvider.GetAsync(AISettings.ReportingEnabled, defaultValue: false); + + if (reportingEnabled) { context.Menu.AddItem(new ApplicationMenuItem( name: AIMenus.Reporting, diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/AIReporting/Index.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/AIReporting/Index.cshtml.cs index 6d8259c9f6..3815419584 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/AIReporting/Index.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Pages/AIReporting/Index.cshtml.cs @@ -2,16 +2,19 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; +using Unity.AI.Settings; using Unity.GrantManager.Integrations; using Unity.Modules.Shared.Permissions; using Volo.Abp; using Volo.Abp.Features; +using Volo.Abp.Settings; namespace Unity.AI.Web.Pages.AIReporting { public class IndexModel( IEndpointManagementAppService endpointManagementAppService, IFeatureChecker featureChecker, + ISettingProvider settingProvider, IAuthorizationService authorizationService, ILogger logger) : PageModel { @@ -20,8 +23,11 @@ public class IndexModel( public async Task OnGetAsync() { - CanViewAiReporting = await featureChecker.IsEnabledAsync("Unity.AIReporting") - || (await authorizationService.AuthorizeAsync(User, IdentityConsts.ITAdminPolicyName)).Succeeded; + var isItAdmin = (await authorizationService.AuthorizeAsync(User, IdentityConsts.ITAdminPolicyName)).Succeeded; + var featureAndSettingEnabled = await featureChecker.IsEnabledAsync("Unity.AIReporting") + && await settingProvider.GetAsync(AISettings.ReportingEnabled, defaultValue: false); + + CanViewAiReporting = featureAndSettingEnabled || isItAdmin; if (!CanViewAiReporting) { From 453938aa5ea26c3bb0c6fc8e8e9e7a9742c89276 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 13:43:48 -0700 Subject: [PATCH 066/121] AB#33815 - Fixed AI Reporting permission gap --- .../Permissions/AIPermissionDefinitionProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs index 195bedf97b..73f5943f02 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Permissions/AIPermissionDefinitionProvider.cs @@ -95,7 +95,8 @@ public override void Define(IPermissionDefinitionContext context) "Unity.AI.ApplicationAnalysis", "Unity.AI.FormMapping", "Unity.AI.FormWorksheet", - "Unity.AI.FormScoresheet")); + "Unity.AI.FormScoresheet", + "Unity.AIReporting")); } private static LocalizableString L(string name) From 47e916eb000d687854301c55a4875b4c533729f3 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 13:54:29 -0700 Subject: [PATCH 067/121] AB#33815 - Localized AISettingGroup --- .../Localization/AI/en.json | 7 +++++++ .../Settings/AISettingGroup/Default.cshtml | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json index 5be408c9d2..94c371f52d 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Localization/AI/en.json @@ -23,9 +23,16 @@ "Permission:AI.Prompts.Delete": "Delete Prompts", "Menu:AIReporting": "AI Reporting", + "AISettingGroup:Title": "AI Configuration", + "AISettingGroup:SaveChanges": "Save Changes", + "AISettingGroup:DiscardChanges": "Discard Changes", + "Setting:AI.AutomaticGenerationEnabled": "Automatically Generate AI Analysis", + "Setting:AI.AutomaticGenerationEnabled:Description": "When enabled, AI analysis runs automatically on new application intake (subject to form-level AI configuration).", "Setting:AI.ManualGenerationEnabled": "Manually Initiate AI Analysis", + "Setting:AI.ManualGenerationEnabled:Description": "When enabled, users with appropriate permissions can manually generate or regenerate AI analysis.", "Setting:AI.ReportingEnabled": "AI Reporting", + "Setting:AI.ReportingEnabled:Description": "When enabled, users with the AI Reporting permission can access the AI Reporting dashboard.", "LegalDisclaimer:Title": "Legal Disclaimer", "LegalDisclaimer:Body": "The Platform does not provide legal, financial, or policy advice, and may not be relied on to determine program eligibility or make final decisions in place of the user, who will be solely responsible for the use of the Platform, including the review and validation of Unity AI outputs before use.", diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml index 08ff8a60ab..0db5e022dd 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Views/Settings/AISettingGroup/Default.cshtml @@ -1,8 +1,11 @@ +@using Microsoft.Extensions.Localization +@using Unity.AI.Localization @model Unity.AI.Web.Views.Settings.AISettingGroup.AISettingViewModel +@inject IStringLocalizer L
-

AI Configuration

+

@L["AISettingGroup:Title"]

@@ -18,10 +21,10 @@ value="true" @(Model.AutomaticGenerationEnabled ? "checked" : "") />
- When enabled, AI analysis runs automatically on new application intake (subject to form-level AI configuration). + @L["Setting:AI.AutomaticGenerationEnabled:Description"]
@@ -33,10 +36,10 @@ value="true" @(Model.ManualGenerationEnabled ? "checked" : "") />
- When enabled, users with appropriate permissions can manually generate or regenerate AI analysis. + @L["Setting:AI.ManualGenerationEnabled:Description"]
@@ -48,10 +51,10 @@ value="true" @(Model.ReportingEnabled ? "checked" : "") />
- When enabled, users with the AI Reporting permission can access the AI Reporting dashboard. + @L["Setting:AI.ReportingEnabled:Description"]
@@ -61,13 +64,13 @@
From 2b6aa723d4f0ba834d026b3c133da88ae827fb15 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 14:13:18 -0700 Subject: [PATCH 068/121] [AB#33234] Add UI for managing applicant portal external links --- .../ProfileData/ExternalLinkDto.cs | 2 - .../SubmissionInfoDataProvider.cs | 3 - .../Applications/ApplicationForm.cs | 1 - .../Default.cshtml | 126 ++++++++ .../ApplicationFormConfigWidget/Default.css | 21 ++ .../ApplicationFormConfigWidget/Default.js | 274 +++++++++++++++++- 6 files changed, 406 insertions(+), 21 deletions(-) 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 index b9e17dffc4..330d5121ed 100644 --- 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 @@ -8,7 +8,5 @@ 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/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicantProfile/DataProviders/SubmissionInfoDataProvider.cs index 47d6cf812b..6ced1a0a5d 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 @@ -75,7 +75,6 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id .Select(x => new ExternalLinkDto { Uri = x.Uri, - ExternalLinkType = x.ExternalLinkType, Order = x.Order, Title = x.Title, Description = x.Description @@ -84,11 +83,9 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id 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 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 c525c1c6d7..df416ff948 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -124,7 +124,6 @@ public ApplicationForm SetExternalLinks(ExternalLink? renewalLink, List
+
+
+
+
@L["ApplicationForms.Configuration:ApplicantPortalLinks"].Value
+
+
+ + + +
+
+ + + +
+
+
+
+ + +
+
+
+
+
+
+ + + +
+
+ +
+ +
+ @L["ApplicationForms.Configuration:OtherLinks"].Value +
+ + +
+ +
+ NOTE: @L["ApplicationForms.Configuration.Notes:LinkVisibilityRequiresUrl"].Value + NOTE: @L["ApplicationForms.Configuration.Notes:MaxRelatedLinks"].Value +
+
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.css index 4a9ddf92ef..f35b14fc2e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.css @@ -37,3 +37,24 @@ border-radius: 0.25rem; border: 1px solid #e9ecef; } + +.field-error { + display: block; + min-height: 1.1rem; + font-size: 0.8rem; +} + +.related-link-row { + align-items: flex-start; + padding-bottom: 0.25rem; +} + +.related-link-row .form-control.is-invalid, +#renewalLinkUri.is-invalid { + border-color: var(--lpx-danger); +} + +#btn-add-related-link:disabled { + opacity: 0.6; +} + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js index 1b72bac2d6..b6d0358e0d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js @@ -14,12 +14,234 @@ const cancelButton = document.getElementById('btn-cancel-other-config'); const backButton = document.getElementById('btn-back-other-config'); + const renewalLinkUri = document.getElementById('renewalLinkUri'); + const renewalLinkTitle = document.getElementById('renewalLinkTitle'); + const renewalLinkPublished = document.getElementById('renewalLinkPublished'); + const applicantMessage = document.getElementById('applicantMessage'); + const relatedLinksContainer = document.getElementById('relatedLinksContainer'); + const addRelatedLinkButton = document.getElementById('btn-add-related-link'); + + const MAX_RELATED_LINKS = 8; + const URL_PATTERN = /^https?:\/\/\S+$/i; + const EXTERNAL_LINK_TYPE_RENEWAL = 2; + const EXTERNAL_LINK_TYPE_RELATED = 1; + + const l = abp.localization.getResource('GrantManager'); + + function isValidUrl(value) { + return !!value && URL_PATTERN.test(value.trim()); + } + + function getFieldErrorElement(input) { + return input.parentElement.querySelector('.field-error'); + } + + function clearFieldError(input) { + const errorEl = getFieldErrorElement(input); + if (errorEl) { + errorEl.textContent = ''; + } + input.classList.remove('is-invalid'); + } + + function setFieldError(input, message) { + const errorEl = getFieldErrorElement(input); + if (errorEl) { + errorEl.textContent = message; + } + input.classList.add('is-invalid'); + } + + function collectRelatedLinkRows() { + return Array.from(relatedLinksContainer.querySelectorAll('.related-link-row')); + } + + function collectRelatedLinksSnapshot() { + return collectRelatedLinkRows().map(function (row) { + return { + uri: row.querySelector('.related-link-uri').value, + title: row.querySelector('.related-link-title').value, + description: row.querySelector('.related-link-description').value, + published: row.querySelector('.related-link-published').checked + }; + }); + } + + function updateAddButtonState() { + addRelatedLinkButton.disabled = collectRelatedLinkRows().length >= MAX_RELATED_LINKS; + } + + function createRelatedLinkRow(data) { + data = data || { uri: '', title: '', description: '', published: false }; + + const row = document.createElement('div'); + row.className = 'related-link-row row mt-2'; + + const uriCol = document.createElement('div'); + uriCol.className = 'col-12 col-md-4'; + const uriInput = document.createElement('input'); + uriInput.type = 'url'; + uriInput.className = 'form-control related-link-uri'; + uriInput.maxLength = 2048; + uriInput.placeholder = 'https://...'; + uriInput.value = data.uri; + const uriError = document.createElement('span'); + uriError.className = 'field-error text-danger small'; + uriCol.appendChild(uriInput); + uriCol.appendChild(uriError); + + const titleCol = document.createElement('div'); + titleCol.className = 'col-12 col-md-3'; + const titleInput = document.createElement('input'); + titleInput.type = 'text'; + titleInput.className = 'form-control related-link-title'; + titleInput.maxLength = 255; + titleInput.placeholder = l('ApplicationForms.Configuration:LinkDisplayName'); + titleInput.value = data.title; + titleCol.appendChild(titleInput); + + const descCol = document.createElement('div'); + descCol.className = 'col-12 col-md-3'; + const descInput = document.createElement('input'); + descInput.type = 'text'; + descInput.className = 'form-control related-link-description'; + descInput.maxLength = 512; + descInput.placeholder = l('ApplicationForms.Configuration:LinkDescription'); + descInput.value = data.description; + descCol.appendChild(descInput); + + const toggleCol = document.createElement('div'); + toggleCol.className = 'col-6 col-md-1 d-flex align-items-center'; + const switchWrapper = document.createElement('div'); + switchWrapper.className = 'form-check unt-form-switch form-switch'; + const toggleInput = document.createElement('input'); + toggleInput.type = 'checkbox'; + toggleInput.className = 'form-check-input related-link-published'; + toggleInput.style.cursor = 'pointer'; + toggleInput.checked = data.published; + switchWrapper.appendChild(toggleInput); + toggleCol.appendChild(switchWrapper); + + const removeCol = document.createElement('div'); + removeCol.className = 'col-6 col-md-1 d-flex align-items-center'; + const removeButton = document.createElement('button'); + removeButton.type = 'button'; + removeButton.className = 'btn btn-sm btn-outline-danger btn-remove-related-link'; + const removeIcon = document.createElement('i'); + removeIcon.className = 'fl fl-trash'; + removeButton.appendChild(removeIcon); + removeCol.appendChild(removeButton); + + row.appendChild(uriCol); + row.appendChild(titleCol); + row.appendChild(descCol); + row.appendChild(toggleCol); + row.appendChild(removeCol); + + removeButton.addEventListener('click', function () { + row.remove(); + updateAddButtonState(); + saveButton.disabled = false; + cancelButton.disabled = false; + }); + + return row; + } + + function rebuildRelatedLinkRows(links) { + relatedLinksContainer.innerHTML = ''; + links.forEach(function (link) { + relatedLinksContainer.appendChild(createRelatedLinkRow(link)); + }); + updateAddButtonState(); + } + + addRelatedLinkButton.addEventListener('click', function () { + if (collectRelatedLinkRows().length >= MAX_RELATED_LINKS) { + return; + } + relatedLinksContainer.appendChild(createRelatedLinkRow()); + updateAddButtonState(); + saveButton.disabled = false; + cancelButton.disabled = false; + }); + + updateAddButtonState(); + + function validateExternalLinksConfig() { + let isValid = true; + + clearFieldError(renewalLinkUri); + const renewalUriValue = renewalLinkUri.value.trim(); + if (renewalLinkPublished.checked && !isValidUrl(renewalUriValue)) { + setFieldError(renewalLinkUri, l('ApplicationForms.Configuration.Errors:RenewalLinkRequiredForVisibility')); + isValid = false; + } else if (renewalUriValue && !isValidUrl(renewalUriValue)) { + setFieldError(renewalLinkUri, l('ApplicationForms.Configuration.Errors:InvalidUrl')); + isValid = false; + } + + const rows = collectRelatedLinkRows(); + if (rows.length > MAX_RELATED_LINKS) { + abp.notify.error(l('ApplicationForms.Configuration.Errors:MaxRelatedLinksReached')); + isValid = false; + } + + rows.forEach(function (row) { + const uriInput = row.querySelector('.related-link-uri'); + const publishedInput = row.querySelector('.related-link-published'); + clearFieldError(uriInput); + const value = uriInput.value.trim(); + if (publishedInput.checked && !isValidUrl(value)) { + setFieldError(uriInput, l('ApplicationForms.Configuration.Errors:OtherLinkRequiredForVisibility')); + isValid = false; + } else if (value && !isValidUrl(value)) { + setFieldError(uriInput, l('ApplicationForms.Configuration.Errors:InvalidUrl')); + isValid = false; + } + }); + + return isValid; + } + + function buildExternalLinksConfigPayload() { + const renewalUriValue = renewalLinkUri.value.trim(); + + return { + renewalLink: renewalUriValue ? { + uri: renewalUriValue, + title: renewalLinkTitle.value, + description: applicantMessage.value, + published: renewalLinkPublished.checked, + externalLinkType: EXTERNAL_LINK_TYPE_RENEWAL, + order: 0 + } : null, + relatedLinks: collectRelatedLinkRows() + .map(function (row, index) { + return { + uri: row.querySelector('.related-link-uri').value.trim(), + title: row.querySelector('.related-link-title').value, + description: row.querySelector('.related-link-description').value, + published: row.querySelector('.related-link-published').checked, + externalLinkType: EXTERNAL_LINK_TYPE_RELATED, + order: index + }; + }) + .filter(function (link) { return link.uri; }) + }; + } + // Store last saved values let lastSavedValues = { directApproval: directApproval.checked, electoralDistrictAddressType: electoralDistrictAddressType.value, prefix: prefix.value, - suffixType: suffixType.value + suffixType: suffixType.value, + renewalLinkUri: renewalLinkUri.value, + renewalLinkTitle: renewalLinkTitle.value, + renewalLinkPublished: renewalLinkPublished.checked, + applicantMessage: applicantMessage.value, + relatedLinks: collectRelatedLinksSnapshot() }; // Initially disable the save and cancel buttons @@ -81,7 +303,13 @@ electoralDistrictAddressType.value = lastSavedValues.electoralDistrictAddressType; prefix.value = lastSavedValues.prefix; suffixType.value = lastSavedValues.suffixType; - + renewalLinkUri.value = lastSavedValues.renewalLinkUri; + renewalLinkTitle.value = lastSavedValues.renewalLinkTitle; + renewalLinkPublished.checked = lastSavedValues.renewalLinkPublished; + applicantMessage.value = lastSavedValues.applicantMessage; + rebuildRelatedLinkRows(lastSavedValues.relatedLinks); + clearFieldError(renewalLinkUri); + // Update preview after restoring values updateUnityIdPreview(); @@ -98,17 +326,15 @@ let isSaving = false; saveButton.addEventListener('click', function (event) { - console.log('Save button clicked'); - console.log(event); - console.log( - electoralDistrictAddressType.value, - prefix.value, - suffixType.value - ); if (isSaving || saveButton.disabled) { event.preventDefault(); return; } + + if (!validateExternalLinksConfig()) { + return; + } + isSaving = true; saveButton.disabled = true; // Disable immediately to prevent double click cancelButton.disabled = true; @@ -125,21 +351,39 @@ }), contentType: 'application/json', }) - .done(function () { - // Update last saved values after successful save + .then(function () { + // Only save external links config once other-config succeeds, + // keeping the two saves sequential. + return abp.ajax({ + url: `/api/app/application-form/${applicationFormId}/external-links-config`, + type: 'PATCH', + data: JSON.stringify(buildExternalLinksConfigPayload()), + contentType: 'application/json', + }); + }) + .then(function () { + // Clear dirty state only after both saves succeed. lastSavedValues = { directApproval: directApproval.checked, electoralDistrictAddressType: electoralDistrictAddressType.value, prefix: prefix.value, - suffixType: suffixType.value + suffixType: suffixType.value, + renewalLinkUri: renewalLinkUri.value, + renewalLinkTitle: renewalLinkTitle.value, + renewalLinkPublished: renewalLinkPublished.checked, + applicantMessage: applicantMessage.value, + relatedLinks: collectRelatedLinksSnapshot() }; abp.notify.success('Other configuration saved successfully.'); + resetFormState(); }) - .fail(function (error) { + .catch(function () { + // Keep the form dirty so the user can retry after a partial failure. abp.notify.error('Failed to save other configuration.'); + saveButton.disabled = false; + cancelButton.disabled = false; }) - .always(function () { - resetFormState(); + .then(function () { isSaving = false; }); }); From 02080a885ad37844e5ad178ed9dac680647cfe86 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Thu, 13 Aug 2026 14:16:06 -0700 Subject: [PATCH 069/121] AB#33815 - Fixed menu item visibility for ITAdmin --- .../Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs index c857ad4fe7..8da7b54593 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Menus/AIMenuContributor.cs @@ -9,6 +9,7 @@ using Volo.Abp.Features; using Volo.Abp.Settings; using Volo.Abp.UI.Navigation; +using Volo.Abp.Users; namespace Unity.AI.Web.Menus; @@ -40,10 +41,13 @@ await context.AddItemAsync(new ApplicationMenuItem( ).OnlyWhenInRole(IdentityConsts.ITOperationsRoleName)); } + var currentUser = context.ServiceProvider.GetRequiredService(); + var isItAdmin = currentUser.IsInRole(IdentityConsts.ITAdminRoleName); + var reportingEnabled = await featureChecker.IsEnabledAsync("Unity.AIReporting") && await settingProvider.GetAsync(AISettings.ReportingEnabled, defaultValue: false); - if (reportingEnabled) + if (reportingEnabled || isItAdmin) { context.Menu.AddItem(new ApplicationMenuItem( name: AIMenus.Reporting, From fe85aa2d9401497d6d29392e9096c3752d527d90 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:25:10 -0700 Subject: [PATCH 070/121] [AB#33234] Form Configuration UI Changes --- .../Components/ApplicationFormConfigWidget/Default.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml index 5518d02abe..cd45ff3b63 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml @@ -153,8 +153,8 @@ maxlength="255" /> -
-
+
+
-
+
- - - - - - - - -
+@page +@using Unity.GrantManager.ApplicationForms +@using Unity.Reporting.Permissions +@using Volo.Abp.AspNetCore.Mvc.UI.Layout; +@using Unity.GrantManager.Web.Pages.ApplicationForms; +@using Unity.GrantManager.Permissions; +@using Unity.Notifications.Permissions; +@using Unity.AI.Permissions; + +@using Volo.Abp.Authorization.Permissions; +@using Unity.GrantManager.Web.Views.Shared.Components.Notifications; +@using Unity.GrantManager.Web.Views.Shared.Components.ApplicationFormConfigWidget; +@using Volo.Abp.Features + +@model MappingModel +@inject IPageLayout PageLayout +@inject IPermissionChecker PermissionChecker +@inject IFeatureChecker FeatureChecker + +@{ + PageLayout.Content.MenuItemName = "GrantManager.ApplicationForms"; + PageLayout.Content.Title = "Application Mapping"; + ViewBag.PageTitle = "Application Forms Mapping"; + var mappingReviewModal = new AiSuggestionReviewModalModel + { + ModalId = "aiMappingReviewModal", + ModalLabelId = "aiMappingReviewModalLabel", + Title = "Review AI Mapping Suggestions", + SourceColumnTitle = "CHEFS Field", + TargetColumnTitle = "Unity Core Field", + FieldsId = "aiMappingReviewFields", + EmptyId = "aiMappingReviewEmpty", + EmptyText = "No mapping suggestions remain.", + SelectAllId = "aiMappingReviewSelectAll", + PrimaryActionId = "btn-add-ai-mapping", + PrimaryActionText = "Add selected to map", + ReviewLaterActionId = "btn-review-later-ai-mapping", + DiscardActionId = "btn-discard-ai-mapping" + }; + var worksheetReviewModal = new AiSuggestionReviewModalModel + { + ModalId = "aiWorksheetReviewModal", + ModalLabelId = "aiWorksheetReviewModalLabel", + Title = "Create Worksheet Draft", + SourceColumnTitle = "Source Field", + TargetColumnTitle = "Worksheet Field", + FieldsId = "aiWorksheetReviewFields", + EmptyId = "aiWorksheetReviewEmpty", + EmptyText = "No custom fields were suggested.", + SelectAllId = "aiWorksheetReviewSelectAll", + TitleInputId = "aiWorksheetTitle", + TitleInputLabel = "Worksheet Title", + TitleInputPlaceholder = "e.g., Project details", + PrimaryActionId = "btn-create-ai-worksheet-draft", + PrimaryActionText = "Create Draft", + PrimaryActionDisabled = true, + ReviewLaterActionId = "btn-review-later-ai-worksheet", + DiscardActionId = "btn-discard-ai-worksheet" + }; +} +@section scripts +{ + + + + + + +} + +@section styles { + + +} + + + + + + + + + + +
+
+
+
@Model.ApplicationFormDto?.ApplicationFormName
+
+
+ + + + + + + + + + + + + + + + +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css index 0484f631b8..6a1f0cdb2b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css @@ -1,397 +1,397 @@ -.col { - min-height: 40px; - border: 1px solid #6a6a6a; - border-radius: 10px; - overflow-y: scroll; - background-color: white; -} - -.unt-tab-content .form-select { - width: auto; -} - -#nav-tabContent.unt-tab-content { - display: flex; - flex-direction: column; - height: calc(100vh - 230px); - min-height: 0; -} - -#nav-tabContent.unt-tab-content > .tab-pane.show.active { - display: flex; - flex: 1 1 auto; - flex-direction: column; - min-height: 0; -} - -.configuration-action-bar { - align-items: center; - border-bottom: 1px solid #dee2e6; - padding-bottom: 0.5rem; - margin-bottom: 0.5rem; -} - -.ai-button-content { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.ai-generate-btn { - height: 2.25rem; -} - - .configuration-action-bar h5 { - font-weight: 700; - } - - .configuration-action-bar .btn, .configuration-action-bar .btn-group { - flex: none; - min-width: 100px; - padding: 0.375rem 0.75rem; - font-weight: 500; - transition: all 0.15s ease-in-out; - justify-content: center !important; - } - - .configuration-action-bar .btn-wrapper { - gap: 0.5rem; - display: flex; - align-items: center; - justify-content: flex-end !important; - padding-right: 0; - } - - .column { - float: left; - width: 40%; - } - - /* Clear floats after the columns */ - .row:after { - content: ""; - display: table; - clear: both; - } - -.card { - display: flex; - align-items: center; - justify-content: center; - border-radius: 10px; - width: fit-content; - padding: 2px; - padding-right: 6px; - padding-left: 6px; - margin: 4px; - text-align: center; - font-size: 16px; - cursor: grab; - background-image: linear-gradient(135deg, #f9f9fb 10%, #38598A 900%); -} - -.dragging { - opacity: 0.5; -} - -.chef-field-table { - overflow-y:scroll; - border: 1px solid #00000017; -} - -caption { - padding: 10px; - padding-left: 2px; - position: fixed; - margin-bottom: 30px; - scroll-behavior: revert; - caption-side: top; - margin-top: -92px; - font-weight: 400; - width: unset; - font-size: 20px; - color: var(--bc-colors-blue-background, #38598A); - background-color: white; -} - -.title { - padding: 10px; - padding-left: 0px; - font-weight: 400; - font-size: 20px; - margin-bottom: 0px; - margin-top: -8px; - color: var(--bc-colors-blue-background, #38598A); -} - -table.dataTable thead th { - background-color: #55698A !important; - color: white; - font-weight: 500; - font-size: 18px; -} - -table.dataTable td { - word-wrap: break-word; - max-width: 250px; - font-size: 15px !important; -} - -.odd { - background-color: #efefef !important; -} - -td { - background-color: transparent !important; -} -tr:nth-child(even) {background-color: #f2f2f2;} - -.content-div { - width: 100%; - height: auto; - margin: 0 auto; - position: relative; -} - -.buttons { - width: fit-content; - display: block; -} - -.ai-generate-btn:disabled, -.ai-generate-btn.disabled, -.ai-generate-btn[data-ai-shared-generating='1'], -.ai-generate-btn[data-ai-cooldown-active='1'], -.ai-generate-btn[data-ai-cooldown-checking='1'] { - cursor: not-allowed; - opacity: 0.55; - pointer-events: none; -} - -.buttons-div { - display: inline-flex; - padding: 20px; - margin: auto; -} - -.dataTables_filter, .dt-search { - margin-right: 10px; -} - -.mappingForm { - margin-bottom: 20px; -} - -.label { - padding: 4px; - white-space: nowrap; - color: var(--bc-colors-blue-background, #38598A); -} - -.toast-top-center { - top: 220px; - margin: 0 auto; - left: 50%; - margin-left: -450px; -} - -thead tr:first-child th { - position: relative; - z-index: 12; - opacity: 1; -} - -.table-title { - background-color: white; - font-size: 20px; - padding: 4px; - z-index: 23; - margin-left: 1px; - position: fixed; - float: left; -} - -table.dataTable thead td { - font-size: 14px; -} - -#intake-map-available-fields-column { - width: 96%; -} - -.intake-mapping-title { - position: static; - float: left; - margin-top: 0px; - padding: 7px; - padding-top: 0px; - width: -webkit-fill-available; -} - -div.row:first-child th { - position: sticky; - z-index: 12; - top: 50px; - opacity: 1; -} - -div.dataTables_wrapper div.dataTables_filter, -div.dataTables_wrapper div.dt-search { - text-align: right; - z-index: 2; - position: sticky; - background: white; - width: 100%; - margin-left: -156px; - padding: 3px; -} - -.col-md-6 { - flex: 0 0 auto; - width: calc(65% - (.5em + 6px)); - z-index: 22; - position: fixed; - overflow: hidden; - margin-bottom: 20px; - margin-top: -13px; - margin-left: 7px; -} - -.form-version-label { - width: 400px; - margin-left: -74px; -} - -textarea { - border: 1px solid #e9e9e9; -} - -.display-modal { - opacity: 1 !important; - display: block !important; -} - -.non-drag { - -webkit-user-drag: none; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; -} - -.dtfh-floatingparenthead { - position: relative !important; -} - -.mapping-field { - display: block; - font-size: 0.9rem; -} - -.mapping-field i { - font-size: 0.7rem; -} - -.mapping-indicator-text { - font-size: 0.5rem; - text-align: center; - vertical-align: middle; - margin-right: 2px; -} - -.published-tick { - background-color: var(--bs-btn-bg, #198754); - border-radius: 35px; - padding: 7px; - color: #fff; - width: 35px; - height: 35px; - margin-left: 5px; - padding-left: 9px; -} - -.select-icon-wrapper { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - align-items: center; -} - -.form-label { - color: var(--bc-colors-blue-background, #38598A); - margin-bottom: 0.1rem; -} - -.input-width { - width: 355px !important; -} - -.input-button-wrapper { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - align-items: center; - gap: 5px; -} - -.unt-tab-content { - background:#FFF; - padding:15px; -} - -.unt-label { - padding: 10px 0px; -} - -.unt-mapping-container { - height: calc(97vh - 400px); - overflow: scroll; - overflow-x: hidden; - border: 1px solid black; -} - -.custom-fields-container { - height: calc(97vh - 400px); - overflow: scroll; - overflow-x: hidden; -} - -.nav-tabs .nav-link.active { - color: #003366; - border-bottom: 3px solid #003366; -} - -.nav-tabs .nav-link { - background: #fff; - border-bottom: 1px solid #dee2e6; - padding: 10px 20px; -} - -.intake-mapping-content { - height: calc(100vh - 200px); - overflow: auto; -} - -.application-forms-table-content { - height: calc(100vh - 200px); - overflow: auto; - background: #fff; -} - -.application-forms-table-content .table.dataTable { - margin-top:0px !important; -} -.note{ - font-size: 0.75rem; -} -.direct-approval { - font-size: 0.9rem; -} - -.worksheet-mapping-content { - height: calc(100vh - 250px); - overflow: auto; -} +.col { + min-height: 40px; + border: 1px solid #6a6a6a; + border-radius: 10px; + overflow-y: scroll; + background-color: white; +} + +.unt-tab-content .form-select { + width: auto; +} + +#nav-tabContent.unt-tab-content { + display: flex; + flex-direction: column; + height: calc(100vh - 230px); + min-height: 0; +} + +#nav-tabContent.unt-tab-content > .tab-pane.show.active { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; +} + +.configuration-action-bar { + align-items: center; + border-bottom: 1px solid #dee2e6; + padding-bottom: 0.5rem; + margin-bottom: 0.5rem; +} + +.ai-button-content { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.ai-generate-btn { + height: 2.25rem; +} + + .configuration-action-bar h5 { + font-weight: 700; + } + + .configuration-action-bar .btn, .configuration-action-bar .btn-group { + flex: none; + min-width: 100px; + padding: 0.375rem 0.75rem; + font-weight: 500; + transition: all 0.15s ease-in-out; + justify-content: center !important; + } + + .configuration-action-bar .btn-wrapper { + gap: 0.5rem; + display: flex; + align-items: center; + justify-content: flex-end !important; + padding-right: 0; + } + + .column { + float: left; + width: 40%; + } + + /* Clear floats after the columns */ + .row:after { + content: ""; + display: table; + clear: both; + } + +.card { + display: flex; + align-items: center; + justify-content: center; + border-radius: 10px; + width: fit-content; + padding: 2px; + padding-right: 6px; + padding-left: 6px; + margin: 4px; + text-align: center; + font-size: 16px; + cursor: grab; + background-image: linear-gradient(135deg, #f9f9fb 10%, #38598A 900%); +} + +.dragging { + opacity: 0.5; +} + +.chef-field-table { + overflow-y:scroll; + border: 1px solid #00000017; +} + +caption { + padding: 10px; + padding-left: 2px; + position: fixed; + margin-bottom: 30px; + scroll-behavior: revert; + caption-side: top; + margin-top: -92px; + font-weight: 400; + width: unset; + font-size: 20px; + color: var(--bc-colors-blue-background, #38598A); + background-color: white; +} + +.title { + padding: 10px; + padding-left: 0px; + font-weight: 400; + font-size: 20px; + margin-bottom: 0px; + margin-top: -8px; + color: var(--bc-colors-blue-background, #38598A); +} + +table.dataTable thead th { + background-color: #55698A !important; + color: white; + font-weight: 500; + font-size: 18px; +} + +table.dataTable td { + word-wrap: break-word; + max-width: 250px; + font-size: 15px !important; +} + +.odd { + background-color: #efefef !important; +} + +td { + background-color: transparent !important; +} +tr:nth-child(even) {background-color: #f2f2f2;} + +.content-div { + width: 100%; + height: auto; + margin: 0 auto; + position: relative; +} + +.buttons { + width: fit-content; + display: block; +} + +.ai-generate-btn:disabled, +.ai-generate-btn.disabled, +.ai-generate-btn[data-ai-shared-generating='1'], +.ai-generate-btn[data-ai-cooldown-active='1'], +.ai-generate-btn[data-ai-cooldown-checking='1'] { + cursor: not-allowed; + opacity: 0.55; + pointer-events: none; +} + +.buttons-div { + display: inline-flex; + padding: 20px; + margin: auto; +} + +.dataTables_filter, .dt-search { + margin-right: 10px; +} + +.mappingForm { + margin-bottom: 20px; +} + +.label { + padding: 4px; + white-space: nowrap; + color: var(--bc-colors-blue-background, #38598A); +} + +.toast-top-center { + top: 220px; + margin: 0 auto; + left: 50%; + margin-left: -450px; +} + +thead tr:first-child th { + position: relative; + z-index: 12; + opacity: 1; +} + +.table-title { + background-color: white; + font-size: 20px; + padding: 4px; + z-index: 23; + margin-left: 1px; + position: fixed; + float: left; +} + +table.dataTable thead td { + font-size: 14px; +} + +#intake-map-available-fields-column { + width: 96%; +} + +.intake-mapping-title { + position: static; + float: left; + margin-top: 0px; + padding: 7px; + padding-top: 0px; + width: -webkit-fill-available; +} + +div.row:first-child th { + position: sticky; + z-index: 12; + top: 50px; + opacity: 1; +} + +div.dataTables_wrapper div.dataTables_filter, +div.dataTables_wrapper div.dt-search { + text-align: right; + z-index: 2; + position: sticky; + background: white; + width: 100%; + margin-left: -156px; + padding: 3px; +} + +.col-md-6 { + flex: 0 0 auto; + width: calc(65% - (.5em + 6px)); + z-index: 22; + position: fixed; + overflow: hidden; + margin-bottom: 20px; + margin-top: -13px; + margin-left: 7px; +} + +.form-version-label { + width: 400px; + margin-left: -74px; +} + +textarea { + border: 1px solid #e9e9e9; +} + +.display-modal { + opacity: 1 !important; + display: block !important; +} + +.non-drag { + -webkit-user-drag: none; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + +.dtfh-floatingparenthead { + position: relative !important; +} + +.mapping-field { + display: block; + font-size: 0.9rem; +} + +.mapping-field i { + font-size: 0.7rem; +} + +.mapping-indicator-text { + font-size: 0.5rem; + text-align: center; + vertical-align: middle; + margin-right: 2px; +} + +.published-tick { + background-color: var(--bs-btn-bg, #198754); + border-radius: 35px; + padding: 7px; + color: #fff; + width: 35px; + height: 35px; + margin-left: 5px; + padding-left: 9px; +} + +.select-icon-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; +} + +.form-label { + color: var(--bc-colors-blue-background, #38598A); + margin-bottom: 0.1rem; +} + +.input-width { + width: 355px !important; +} + +.input-button-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; + gap: 5px; +} + +.unt-tab-content { + background:#FFF; + padding:15px; +} + +.unt-label { + padding: 10px 0px; +} + +.unt-mapping-container { + height: calc(97vh - 400px); + overflow: scroll; + overflow-x: hidden; + border: 1px solid black; +} + +.custom-fields-container { + height: calc(97vh - 400px); + overflow: scroll; + overflow-x: hidden; +} + +.nav-tabs .nav-link.active { + color: #003366; + border-bottom: 3px solid #003366; +} + +.nav-tabs .nav-link { + background: #fff; + border-bottom: 1px solid #dee2e6; + padding: 10px 20px; +} + +.intake-mapping-content { + height: calc(100vh - 200px); + overflow: auto; +} + +.application-forms-table-content { + height: calc(100vh - 200px); + overflow: auto; + background: #fff; +} + +.application-forms-table-content .table.dataTable { + margin-top:0px !important; +} +.note{ + font-size: 0.75rem; +} +.direct-approval { + font-size: 0.9rem; +} + +.worksheet-mapping-content { + height: calc(100vh - 250px); + overflow: auto; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index fb41646a66..8c852776fa 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -1,1377 +1,1377 @@ -$(function () { - let availableChefFieldsString = document.getElementById('availableChefsFields').value; - let existingMappingString = document.getElementById('existingMapping').value; - let intakeFieldsString = document.getElementById('intakeProperties').value; - let chefsFormId = document.getElementById('chefsFormId').value; - let formVersionId = document.getElementById('formVersionId').value; - let intakeMapColumn = document.querySelector('#intake-map-available-fields-column'); - let worksheetMapColumn = document.querySelector('#worksheet-map-available-fields-column'); - let excludedIntakeMappings = new Set(['ConfirmationId', 'SubmissionId', 'SubmissionDate']); - let dataTable; - - let allowableTypes = new Set(['textarea', - 'orgbook', - 'textfield', - 'currency', - 'datetime', - 'checkbox', - 'select', - 'selectboxes', - 'radio', - 'phoneNumber', - 'email', - 'number', - 'time', - 'day', - 'hidden', - 'simpletextfield', - 'simpletextfieldadvanced', - 'simpletime', - 'simpletimeadvanced', - 'simplenumber', - 'simplenumberadvanced', - 'simplephonenumber', - 'simplephonenumberadvanced', - 'simpleselect', - 'simpleselectadvanced', - 'simpleday', - 'simpledayadvanced', - 'simpleemail', - 'simpleemailadvanced', - 'simpledatetime', - 'simpledatetimeadvanced', - 'simpleurladvanced', - 'simplecheckbox', - 'simpleradios', - 'simpleradioadvanced', - 'simplecheckboxes', - 'simplecheckboxadvanced', - 'simplecurrencyadvanced', - 'simpletextarea', - 'simpletextareaadvanced', - 'bcaddress', - 'datagrid']); - - const UIElements = { - btnBack: $('#btn-back'), - btnSave: $('#btn-save'), - btnEdit: $('#btn-edit'), - btnGenerate: $('#btn-generate'), - btnReviewMapping: $('#btn-review-mapping'), - btnGenerateWorksheet: $('#btn-generate-worksheet'), - btnGenerateScoresheet: $('#btn-generate-scoresheet'), - btnReviewWorksheet: $('#btn-review-worksheet'), - btnPublishAssignWorksheets: $('#btn-publish-assign-worksheets'), - btnGenerateFinalMapping: $('#btn-generate-final-mapping'), - btnReviewFinalMapping: $('#btn-review-final-mapping'), - btnGenerateNextMapping: $('#btn-generate-next-mapping'), - btnGenerateNextWorksheets: $('#btn-generate-next-worksheets'), - btnRestartAiFlow: $('#btn-restart-ai-flow'), - worksheetReviewModal: $('#aiWorksheetReviewModal'), - mappingReviewModal: $('#aiMappingReviewModal'), - mappingReviewFields: $('#aiMappingReviewFields'), - mappingReviewEmpty: $('#aiMappingReviewEmpty'), - mappingReviewSelectAll: $('#aiMappingReviewSelectAll'), - btnAddMapping: $('#btn-add-ai-mapping'), - btnReviewLaterMapping: $('#btn-review-later-ai-mapping'), - btnDiscardMapping: $('#btn-discard-ai-mapping'), - worksheetReviewFields: $('#aiWorksheetReviewFields'), - worksheetReviewEmpty: $('#aiWorksheetReviewEmpty'), - worksheetTitle: $('#aiWorksheetTitle'), - btnCreateWorksheetDraft: $('#btn-create-ai-worksheet-draft'), - btnDiscardWorksheet: $('#btn-discard-ai-worksheet'), - btnSync: $('#btn-sync'), - btnReset: $('#btn-reset'), - btnClose: $('.btn-close'), - btnSaveMapping: $('#btn-save-mapping'), - btnCancel: $('#btn-cancel-mapping'), - inputSearchBar: $('#search-bar'), - selectVersionList: $('#applicationFormVersion'), - editMappingModal: $('#editMappingModal'), - uiConfigurationTab: $('#nav-ui-configuration'), - mappingTab: $('#nav-mapping-tab'), - customFieldsTab: $('#nav-worksheet-fields-tab'), - intakeFieldsTab: $('#nav-intake-fields-tab'), - refreshAvailableWorksheetsHidden: $('#refresh_available_worksheets') - }; - - init(); - - function init() { - bindUIEvents(); - restoreActiveTab(); - dataTable = initializeApplicationFormsTable(); - let availableChefsFields = availableChefFieldsString ? JSON.parse(availableChefFieldsString) : [] - initializeIntakeMap(availableChefsFields); - bindExistingMaps(); - setupTooltips(); - initializeUIConfiguration(); - loadMappingReview(false); - } - - function setupTooltips() { - $('[data-toggle="tooltip"]').tooltip({ - placement: 'top' - }); - } - - function startWorksheetPhase(callback) { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, - type: 'POST' - }).done(callback).fail(function () { - abp.notify.error('', 'Unable to start worksheet generation.'); - }); - } - - function bindUIEvents() { - UIElements.btnBack.on('click', handleBack); - UIElements.btnSave.on('click', handleSave); - UIElements.btnSaveMapping.on('click', handleSaveEditMapping); - UIElements.btnSync.on('click', handleSync); - UIElements.btnEdit.on('click', handleEdit); - UIElements.btnGenerate.on('click', queueFormMapping); - UIElements.btnReviewMapping.on('click', function () { - loadMappingReview(true); - }); - UIElements.btnReviewFinalMapping.on('click', function () { - loadMappingReview(true); - }); - UIElements.btnGenerateWorksheet.on('click', queueFormWorksheet); - UIElements.btnGenerateFinalMapping.on('click', finalizeMappingReview); - UIElements.btnGenerateNextMapping.on('click', queueFormMapping); - UIElements.btnGenerateNextWorksheets.on('click', queueFormWorksheet); - UIElements.btnRestartAiFlow.on('click', restartAiFlow); - UIElements.btnGenerateScoresheet.on('click', queueFormScoresheet); - UIElements.btnReviewWorksheet.on('click', loadAiWorksheetReview); - UIElements.btnAddMapping.on('click', addSelectedMappingSuggestion); - UIElements.btnReviewLaterMapping.on('click', function () { - UIElements.mappingReviewModal.modal('hide'); - }); - UIElements.btnDiscardMapping.on('click', discardMappingSuggestions); - UIElements.mappingReviewFields.on('change', 'input[data-suggestion-id]', updateMappingReviewSelection); - UIElements.mappingReviewSelectAll.on('change', toggleMappingReviewAll); - UIElements.btnCreateWorksheetDraft.on('click', createAiWorksheetDraft); - UIElements.btnDiscardWorksheet.on('click', discardAiWorksheetSuggestions); - UIElements.worksheetReviewFields.on('change', 'input[data-field-id]', updateAiWorksheetReview); - $('#aiWorksheetReviewSelectAll').on('change', toggleAiWorksheetReviewAll); - UIElements.worksheetTitle.on('input', updateAiWorksheetDraftButton); - UIElements.btnReset.on('click', handleReset); - UIElements.btnCancel.on('click', handleCancelMapping); - UIElements.btnClose.on('click', handleCancelMapping); - UIElements.inputSearchBar.on('keyup', handleSeearchBar); - UIElements.selectVersionList.on('change', handleSelectVersion); - UIElements.mappingTab.on('click', handleMappingTabClick); - - // Persist active tab to localStorage on switch - $('#nav-tab').on('shown.bs.tab', 'button[data-bs-toggle="tab"]', function () { - const formId = document.getElementById('applicationFormId')?.value; - if (formId) { - localStorage.setItem('mapping-active-tab:' + formId, this.id); - } - }); - } - - function restoreActiveTab() { - const formId = document.getElementById('applicationFormId')?.value; - if (!formId) return; - const savedTabId = localStorage.getItem('mapping-active-tab:' + formId); - if (!savedTabId) return; - const tabEl = document.getElementById(savedTabId); - if (tabEl) { - bootstrap.Tab.getOrCreateInstance(tabEl).show(); - } - } - - function initializeUIConfiguration() { - const providerName = 'F'; - const providerKey = $('#applicationFormId').val(); - const providerKeyDisplayName = 'Test.Display.Name'; - - $.ajax({ - url: abp.appPath + 'SettingManagement/ZoneManagement', - type: 'GET', - data: { - providerName: providerName, - providerKey: providerKey, - providerKeyDisplayName: providerKeyDisplayName - }, - success: function (response) { - UIElements.uiConfigurationTab.html(response); - }, - error: function () { - abp.notify.error('Failed to load UI Configuration.'); - } - }); - } - - function handleEdit() { - $('#jsonText').val(prettyJson(existingMappingString)); - UIElements.editMappingModal.addClass('display-modal'); - } - - function queueFormMapping(triggerButton = null) { - if (UIElements.btnGenerate.attr('data-ai-pending') === 'true') { - loadMappingReview(true); - return; - } - - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - if (!validateGuid(formVersion)) { - abp.notify.error('', 'The Form Version ID is not in a GUID format'); - return; - } - if (!validateGuid(applicationId)) { - abp.notify.error('', 'The Application ID is not in a GUID format'); - return; - } - - const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerate?.get?.(0); - const $button = $(buttonElement); - const existingHtml = $button.html(); - - if ($button.prop('disabled')) { - return; - } - - globalThis.AIGenerationButtonState?.setGenerating($button); - - abp.ajax({ - url: `/api/app/ai/generation/form-mapping?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) - .done(function (generationStatus) { - const request = generationStatus?.generationRequest; - const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; - - if (status === 'Completed') { - globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); - globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); - refreshMappingAfterGeneration(applicationId, formVersion); - return; - } - - monitorFormMappingGeneration(applicationId, $button, existingHtml); - }) - .fail(function (error) { - if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { - return; - } - - abp.message.error('Failed to queue AI mapping generation. Please try again.'); - restoreGenerateMappingButton($button, existingHtml); - globalThis.syncAIRateLimitButtons?.(); - }); - } - - function queueFormWorksheet(triggerButton = null) { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - if (!validateGuid(formVersion) || !validateGuid(applicationId)) { - abp.notify.error('', 'The Form Version ID or Application ID is not in a GUID format'); - return; - } - - const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateWorksheet?.get?.(0); - const $button = $(buttonElement); - - if (isAiWorksheetPending()) { - loadAiWorksheetReview(); - return; - } - - startWorksheetPhase(function () { - queueFormWorksheetCore(triggerButton); - }); - } - - function queueFormWorksheetCore(triggerButton = null) { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateWorksheet?.get?.(0); - const $button = $(buttonElement); - const existingHtml = $button.html(); - - if ($button.prop('disabled')) { - return; - } - - globalThis.AIGenerationButtonState?.setGenerating($button); - - abp.ajax({ - url: `/api/app/ai/generation/form-worksheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) - .done(function (generationStatus) { - const request = generationStatus?.generationRequest; - const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; - if (status === 'Completed') { - globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); - globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); - refreshWorksheetAfterGeneration(); - return; - } - - monitorFormWorksheetGeneration(applicationId, $button, existingHtml); - }) - .fail(function (error) { - if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { - return; - } - - abp.message.error('Failed to queue AI worksheet generation. Please try again.'); - restoreGenerateWorksheetButton($button, existingHtml); - globalThis.syncAIRateLimitButtons?.(); - }); - } - - function monitorFormWorksheetGeneration(applicationId, $button, existingHtml) { - globalThis.AIGenerationButtonState?.monitor({ - $button, - originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-worksheet`, - type: 'GET' - }), - onComplete: function () { - refreshWorksheetAfterGeneration(); - }, - onFailed: function (request) { - abp.message.error(request?.failureReason || 'AI worksheet generation failed.'); - }, - onPollFailed: function () { - abp.message.error('Unable to load AI worksheet generation status. Please try again.'); - } - }); - } - - function queueFormScoresheet(triggerButton = null) { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - if (!validateGuid(formVersion) || !validateGuid(applicationId)) { - abp.notify.error('', 'The Form Version ID or Application ID is not in a GUID format'); - return; - } - - const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateScoresheet?.get?.(0); - const $button = $(buttonElement); - const existingHtml = $button.html(); - - if ($button.prop('disabled')) { - return; - } - - globalThis.AIGenerationButtonState?.setGenerating($button); - - abp.ajax({ - url: `/api/app/ai/generation/form-scoresheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) - .done(function (generationStatus) { - const request = generationStatus?.generationRequest; - const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; - if (status === 'Completed') { - globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); - globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); - refreshScoresheetAfterGeneration(); - return; - } - - monitorFormScoresheetGeneration(applicationId, $button, existingHtml); - }) - .fail(function (error) { - if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { - return; - } - - abp.message.error('Failed to queue AI scoresheet generation. Please try again.'); - restoreGenerateScoresheetButton($button, existingHtml); - globalThis.syncAIRateLimitButtons?.(); - }); - } - - function monitorFormScoresheetGeneration(applicationId, $button, existingHtml) { - globalThis.AIGenerationButtonState?.monitor({ - $button, - originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-scoresheet`, - type: 'GET' - }), - onComplete: function () { - refreshScoresheetAfterGeneration(); - }, - onFailed: function (request) { - abp.message.error(request?.failureReason || 'AI scoresheet generation failed.'); - }, - onPollFailed: function () { - abp.message.error('Unable to load AI scoresheet generation status. Please try again.'); - } - }); - } - - function refreshWorksheetAfterGeneration() { - setAiWorksheetPending(true); - abp.notify.success('', 'Worksheet generated. Review the suggested fields and create draft worksheets.'); - loadAiWorksheetReview(); - } - - function isAiWorksheetPending() { - return UIElements.btnGenerateWorksheet.attr('data-ai-pending') === 'true'; - } - - function setAiWorksheetPending(isPending) { - UIElements.btnGenerateWorksheet - .attr('data-ai-pending', isPending ? 'true' : 'false') - .toggleClass('d-none', isPending); - UIElements.btnReviewWorksheet.toggleClass('d-none', !isPending); - - if (!isPending) { - globalThis.syncAIRateLimitButtons?.(); - } - } - - function loadAiWorksheetReview() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(formVersion)) { - abp.notify.error('', 'Unable to review the worksheet because the Form Version ID is invalid.'); - return; - } - - abp.ajax({ - url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) - .done(function (worksheet) { - if (!worksheet) { - setAiWorksheetPending(false); - abp.notify.error('', 'The pending AI worksheet is no longer available.'); - return; - } - - setAiWorksheetPending(true); - renderAiWorksheetReview(worksheet); - UIElements.worksheetReviewModal.modal('show'); - }) - .fail(function () { - abp.notify.error('', 'Unable to load the pending AI worksheet.'); - }); - } - - function renderAiWorksheetReview(worksheet) { - UIElements.worksheetReviewFields.empty(); - - const fields = worksheet.fields || []; - fields.forEach(function (field) { - const fieldId = `ai-worksheet-field-${field.id}`; - const $row = $('
'); - $('') - .attr('data-field-role', 'Source') - .text(field.key || '—') - .appendTo($row); - $('').appendTo($row); - $('') - .attr('data-field-role', 'Worksheet') - .text(field.label || field.key || '—') - .appendTo($row); - const $switch = $('
'); - const $switchContainer = $('
'); - $('') - .attr('id', fieldId) - .attr('data-field-id', field.id) - .attr('aria-label', `Include ${field.label || field.key || 'field'}`) - .prop('checked', field.selected !== false) - .appendTo($switchContainer); - $switchContainer.appendTo($switch); - $switch.appendTo($row); - $row.appendTo(UIElements.worksheetReviewFields); - }); - - UIElements.worksheetReviewFields.attr('data-session-id', worksheet.sessionId); - UIElements.worksheetReviewEmpty.toggleClass('d-none', fields.length > 0); - updateAiWorksheetReview(); - } - - function updateAiWorksheetReview() { - const $fields = UIElements.worksheetReviewFields.find('input[data-field-id]'); - const selectedCount = $fields.filter(':checked').length; - $('#aiWorksheetReviewSelectAll') - .prop('checked', $fields.length > 0 && selectedCount === $fields.length) - .prop('indeterminate', false); - updateAiWorksheetDraftButton(); - } - - function toggleAiWorksheetReviewAll() { - UIElements.worksheetReviewFields.find('input[data-field-id]').prop('checked', $(this).prop('checked')); - updateAiWorksheetReview(); - } - - function updateAiWorksheetDraftButton() { - const hasTitle = String(UIElements.worksheetTitle.val() ?? '').trim().length > 0; - const hasSelectedFields = UIElements.worksheetReviewFields.find('input[data-field-id]:checked').length > 0; - UIElements.btnCreateWorksheetDraft.prop('disabled', !hasTitle || !hasSelectedFields); - } - - function createAiWorksheetDraft() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const sessionId = UIElements.worksheetReviewFields.attr('data-session-id'); - const title = String(UIElements.worksheetTitle.val() ?? '').trim(); - const selectedFieldIds = UIElements.worksheetReviewFields - .find('input[data-field-id]:checked') - .map(function () { return $(this).attr('data-field-id'); }) - .get(); - - if (!validateGuid(formVersion) || !validateGuid(sessionId) || !title || selectedFieldIds.length === 0) { - abp.notify.error('', 'Enter a worksheet title and select at least one suggested field.'); - return; - } - - UIElements.btnCreateWorksheetDraft.prop('disabled', true); - UIElements.btnDiscardWorksheet.prop('disabled', true); - - abp.ajax({ - url: `/api/app/application-form-version/create-ai-worksheet-draft?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ sessionId, title, selectedFieldIds }) - }) - .done(function () { - UIElements.worksheetTitle.val(''); - abp.notify.success('', 'Draft worksheet created.'); - refreshAiWorksheetReviewAfterDraftCreation(formVersion); - }) - .fail(function () { - abp.notify.error('', 'Unable to create the draft worksheet.'); - }) - .always(function () { - UIElements.btnDiscardWorksheet.prop('disabled', false); - updateAiWorksheetDraftButton(); - }); - } - - function refreshAiWorksheetReviewAfterDraftCreation(formVersion) { - abp.ajax({ - url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) - .done(function (worksheet) { - if (!worksheet) { - UIElements.worksheetReviewModal.modal('hide'); - setAiWorksheetPending(false); - offerFinalMappingGeneration(); - return; - } - - renderAiWorksheetReview(worksheet); - }) - .fail(function () { - abp.notify.error('', 'Draft created, but the remaining suggestions could not be loaded.'); - }); - } - - function discardAiWorksheetSuggestions() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(formVersion) || !isAiWorksheetPending()) { - return; - } - - abp.message.confirm( - 'This will permanently remove the remaining AI field suggestions.', - 'Discard remaining suggestions?') - .then(function (confirmed) { - if (!confirmed) { - return; - } - - UIElements.btnCreateWorksheetDraft.prop('disabled', true); - UIElements.btnDiscardWorksheet.prop('disabled', true); - abp.ajax({ - url: `/api/app/application-form-version/discard-ai-worksheet-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }) - .done(function () { - setAiWorksheetPending(false); - UIElements.worksheetReviewModal.modal('hide'); - abp.notify.success('', 'Remaining AI worksheet suggestions discarded.'); - offerFinalMappingGeneration(); - }) - .fail(function () { - abp.notify.error('', 'Unable to discard the remaining AI worksheet suggestions.'); - }) - .always(function () { - UIElements.btnDiscardWorksheet.prop('disabled', false); - updateAiWorksheetDraftButton(); - }); - }); - } - - function offerFinalMappingGeneration() { - UIElements.mappingReviewModal.modal('hide'); - abp.notify.success('', 'Publish and assign the worksheet drafts, then return here to generate mapping.'); - loadMappingReview(false); - } - - function finalizeMappingReview() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/finalize-mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { - monitorFormMappingGeneration(applicationId, UIElements.btnGenerate, UIElements.btnGenerate.html()); - }).fail(function (error) { - abp.notify.error('', error?.responseJSON?.error?.message || 'Publish and assign all AI worksheet drafts before generating mapping.'); - loadMappingReview(false); - }); - } - - function checkMappingReviewComplete() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }).done(function (review) { - if (review && (!review.pendingSuggestions || review.pendingSuggestions.length === 0)) { - UIElements.mappingReviewModal.modal('hide'); - if (isFinalMappingPhase(review.phase)) { - completeMappingReview(); - } else { - offerWorksheetGeneration(); - } - } - }); - } - - function completeMappingReview() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=Completed`, - type: 'POST' - }).done(function () { - UIElements.btnGenerate.attr('data-ai-pending', 'false'); - abp.notify.success('', 'AI mapping review completed.'); - }); - } - - function isFinalMappingPhase(phase) { - return phase === 'FinalMappingReview' || phase === 2 || phase === '2'; - } - - function refreshScoresheetAfterGeneration() { - abp.notify.success('', 'Scoresheet generated and assigned successfully. Reloading page.'); - setTimeout(function () { - globalThis.location.reload(); - }, 500); - } - - function monitorFormMappingGeneration(applicationId, $button, existingHtml) { - globalThis.AIGenerationButtonState?.monitor({ - $button, - originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-mapping`, - type: 'GET' - }), - onComplete: function () { - refreshMappingAfterGeneration(applicationId); - }, - onFailed: function (request) { - abp.message.error(request?.failureReason || 'AI mapping generation failed.'); - }, - onPollFailed: function () { - abp.message.error('Unable to load AI mapping generation status. Please try again.'); - } - }); - } - - function loadMappingReview(showModal = true) { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(formVersion)) { - return; - } - - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) - .done(function (review) { - updateWorkflowActions(review); - if (!review || !review.pendingSuggestions || review.pendingSuggestions.length === 0) { - if (review?.phase === 'PublishAndAssignWorksheets') { - UIElements.btnGenerate.prop('disabled', !review.canGenerateFinalMapping); - } - return; - } - - renderMappingReview(review); - if (showModal) { - UIElements.mappingReviewModal.modal('show'); - } - }); - } - - function updateWorkflowActions(review) { - const action = getWorkflowAction(review); - const state = getWorkflowState(review); - const actions = (review?.availableActions || []).map(getActionName); - const isActionAvailable = name => actions.includes(name); - const isInitial = action === 'GenerateInitialMapping'; - const isInitialReview = action === 'ReviewInitialMapping'; - const isGenerateWorksheets = action === 'GenerateWorksheets'; - const isReviewWorksheets = action === 'ReviewWorksheets'; - const isPublishAssign = action === 'PublishAndAssignWorksheets'; - const isFinalMapping = action === 'GenerateFinalMapping'; - const isFinalReview = action === 'ReviewFinalMapping'; - const isCompleted = state === 'Completed'; - - UIElements.btnGenerate.toggleClass('d-none', !isInitial); - UIElements.btnReviewMapping.toggleClass('d-none', !isInitialReview); - UIElements.btnGenerateWorksheet.toggleClass('d-none', !isGenerateWorksheets); - UIElements.btnReviewWorksheet.toggleClass('d-none', !isReviewWorksheets); - UIElements.btnPublishAssignWorksheets.toggleClass('d-none', !isPublishAssign); - UIElements.btnGenerateFinalMapping.toggleClass('d-none', !isFinalMapping); - UIElements.btnReviewFinalMapping.toggleClass('d-none', !isFinalReview); - UIElements.btnGenerateNextMapping.toggleClass('d-none', !isCompleted || !isActionAvailable('GenerateMapping')); - UIElements.btnGenerateNextWorksheets.toggleClass('d-none', !isCompleted || !isActionAvailable('GenerateWorksheetsNextCycle')); - UIElements.btnGenerate.prop('disabled', !review?.actionEnabled && isInitial); - UIElements.btnGenerateFinalMapping.prop('disabled', !review?.actionEnabled); - - } - - function getWorkflowState(review) { - if (review?.state) { - return review.state; - } - return getEnumName(review?.workflowState, { - 10: 'GenerateInitialMapping', - 20: 'ReviewInitialMapping', - 30: 'GenerateWorksheets', - 40: 'ReviewWorksheets', - 50: 'PublishAndAssignWorksheets', - 60: 'GenerateFinalMapping', - 70: 'ReviewFinalMapping', - 80: 'Completed' - }); - } - - function getWorkflowAction(review) { - if (review?.action) { - return review.action; - } - return getEnumName(review?.workflowAction, { - 10: 'GenerateInitialMapping', - 20: 'ReviewInitialMapping', - 30: 'GenerateWorksheets', - 40: 'ReviewWorksheets', - 50: 'PublishAndAssignWorksheets', - 60: 'GenerateFinalMapping', - 70: 'ReviewFinalMapping', - 80: 'GenerateMapping', - 90: 'GenerateWorksheetsNextCycle' - }); - } - - function getActionName(action) { - return getEnumName(action, { - 10: 'GenerateInitialMapping', - 20: 'ReviewInitialMapping', - 30: 'GenerateWorksheets', - 40: 'ReviewWorksheets', - 50: 'PublishAndAssignWorksheets', - 60: 'GenerateFinalMapping', - 70: 'ReviewFinalMapping', - 80: 'GenerateMapping', - 90: 'GenerateWorksheetsNextCycle' - }); - } - - function getEnumName(value, numericNames) { - if (typeof value === 'string') { - return value; - } - return numericNames[String(value)] || ''; - } - - function renderMappingReview(review) { - UIElements.mappingReviewFields.empty(); - (review.pendingSuggestions || []).forEach(function (suggestion) { - const fieldId = `ai-mapping-field-${suggestion.id}`; - const $row = $('
'); - $('') - .attr('data-field-role', 'CHEFS field') - .text(suggestion.sourceField || '—') - .appendTo($row); - $('').appendTo($row); - $('') - .attr('data-field-role', 'Unity core field') - .text(suggestion.targetField || '—') - .appendTo($row); - const $switch = $('
'); - const $switchContainer = $('
'); - $('') - .attr('id', fieldId) - .attr('data-suggestion-id', suggestion.id) - .attr('aria-label', `Include ${suggestion.sourceField || 'CHEFS field'} mapping suggestion`) - .prop('checked', false) - .appendTo($switchContainer); - $switchContainer.appendTo($switch); - $switch.appendTo($row); - $row.appendTo(UIElements.mappingReviewFields); - }); - UIElements.mappingReviewEmpty.toggleClass('d-none', (review.pendingSuggestions || []).length > 0); - UIElements.mappingReviewFields.attr('data-phase', review.phase || ''); - updateMappingReviewSelection(); - } - - function updateMappingReviewSelection() { - const $suggestions = UIElements.mappingReviewFields.find('input[data-suggestion-id]'); - const selectedCount = $suggestions.filter(':checked').length; - UIElements.mappingReviewSelectAll - .prop('checked', $suggestions.length > 0 && selectedCount === $suggestions.length) - .prop('indeterminate', false); - UIElements.btnAddMapping.prop('disabled', selectedCount === 0); - } - - function toggleMappingReviewAll() { - UIElements.mappingReviewFields - .find('input[data-suggestion-id]') - .prop('checked', UIElements.mappingReviewSelectAll.prop('checked')); - updateMappingReviewSelection(); - } - - async function addSelectedMappingSuggestion() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - const suggestionIds = UIElements.mappingReviewFields - .find('input[data-suggestion-id]:checked') - .map(function () { return $(this).attr('data-suggestion-id'); }) - .get(); - - if (!validateGuid(formVersion) || suggestionIds.length === 0) { - return; - } - - UIElements.btnAddMapping.prop('disabled', true); - let result; - try { - result = await abp.ajax({ - url: `/api/app/application-form-version/accept-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ suggestionIds }) - }); - } catch (error) { - abp.notify.error( - '', - error?.responseJSON?.error?.message || 'Unable to add the selected mapping suggestions.'); - updateMappingReviewSelection(); - return; - } - - existingMappingString = result.submissionHeaderMapping; - $('#existingMapping').val(existingMappingString); - handleReset(); - - try { - await loadMappingReview(true); - checkMappingReviewComplete(); - } catch (error) { - console.error('Unable to refresh mapping suggestions after they were added.', error); - } finally { - updateMappingReviewSelection(); - } - } - - function discardMappingSuggestions() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.message.confirm('This will permanently remove the remaining AI mapping suggestions.', 'Discard remaining suggestions?') - .then(function (confirmed) { - if (!confirmed) { - return; - } - - return abp.ajax({ - url: `/api/app/application-form-version/discard-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }) - .done(function () { - UIElements.mappingReviewModal.modal('hide'); - if (isFinalMappingPhase(UIElements.mappingReviewFields.attr('data-phase'))) { - completeMappingReview(); - } else { - offerWorksheetGeneration(); - } - }) - .fail(function () { - abp.notify.error('', 'Unable to discard the mapping suggestions.'); - }); - }); - } - - function restartAiFlow() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(formVersion)) { - return; - } - - abp.message.confirm( - 'This permanently deletes AI workflow progress, AI-created worksheets and assignments, and all saved mappings for this form version.', - 'Restart AI Flow?') - .then(function (confirmed) { - if (!confirmed) { - return; - } - - UIElements.btnRestartAiFlow.prop('disabled', true); - return abp.ajax({ - url: `/api/app/application-form-version/reset-ai-flow?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { - globalThis.location.reload(); - }).fail(function (error) { - abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to restart the AI flow.'); - }).always(function () { - UIElements.btnRestartAiFlow.prop('disabled', false); - }); - }); - } - - function offerWorksheetGeneration() { - const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(formVersion)) { - return; - } - - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, - type: 'POST' - }).done(function () { - loadMappingReview(false); - }).fail(function (error) { - abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to continue to worksheet generation.'); - }); - } - - function refreshMappingAfterGeneration(applicationId, formVersion = null) { - const resolvedFormVersion = String(formVersion ?? document.getElementById('formVersionId')?.value ?? '').trim(); - if (!validateGuid(resolvedFormVersion)) { - abp.notify.error('', 'Unable to refresh the generated mapping because the Form Version ID is invalid.'); - return; - } - - loadMappingReview(true); - abp.notify.success('', 'Form mapping suggestions are ready for review.'); - } - - function restoreGenerateMappingButton($button, existingHtml) { - if (!$button?.length) { - return; - } - - globalThis.AIGenerationButtonState?.restore($button); - $button.html(existingHtml).prop('disabled', false); - $button.find('span').last().text('Generate Mapping'); - } - - function restoreGenerateWorksheetButton($button, existingHtml) { - if (!$button?.length) { - return; - } - - globalThis.AIGenerationButtonState?.restore($button); - $button.html(existingHtml).prop('disabled', false); - } - - function restoreGenerateScoresheetButton($button, existingHtml) { - if (!$button?.length) { - return; - } - - globalThis.AIGenerationButtonState?.restore($button); - $button.html(existingHtml).prop('disabled', false); - $button.find('span').last().text('Generate Scoresheet'); - } - - function handleSaveEditMapping() { - try { - let jsonText = $('#jsonText').val(); - $.parseJSON(jsonText); - let mappingJsonStr = jsonText.replaceAll(/\s+/g, ' ').replaceAll(/(\r\n|\n|\r)/gm, ""); - UIElements.btnSaveMapping.prop('disabled', true); - handleSaveMapping($.parseJSON(mappingJsonStr)); - handleCancelMapping(); - - abp.notify.success( - '', - 'Edit mapping save successful. Reloading page to new version' - ); - - setTimeout(function () { - globalThis.location.href = location.href; - }, 500); - - } - catch (err) { - UIElements.btnSaveMapping.prop('disabled', false); - abp.notify.error( - '', - 'The JSON is not valid:' + err - ); - } - } - - function handleCancelMapping() { - UIElements.editMappingModal.removeClass('display-modal'); - } - - function handleSeearchBar(e) { - let filterValue = e.currentTarget.value; - let oTable = $('#ApplicationFormsTable').dataTable(); - oTable.fnFilter(filterValue); - } - - function handleSelectVersion(e) { - let chefsFormVersionGuid = e.currentTarget.value; - navigateToVersion(chefsFormVersionGuid); - } - - function navigateToVersion(chefsFormVersionGuid) { - abp.notify.success( - '', - 'Reloading page to new version' - ); - - setTimeout(function () { - const url = new URL(globalThis.location.href); - - // If this really is a GUID, validate it defensively - if (!/^[0-9a-fA-F-]{36}$/.test(chefsFormVersionGuid)) { - abp.notify.error("The CHEFS Form Version ID is not in a GUID format"); - return; // or handle error - } - - url.searchParams.set("ChefsFormVersionGuid", chefsFormVersionGuid); - globalThis.location.href = url.toString(); - }, 500); - } - - function bindExistingMaps() { - if (existingMappingString + "" != "undefined" && existingMappingString != null && existingMappingString != "") { - try { - let existingMapping = JSON.parse(existingMappingString); - let keys = Object.keys(existingMapping); - for (let key of keys) { - let intakeProperty = key; - let chefsMappingProperty = existingMapping[intakeProperty]; - let intakeMappingCard = document.getElementById("unity_" + intakeProperty); - let chefsMappingDiv = document.getElementById(chefsMappingProperty); - if (chefsMappingDiv != null && intakeMappingCard != null) { - chefsMappingDiv.appendChild(intakeMappingCard); - } else { - abp.notify.error( - '', - 'Could not map existing: ' + chefsMappingProperty - ); - } - } - } catch (err) { - console.log(err); - } - } - } - - - - function handleSync() { - let chefsFormVersionId = document.getElementById('chefsFormVersionId').value; - if (!validateGuid(chefsFormVersionId)) { - abp.notify.error( - '', - 'The Form Version ID is not in a GUID format' - ); - return; - } - - if (chefsFormVersionId == "") { - abp.notify.error( - '', - 'ChefsFormVersionGuid is neeeded - Mapping Not Synchronized Successful' - ); - - } else { - $.ajax( - { - url: `/api/app/form/${chefsFormId}/version/${chefsFormVersionId}`, - type: "POST", - success: function (data) { - let formVersion = data.formVersion; - let updatedApplicationFormName = data.updatedFormName; - let updatedNameMessage = updatedApplicationFormName ? 'Form name updated to ' + updatedApplicationFormName : 'Form name is unchanged'; - if (updatedApplicationFormName) { - document.getElementById('applicationFormName').textContent = updatedApplicationFormName; - } - - let availableChefsFields = JSON.parse(formVersion.availableChefsFields) - document.getElementById('availableChefsFields').value = JSON.stringify(availableChefsFields); - initializeIntakeMap(availableChefsFields); - - abp.notify.success( - '', - 'Synchronized Successful' + updatedNameMessage - ); - navigateToVersion(formVersion.chefsFormVersionGuid); - }, - error: function () { - abp.notify.error( - '', - 'Mapping Not Synchronized Successful' - ); - } - } - ); - } - } - - - - function handleSave() { - let mappingDivs = $('.map-div'); - let mappingJson = {}; - - for (let mappingDiv of mappingDivs) { - let chefMappingDiv = mappingDiv; - if (chefMappingDiv.childElementCount > 0) { - - let chefsKey = mappingDiv.id; - let intakeMappingChildren = chefMappingDiv.children; - - for (let intakeMappingChild of intakeMappingChildren) { - mappingJson[intakeMappingChild.id.replace('unity_', '')] = chefsKey; - } - } - } - handleSaveMapping(mappingJson); - } - - function handleSaveMapping(mappingJson) { - let formData = JSON.parse(document.getElementById('applicationFormVersionDtoString').value); - formData["submissionHeaderMapping"] = JSON.stringify(mappingJson); - formData["availableChefsFields"] = document.getElementById('availableChefsFields').value; - formData["ChefsApplicationFormGuid"] = document.getElementById('applicationFormId').value; - - UIElements.btnSave.prop('disabled', true); - $.ajax( - { - url: "/api/app/application-form-version/" + formVersionId, - data: JSON.stringify(formData), - contentType: "application/json", - type: "PUT", - success: function (data) { - $('#existingMapping').val(data.submissionHeaderMapping); - existingMappingString = data.submissionHeaderMapping; - abp.notify.success( - data.responseText, - 'Mapping Saved Successfully' - ); - }, - error: function (data) { - abp.notify.error( - data.responseText, - 'Mapping Not Saved Successful' - ); - }, - complete: function () { - UIElements.btnSave.prop('disabled', false); - } - } - ); - } - - function handleReset() { - $(intakeMapColumn).empty(); - $(worksheetMapColumn).empty(); - let availableChefsFields = availableChefFieldsString ? JSON.parse(availableChefFieldsString) : [] - initializeIntakeMap(availableChefsFields); - bindExistingMaps(); - } - - - function createIntakeFieldCard(intakeField) { - let intakeFieldJson = intakeField; - let dragableDiv = document.createElement('div'); - dragableDiv.id = 'unity_' + intakeFieldJson.Name; - dragableDiv.className = 'card mapping-field'; - dragableDiv.setAttribute("draggable", "true"); - - // Set icon HTML (internal code, safe) - dragableDiv.innerHTML = `${setTypeIndicator(intakeField)}`; - - // Append label as text node to prevent HTML injection - dragableDiv.appendChild(document.createTextNode(intakeFieldJson.Label)); - - // Append asterisk and route to the appropriate column based on custom status - if (intakeFieldJson.IsCustom) { - dragableDiv.appendChild(document.createTextNode(" *")); - dragableDiv.className += ' custom-field'; - worksheetMapColumn.appendChild(dragableDiv); - } else { - intakeMapColumn.appendChild(dragableDiv); - } - } - - function buildAvailableChefsFieldsRows(availableChefsFields) { - let rowsToAdd = []; - for (let key of Object.keys(availableChefsFields)) { - let jsonObj = JSON.parse(availableChefsFields[key]); - if (allowableTypes.has(jsonObj.type.trim())) { - rowsToAdd.push([stripHtml(jsonObj.label), key, jsonObj.type, key]); - } - } - return rowsToAdd; - } - - function initializeIntakeMap(availableChefsFields) { - try { - - let intakeFields = JSON.parse(intakeFieldsString); - - for (let intakeField of intakeFields) { - if (!excludedIntakeMappings.has(intakeField.Name)) { - createIntakeFieldCard(intakeField); - } - } - - dataTable.clear(); - - let rowsToAdd = buildAvailableChefsFieldsRows(availableChefsFields); - - if (rowsToAdd.length > 0) { - dataTable.rows.add(rowsToAdd); - } - dataTable.draw(); - } - catch (err) { - console.info('Mapping error: ' + err); - } - } - - - document.addEventListener('dragstart', function (ev) { - if (ev.target.classList.contains('non-drag')) { - ev.preventDefault(); - return; - } else if (ev.target.classList.contains('custom-field')) { - UIElements.customFieldsTab.trigger('click'); - } else if (!ev.target.classList.contains('custom-field')) { - UIElements.intakeFieldsTab.trigger('click'); - } - beingDragged(ev); - }); - - document.addEventListener('dragend', function (ev) { - if (ev.target.classList.contains('non-drag')) { - ev.preventDefault(); - return; - } - dragEnd(ev); - }); - - document.addEventListener('dragover', function (event) { - let beingDragged = document.querySelector('.dragging'); - if (event.target.matches('.card')) { - if (beingDragged.classList.contains('card')) { - allowDrop(event); - } - } - if (event.target.matches('.col')) { - if (beingDragged.classList.contains('card')) { - colDraggedOver(event); - } - if (beingDragged.classList.contains('col')) { - allowDrop(event); - } - } - }); - - - - function allowDrop(ev) { - ev.preventDefault(); - - let dragOver = ev.target; - let dragOverParent = dragOver.parentElement; - let beingDragged = document.querySelector('.dragging'); - let draggedParent = beingDragged.parentElement; - - let draggedIndex = whichChild(beingDragged); - let dragOverIndex = whichChild(dragOver); - - if (draggedParent === dragOverParent) { - if (draggedIndex < dragOverIndex) { - beingDragged.before(dragOver); - } else if (draggedIndex > dragOverIndex) { - beingDragged.after(dragOver); - } - } else { - dragOver.before(beingDragged); - } - } - - function colDraggedOver(event) { - let dragOver = event.target; - let beingDragged = document.querySelector('.dragging'); - let draggedParent = beingDragged.parentElement; - if ( - draggedParent.id !== dragOver.id && - draggedParent.classList.contains('col') && - dragOver.classList.contains('col') - ) { - if (dragOver.childElementCount == 0) { - dragOver.appendChild(beingDragged); - } - } - } - - - - - - - function handleMappingTabClick() { - loadMappingReview(false); - // Refresh the hidden field with the latest form version ID - let refreshAvailableWorkSheets = UIElements.refreshAvailableWorksheetsHidden.val(); - if (refreshAvailableWorkSheets && refreshAvailableWorkSheets !== "undefined") { - navigateToVersion(refreshAvailableWorkSheets); - } - } - - PubSub.subscribe( - 'refresh_available_worksheets', - (_, data) => { - UIElements.refreshAvailableWorksheetsHidden.val(data.chefsFormVersionId); - } - ); - - -}); - - -function handleBack() { - location.href = '/ApplicationForms'; -} - -function beingDragged(ev) { - let draggedEl = ev.target; - if (draggedEl.classList + "" !== "undefined") { - draggedEl.classList.add('dragging'); - } -} - -function dragEnd(ev) { - let draggedEl = ev.target; - if (draggedEl.classList + "" !== "undefined") { - draggedEl.classList.remove('dragging'); - } -} +$(function () { + let availableChefFieldsString = document.getElementById('availableChefsFields').value; + let existingMappingString = document.getElementById('existingMapping').value; + let intakeFieldsString = document.getElementById('intakeProperties').value; + let chefsFormId = document.getElementById('chefsFormId').value; + let formVersionId = document.getElementById('formVersionId').value; + let intakeMapColumn = document.querySelector('#intake-map-available-fields-column'); + let worksheetMapColumn = document.querySelector('#worksheet-map-available-fields-column'); + let excludedIntakeMappings = new Set(['ConfirmationId', 'SubmissionId', 'SubmissionDate']); + let dataTable; + + let allowableTypes = new Set(['textarea', + 'orgbook', + 'textfield', + 'currency', + 'datetime', + 'checkbox', + 'select', + 'selectboxes', + 'radio', + 'phoneNumber', + 'email', + 'number', + 'time', + 'day', + 'hidden', + 'simpletextfield', + 'simpletextfieldadvanced', + 'simpletime', + 'simpletimeadvanced', + 'simplenumber', + 'simplenumberadvanced', + 'simplephonenumber', + 'simplephonenumberadvanced', + 'simpleselect', + 'simpleselectadvanced', + 'simpleday', + 'simpledayadvanced', + 'simpleemail', + 'simpleemailadvanced', + 'simpledatetime', + 'simpledatetimeadvanced', + 'simpleurladvanced', + 'simplecheckbox', + 'simpleradios', + 'simpleradioadvanced', + 'simplecheckboxes', + 'simplecheckboxadvanced', + 'simplecurrencyadvanced', + 'simpletextarea', + 'simpletextareaadvanced', + 'bcaddress', + 'datagrid']); + + const UIElements = { + btnBack: $('#btn-back'), + btnSave: $('#btn-save'), + btnEdit: $('#btn-edit'), + btnGenerate: $('#btn-generate'), + btnReviewMapping: $('#btn-review-mapping'), + btnGenerateWorksheet: $('#btn-generate-worksheet'), + btnGenerateScoresheet: $('#btn-generate-scoresheet'), + btnReviewWorksheet: $('#btn-review-worksheet'), + btnPublishAssignWorksheets: $('#btn-publish-assign-worksheets'), + btnGenerateFinalMapping: $('#btn-generate-final-mapping'), + btnReviewFinalMapping: $('#btn-review-final-mapping'), + btnGenerateNextMapping: $('#btn-generate-next-mapping'), + btnGenerateNextWorksheets: $('#btn-generate-next-worksheets'), + btnRestartAiFlow: $('#btn-restart-ai-flow'), + worksheetReviewModal: $('#aiWorksheetReviewModal'), + mappingReviewModal: $('#aiMappingReviewModal'), + mappingReviewFields: $('#aiMappingReviewFields'), + mappingReviewEmpty: $('#aiMappingReviewEmpty'), + mappingReviewSelectAll: $('#aiMappingReviewSelectAll'), + btnAddMapping: $('#btn-add-ai-mapping'), + btnReviewLaterMapping: $('#btn-review-later-ai-mapping'), + btnDiscardMapping: $('#btn-discard-ai-mapping'), + worksheetReviewFields: $('#aiWorksheetReviewFields'), + worksheetReviewEmpty: $('#aiWorksheetReviewEmpty'), + worksheetTitle: $('#aiWorksheetTitle'), + btnCreateWorksheetDraft: $('#btn-create-ai-worksheet-draft'), + btnDiscardWorksheet: $('#btn-discard-ai-worksheet'), + btnSync: $('#btn-sync'), + btnReset: $('#btn-reset'), + btnClose: $('.btn-close'), + btnSaveMapping: $('#btn-save-mapping'), + btnCancel: $('#btn-cancel-mapping'), + inputSearchBar: $('#search-bar'), + selectVersionList: $('#applicationFormVersion'), + editMappingModal: $('#editMappingModal'), + uiConfigurationTab: $('#nav-ui-configuration'), + mappingTab: $('#nav-mapping-tab'), + customFieldsTab: $('#nav-worksheet-fields-tab'), + intakeFieldsTab: $('#nav-intake-fields-tab'), + refreshAvailableWorksheetsHidden: $('#refresh_available_worksheets') + }; + + init(); + + function init() { + bindUIEvents(); + restoreActiveTab(); + dataTable = initializeApplicationFormsTable(); + let availableChefsFields = availableChefFieldsString ? JSON.parse(availableChefFieldsString) : [] + initializeIntakeMap(availableChefsFields); + bindExistingMaps(); + setupTooltips(); + initializeUIConfiguration(); + loadMappingReview(false); + } + + function setupTooltips() { + $('[data-toggle="tooltip"]').tooltip({ + placement: 'top' + }); + } + + function startWorksheetPhase(callback) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + return abp.ajax({ + url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, + type: 'POST' + }).done(callback).fail(function () { + abp.notify.error('', 'Unable to start worksheet generation.'); + }); + } + + function bindUIEvents() { + UIElements.btnBack.on('click', handleBack); + UIElements.btnSave.on('click', handleSave); + UIElements.btnSaveMapping.on('click', handleSaveEditMapping); + UIElements.btnSync.on('click', handleSync); + UIElements.btnEdit.on('click', handleEdit); + UIElements.btnGenerate.on('click', queueFormMapping); + UIElements.btnReviewMapping.on('click', function () { + loadMappingReview(true); + }); + UIElements.btnReviewFinalMapping.on('click', function () { + loadMappingReview(true); + }); + UIElements.btnGenerateWorksheet.on('click', queueFormWorksheet); + UIElements.btnGenerateFinalMapping.on('click', finalizeMappingReview); + UIElements.btnGenerateNextMapping.on('click', queueFormMapping); + UIElements.btnGenerateNextWorksheets.on('click', queueFormWorksheet); + UIElements.btnRestartAiFlow.on('click', restartAiFlow); + UIElements.btnGenerateScoresheet.on('click', queueFormScoresheet); + UIElements.btnReviewWorksheet.on('click', loadAiWorksheetReview); + UIElements.btnAddMapping.on('click', addSelectedMappingSuggestion); + UIElements.btnReviewLaterMapping.on('click', function () { + UIElements.mappingReviewModal.modal('hide'); + }); + UIElements.btnDiscardMapping.on('click', discardMappingSuggestions); + UIElements.mappingReviewFields.on('change', 'input[data-suggestion-id]', updateMappingReviewSelection); + UIElements.mappingReviewSelectAll.on('change', toggleMappingReviewAll); + UIElements.btnCreateWorksheetDraft.on('click', createAiWorksheetDraft); + UIElements.btnDiscardWorksheet.on('click', discardAiWorksheetSuggestions); + UIElements.worksheetReviewFields.on('change', 'input[data-field-id]', updateAiWorksheetReview); + $('#aiWorksheetReviewSelectAll').on('change', toggleAiWorksheetReviewAll); + UIElements.worksheetTitle.on('input', updateAiWorksheetDraftButton); + UIElements.btnReset.on('click', handleReset); + UIElements.btnCancel.on('click', handleCancelMapping); + UIElements.btnClose.on('click', handleCancelMapping); + UIElements.inputSearchBar.on('keyup', handleSeearchBar); + UIElements.selectVersionList.on('change', handleSelectVersion); + UIElements.mappingTab.on('click', handleMappingTabClick); + + // Persist active tab to localStorage on switch + $('#nav-tab').on('shown.bs.tab', 'button[data-bs-toggle="tab"]', function () { + const formId = document.getElementById('applicationFormId')?.value; + if (formId) { + localStorage.setItem('mapping-active-tab:' + formId, this.id); + } + }); + } + + function restoreActiveTab() { + const formId = document.getElementById('applicationFormId')?.value; + if (!formId) return; + const savedTabId = localStorage.getItem('mapping-active-tab:' + formId); + if (!savedTabId) return; + const tabEl = document.getElementById(savedTabId); + if (tabEl) { + bootstrap.Tab.getOrCreateInstance(tabEl).show(); + } + } + + function initializeUIConfiguration() { + const providerName = 'F'; + const providerKey = $('#applicationFormId').val(); + const providerKeyDisplayName = 'Test.Display.Name'; + + $.ajax({ + url: abp.appPath + 'SettingManagement/ZoneManagement', + type: 'GET', + data: { + providerName: providerName, + providerKey: providerKey, + providerKeyDisplayName: providerKeyDisplayName + }, + success: function (response) { + UIElements.uiConfigurationTab.html(response); + }, + error: function () { + abp.notify.error('Failed to load UI Configuration.'); + } + }); + } + + function handleEdit() { + $('#jsonText').val(prettyJson(existingMappingString)); + UIElements.editMappingModal.addClass('display-modal'); + } + + function queueFormMapping(triggerButton = null) { + if (UIElements.btnGenerate.attr('data-ai-pending') === 'true') { + loadMappingReview(true); + return; + } + + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + abp.notify.error('', 'The Form Version ID is not in a GUID format'); + return; + } + if (!validateGuid(applicationId)) { + abp.notify.error('', 'The Application ID is not in a GUID format'); + return; + } + + const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerate?.get?.(0); + const $button = $(buttonElement); + const existingHtml = $button.html(); + + if ($button.prop('disabled')) { + return; + } + + globalThis.AIGenerationButtonState?.setGenerating($button); + + abp.ajax({ + url: `/api/app/ai/generation/form-mapping?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST', + }) + .done(function (generationStatus) { + const request = generationStatus?.generationRequest; + const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; + + if (status === 'Completed') { + globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); + globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); + refreshMappingAfterGeneration(applicationId, formVersion); + return; + } + + monitorFormMappingGeneration(applicationId, $button, existingHtml); + }) + .fail(function (error) { + if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { + return; + } + + abp.message.error('Failed to queue AI mapping generation. Please try again.'); + restoreGenerateMappingButton($button, existingHtml); + globalThis.syncAIRateLimitButtons?.(); + }); + } + + function queueFormWorksheet(triggerButton = null) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + if (!validateGuid(formVersion) || !validateGuid(applicationId)) { + abp.notify.error('', 'The Form Version ID or Application ID is not in a GUID format'); + return; + } + + const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateWorksheet?.get?.(0); + const $button = $(buttonElement); + + if (isAiWorksheetPending()) { + loadAiWorksheetReview(); + return; + } + + startWorksheetPhase(function () { + queueFormWorksheetCore(triggerButton); + }); + } + + function queueFormWorksheetCore(triggerButton = null) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateWorksheet?.get?.(0); + const $button = $(buttonElement); + const existingHtml = $button.html(); + + if ($button.prop('disabled')) { + return; + } + + globalThis.AIGenerationButtonState?.setGenerating($button); + + abp.ajax({ + url: `/api/app/ai/generation/form-worksheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST', + }) + .done(function (generationStatus) { + const request = generationStatus?.generationRequest; + const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; + if (status === 'Completed') { + globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); + globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); + refreshWorksheetAfterGeneration(); + return; + } + + monitorFormWorksheetGeneration(applicationId, $button, existingHtml); + }) + .fail(function (error) { + if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { + return; + } + + abp.message.error('Failed to queue AI worksheet generation. Please try again.'); + restoreGenerateWorksheetButton($button, existingHtml); + globalThis.syncAIRateLimitButtons?.(); + }); + } + + function monitorFormWorksheetGeneration(applicationId, $button, existingHtml) { + globalThis.AIGenerationButtonState?.monitor({ + $button, + originalHtml: existingHtml, + getStatus: () => abp.ajax({ + url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-worksheet`, + type: 'GET' + }), + onComplete: function () { + refreshWorksheetAfterGeneration(); + }, + onFailed: function (request) { + abp.message.error(request?.failureReason || 'AI worksheet generation failed.'); + }, + onPollFailed: function () { + abp.message.error('Unable to load AI worksheet generation status. Please try again.'); + } + }); + } + + function queueFormScoresheet(triggerButton = null) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + if (!validateGuid(formVersion) || !validateGuid(applicationId)) { + abp.notify.error('', 'The Form Version ID or Application ID is not in a GUID format'); + return; + } + + const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateScoresheet?.get?.(0); + const $button = $(buttonElement); + const existingHtml = $button.html(); + + if ($button.prop('disabled')) { + return; + } + + globalThis.AIGenerationButtonState?.setGenerating($button); + + abp.ajax({ + url: `/api/app/ai/generation/form-scoresheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST', + }) + .done(function (generationStatus) { + const request = generationStatus?.generationRequest; + const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; + if (status === 'Completed') { + globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml); + globalThis.AIGenerationButtonState?.applyStatusState(generationStatus); + refreshScoresheetAfterGeneration(); + return; + } + + monitorFormScoresheetGeneration(applicationId, $button, existingHtml); + }) + .fail(function (error) { + if (globalThis.AIGenerationButtonState?.handleQueueFailure(error)) { + return; + } + + abp.message.error('Failed to queue AI scoresheet generation. Please try again.'); + restoreGenerateScoresheetButton($button, existingHtml); + globalThis.syncAIRateLimitButtons?.(); + }); + } + + function monitorFormScoresheetGeneration(applicationId, $button, existingHtml) { + globalThis.AIGenerationButtonState?.monitor({ + $button, + originalHtml: existingHtml, + getStatus: () => abp.ajax({ + url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-scoresheet`, + type: 'GET' + }), + onComplete: function () { + refreshScoresheetAfterGeneration(); + }, + onFailed: function (request) { + abp.message.error(request?.failureReason || 'AI scoresheet generation failed.'); + }, + onPollFailed: function () { + abp.message.error('Unable to load AI scoresheet generation status. Please try again.'); + } + }); + } + + function refreshWorksheetAfterGeneration() { + setAiWorksheetPending(true); + abp.notify.success('', 'Worksheet generated. Review the suggested fields and create draft worksheets.'); + loadAiWorksheetReview(); + } + + function isAiWorksheetPending() { + return UIElements.btnGenerateWorksheet.attr('data-ai-pending') === 'true'; + } + + function setAiWorksheetPending(isPending) { + UIElements.btnGenerateWorksheet + .attr('data-ai-pending', isPending ? 'true' : 'false') + .toggleClass('d-none', isPending); + UIElements.btnReviewWorksheet.toggleClass('d-none', !isPending); + + if (!isPending) { + globalThis.syncAIRateLimitButtons?.(); + } + } + + function loadAiWorksheetReview() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + abp.notify.error('', 'Unable to review the worksheet because the Form Version ID is invalid.'); + return; + } + + abp.ajax({ + url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'GET' + }) + .done(function (worksheet) { + if (!worksheet) { + setAiWorksheetPending(false); + abp.notify.error('', 'The pending AI worksheet is no longer available.'); + return; + } + + setAiWorksheetPending(true); + renderAiWorksheetReview(worksheet); + UIElements.worksheetReviewModal.modal('show'); + }) + .fail(function () { + abp.notify.error('', 'Unable to load the pending AI worksheet.'); + }); + } + + function renderAiWorksheetReview(worksheet) { + UIElements.worksheetReviewFields.empty(); + + const fields = worksheet.fields || []; + fields.forEach(function (field) { + const fieldId = `ai-worksheet-field-${field.id}`; + const $row = $('
'); + $('') + .attr('data-field-role', 'Source') + .text(field.key || '—') + .appendTo($row); + $('').appendTo($row); + $('') + .attr('data-field-role', 'Worksheet') + .text(field.label || field.key || '—') + .appendTo($row); + const $switch = $('
'); + const $switchContainer = $('
'); + $('') + .attr('id', fieldId) + .attr('data-field-id', field.id) + .attr('aria-label', `Include ${field.label || field.key || 'field'}`) + .prop('checked', field.selected !== false) + .appendTo($switchContainer); + $switchContainer.appendTo($switch); + $switch.appendTo($row); + $row.appendTo(UIElements.worksheetReviewFields); + }); + + UIElements.worksheetReviewFields.attr('data-session-id', worksheet.sessionId); + UIElements.worksheetReviewEmpty.toggleClass('d-none', fields.length > 0); + updateAiWorksheetReview(); + } + + function updateAiWorksheetReview() { + const $fields = UIElements.worksheetReviewFields.find('input[data-field-id]'); + const selectedCount = $fields.filter(':checked').length; + $('#aiWorksheetReviewSelectAll') + .prop('checked', $fields.length > 0 && selectedCount === $fields.length) + .prop('indeterminate', false); + updateAiWorksheetDraftButton(); + } + + function toggleAiWorksheetReviewAll() { + UIElements.worksheetReviewFields.find('input[data-field-id]').prop('checked', $(this).prop('checked')); + updateAiWorksheetReview(); + } + + function updateAiWorksheetDraftButton() { + const hasTitle = String(UIElements.worksheetTitle.val() ?? '').trim().length > 0; + const hasSelectedFields = UIElements.worksheetReviewFields.find('input[data-field-id]:checked').length > 0; + UIElements.btnCreateWorksheetDraft.prop('disabled', !hasTitle || !hasSelectedFields); + } + + function createAiWorksheetDraft() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const sessionId = UIElements.worksheetReviewFields.attr('data-session-id'); + const title = String(UIElements.worksheetTitle.val() ?? '').trim(); + const selectedFieldIds = UIElements.worksheetReviewFields + .find('input[data-field-id]:checked') + .map(function () { return $(this).attr('data-field-id'); }) + .get(); + + if (!validateGuid(formVersion) || !validateGuid(sessionId) || !title || selectedFieldIds.length === 0) { + abp.notify.error('', 'Enter a worksheet title and select at least one suggested field.'); + return; + } + + UIElements.btnCreateWorksheetDraft.prop('disabled', true); + UIElements.btnDiscardWorksheet.prop('disabled', true); + + abp.ajax({ + url: `/api/app/application-form-version/create-ai-worksheet-draft?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ sessionId, title, selectedFieldIds }) + }) + .done(function () { + UIElements.worksheetTitle.val(''); + abp.notify.success('', 'Draft worksheet created.'); + refreshAiWorksheetReviewAfterDraftCreation(formVersion); + }) + .fail(function () { + abp.notify.error('', 'Unable to create the draft worksheet.'); + }) + .always(function () { + UIElements.btnDiscardWorksheet.prop('disabled', false); + updateAiWorksheetDraftButton(); + }); + } + + function refreshAiWorksheetReviewAfterDraftCreation(formVersion) { + abp.ajax({ + url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'GET' + }) + .done(function (worksheet) { + if (!worksheet) { + UIElements.worksheetReviewModal.modal('hide'); + setAiWorksheetPending(false); + offerFinalMappingGeneration(); + return; + } + + renderAiWorksheetReview(worksheet); + }) + .fail(function () { + abp.notify.error('', 'Draft created, but the remaining suggestions could not be loaded.'); + }); + } + + function discardAiWorksheetSuggestions() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion) || !isAiWorksheetPending()) { + return; + } + + abp.message.confirm( + 'This will permanently remove the remaining AI field suggestions.', + 'Discard remaining suggestions?') + .then(function (confirmed) { + if (!confirmed) { + return; + } + + UIElements.btnCreateWorksheetDraft.prop('disabled', true); + UIElements.btnDiscardWorksheet.prop('disabled', true); + abp.ajax({ + url: `/api/app/application-form-version/discard-ai-worksheet-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST' + }) + .done(function () { + setAiWorksheetPending(false); + UIElements.worksheetReviewModal.modal('hide'); + abp.notify.success('', 'Remaining AI worksheet suggestions discarded.'); + offerFinalMappingGeneration(); + }) + .fail(function () { + abp.notify.error('', 'Unable to discard the remaining AI worksheet suggestions.'); + }) + .always(function () { + UIElements.btnDiscardWorksheet.prop('disabled', false); + updateAiWorksheetDraftButton(); + }); + }); + } + + function offerFinalMappingGeneration() { + UIElements.mappingReviewModal.modal('hide'); + abp.notify.success('', 'Publish and assign the worksheet drafts, then return here to generate mapping.'); + loadMappingReview(false); + } + + function finalizeMappingReview() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + abp.ajax({ + url: `/api/app/application-form-version/finalize-mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST' + }).done(function () { + monitorFormMappingGeneration(applicationId, UIElements.btnGenerate, UIElements.btnGenerate.html()); + }).fail(function (error) { + abp.notify.error('', error?.responseJSON?.error?.message || 'Publish and assign all AI worksheet drafts before generating mapping.'); + loadMappingReview(false); + }); + } + + function checkMappingReviewComplete() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + abp.ajax({ + url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'GET' + }).done(function (review) { + if (review && (!review.pendingSuggestions || review.pendingSuggestions.length === 0)) { + UIElements.mappingReviewModal.modal('hide'); + if (isFinalMappingPhase(review.phase)) { + completeMappingReview(); + } else { + offerWorksheetGeneration(); + } + } + }); + } + + function completeMappingReview() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + abp.ajax({ + url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=Completed`, + type: 'POST' + }).done(function () { + UIElements.btnGenerate.attr('data-ai-pending', 'false'); + abp.notify.success('', 'AI mapping review completed.'); + }); + } + + function isFinalMappingPhase(phase) { + return phase === 'FinalMappingReview' || phase === 2 || phase === '2'; + } + + function refreshScoresheetAfterGeneration() { + abp.notify.success('', 'Scoresheet generated and assigned successfully. Reloading page.'); + setTimeout(function () { + globalThis.location.reload(); + }, 500); + } + + function monitorFormMappingGeneration(applicationId, $button, existingHtml) { + globalThis.AIGenerationButtonState?.monitor({ + $button, + originalHtml: existingHtml, + getStatus: () => abp.ajax({ + url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-mapping`, + type: 'GET' + }), + onComplete: function () { + refreshMappingAfterGeneration(applicationId); + }, + onFailed: function (request) { + abp.message.error(request?.failureReason || 'AI mapping generation failed.'); + }, + onPollFailed: function () { + abp.message.error('Unable to load AI mapping generation status. Please try again.'); + } + }); + } + + function loadMappingReview(showModal = true) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + return; + } + + return abp.ajax({ + url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'GET' + }) + .done(function (review) { + updateWorkflowActions(review); + if (!review || !review.pendingSuggestions || review.pendingSuggestions.length === 0) { + if (review?.phase === 'PublishAndAssignWorksheets') { + UIElements.btnGenerate.prop('disabled', !review.canGenerateFinalMapping); + } + return; + } + + renderMappingReview(review); + if (showModal) { + UIElements.mappingReviewModal.modal('show'); + } + }); + } + + function updateWorkflowActions(review) { + const action = getWorkflowAction(review); + const state = getWorkflowState(review); + const actions = (review?.availableActions || []).map(getActionName); + const isActionAvailable = name => actions.includes(name); + const isInitial = action === 'GenerateInitialMapping'; + const isInitialReview = action === 'ReviewInitialMapping'; + const isGenerateWorksheets = action === 'GenerateWorksheets'; + const isReviewWorksheets = action === 'ReviewWorksheets'; + const isPublishAssign = action === 'PublishAndAssignWorksheets'; + const isFinalMapping = action === 'GenerateFinalMapping'; + const isFinalReview = action === 'ReviewFinalMapping'; + const isCompleted = state === 'Completed'; + + UIElements.btnGenerate.toggleClass('d-none', !isInitial); + UIElements.btnReviewMapping.toggleClass('d-none', !isInitialReview); + UIElements.btnGenerateWorksheet.toggleClass('d-none', !isGenerateWorksheets); + UIElements.btnReviewWorksheet.toggleClass('d-none', !isReviewWorksheets); + UIElements.btnPublishAssignWorksheets.toggleClass('d-none', !isPublishAssign); + UIElements.btnGenerateFinalMapping.toggleClass('d-none', !isFinalMapping); + UIElements.btnReviewFinalMapping.toggleClass('d-none', !isFinalReview); + UIElements.btnGenerateNextMapping.toggleClass('d-none', !isCompleted || !isActionAvailable('GenerateMapping')); + UIElements.btnGenerateNextWorksheets.toggleClass('d-none', !isCompleted || !isActionAvailable('GenerateWorksheetsNextCycle')); + UIElements.btnGenerate.prop('disabled', !review?.actionEnabled && isInitial); + UIElements.btnGenerateFinalMapping.prop('disabled', !review?.actionEnabled); + + } + + function getWorkflowState(review) { + if (review?.state) { + return review.state; + } + return getEnumName(review?.workflowState, { + 10: 'GenerateInitialMapping', + 20: 'ReviewInitialMapping', + 30: 'GenerateWorksheets', + 40: 'ReviewWorksheets', + 50: 'PublishAndAssignWorksheets', + 60: 'GenerateFinalMapping', + 70: 'ReviewFinalMapping', + 80: 'Completed' + }); + } + + function getWorkflowAction(review) { + if (review?.action) { + return review.action; + } + return getEnumName(review?.workflowAction, { + 10: 'GenerateInitialMapping', + 20: 'ReviewInitialMapping', + 30: 'GenerateWorksheets', + 40: 'ReviewWorksheets', + 50: 'PublishAndAssignWorksheets', + 60: 'GenerateFinalMapping', + 70: 'ReviewFinalMapping', + 80: 'GenerateMapping', + 90: 'GenerateWorksheetsNextCycle' + }); + } + + function getActionName(action) { + return getEnumName(action, { + 10: 'GenerateInitialMapping', + 20: 'ReviewInitialMapping', + 30: 'GenerateWorksheets', + 40: 'ReviewWorksheets', + 50: 'PublishAndAssignWorksheets', + 60: 'GenerateFinalMapping', + 70: 'ReviewFinalMapping', + 80: 'GenerateMapping', + 90: 'GenerateWorksheetsNextCycle' + }); + } + + function getEnumName(value, numericNames) { + if (typeof value === 'string') { + return value; + } + return numericNames[String(value)] || ''; + } + + function renderMappingReview(review) { + UIElements.mappingReviewFields.empty(); + (review.pendingSuggestions || []).forEach(function (suggestion) { + const fieldId = `ai-mapping-field-${suggestion.id}`; + const $row = $('
'); + $('') + .attr('data-field-role', 'CHEFS field') + .text(suggestion.sourceField || '—') + .appendTo($row); + $('').appendTo($row); + $('') + .attr('data-field-role', 'Unity core field') + .text(suggestion.targetField || '—') + .appendTo($row); + const $switch = $('
'); + const $switchContainer = $('
'); + $('') + .attr('id', fieldId) + .attr('data-suggestion-id', suggestion.id) + .attr('aria-label', `Include ${suggestion.sourceField || 'CHEFS field'} mapping suggestion`) + .prop('checked', false) + .appendTo($switchContainer); + $switchContainer.appendTo($switch); + $switch.appendTo($row); + $row.appendTo(UIElements.mappingReviewFields); + }); + UIElements.mappingReviewEmpty.toggleClass('d-none', (review.pendingSuggestions || []).length > 0); + UIElements.mappingReviewFields.attr('data-phase', review.phase || ''); + updateMappingReviewSelection(); + } + + function updateMappingReviewSelection() { + const $suggestions = UIElements.mappingReviewFields.find('input[data-suggestion-id]'); + const selectedCount = $suggestions.filter(':checked').length; + UIElements.mappingReviewSelectAll + .prop('checked', $suggestions.length > 0 && selectedCount === $suggestions.length) + .prop('indeterminate', false); + UIElements.btnAddMapping.prop('disabled', selectedCount === 0); + } + + function toggleMappingReviewAll() { + UIElements.mappingReviewFields + .find('input[data-suggestion-id]') + .prop('checked', UIElements.mappingReviewSelectAll.prop('checked')); + updateMappingReviewSelection(); + } + + async function addSelectedMappingSuggestion() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + const suggestionIds = UIElements.mappingReviewFields + .find('input[data-suggestion-id]:checked') + .map(function () { return $(this).attr('data-suggestion-id'); }) + .get(); + + if (!validateGuid(formVersion) || suggestionIds.length === 0) { + return; + } + + UIElements.btnAddMapping.prop('disabled', true); + let result; + try { + result = await abp.ajax({ + url: `/api/app/application-form-version/accept-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ suggestionIds }) + }); + } catch (error) { + abp.notify.error( + '', + error?.responseJSON?.error?.message || 'Unable to add the selected mapping suggestions.'); + updateMappingReviewSelection(); + return; + } + + existingMappingString = result.submissionHeaderMapping; + $('#existingMapping').val(existingMappingString); + handleReset(); + + try { + await loadMappingReview(true); + checkMappingReviewComplete(); + } catch (error) { + console.error('Unable to refresh mapping suggestions after they were added.', error); + } finally { + updateMappingReviewSelection(); + } + } + + function discardMappingSuggestions() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + abp.message.confirm('This will permanently remove the remaining AI mapping suggestions.', 'Discard remaining suggestions?') + .then(function (confirmed) { + if (!confirmed) { + return; + } + + return abp.ajax({ + url: `/api/app/application-form-version/discard-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST' + }) + .done(function () { + UIElements.mappingReviewModal.modal('hide'); + if (isFinalMappingPhase(UIElements.mappingReviewFields.attr('data-phase'))) { + completeMappingReview(); + } else { + offerWorksheetGeneration(); + } + }) + .fail(function () { + abp.notify.error('', 'Unable to discard the mapping suggestions.'); + }); + }); + } + + function restartAiFlow() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + return; + } + + abp.message.confirm( + 'This permanently deletes AI workflow progress, AI-created worksheets and assignments, and all saved mappings for this form version.', + 'Restart AI Flow?') + .then(function (confirmed) { + if (!confirmed) { + return; + } + + UIElements.btnRestartAiFlow.prop('disabled', true); + return abp.ajax({ + url: `/api/app/application-form-version/reset-ai-flow?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'POST' + }).done(function () { + globalThis.location.reload(); + }).fail(function (error) { + abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to restart the AI flow.'); + }).always(function () { + UIElements.btnRestartAiFlow.prop('disabled', false); + }); + }); + } + + function offerWorksheetGeneration() { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + return; + } + + return abp.ajax({ + url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, + type: 'POST' + }).done(function () { + loadMappingReview(false); + }).fail(function (error) { + abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to continue to worksheet generation.'); + }); + } + + function refreshMappingAfterGeneration(applicationId, formVersion = null) { + const resolvedFormVersion = String(formVersion ?? document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(resolvedFormVersion)) { + abp.notify.error('', 'Unable to refresh the generated mapping because the Form Version ID is invalid.'); + return; + } + + loadMappingReview(true); + abp.notify.success('', 'Form mapping suggestions are ready for review.'); + } + + function restoreGenerateMappingButton($button, existingHtml) { + if (!$button?.length) { + return; + } + + globalThis.AIGenerationButtonState?.restore($button); + $button.html(existingHtml).prop('disabled', false); + $button.find('span').last().text('Generate Mapping'); + } + + function restoreGenerateWorksheetButton($button, existingHtml) { + if (!$button?.length) { + return; + } + + globalThis.AIGenerationButtonState?.restore($button); + $button.html(existingHtml).prop('disabled', false); + } + + function restoreGenerateScoresheetButton($button, existingHtml) { + if (!$button?.length) { + return; + } + + globalThis.AIGenerationButtonState?.restore($button); + $button.html(existingHtml).prop('disabled', false); + $button.find('span').last().text('Generate Scoresheet'); + } + + function handleSaveEditMapping() { + try { + let jsonText = $('#jsonText').val(); + $.parseJSON(jsonText); + let mappingJsonStr = jsonText.replaceAll(/\s+/g, ' ').replaceAll(/(\r\n|\n|\r)/gm, ""); + UIElements.btnSaveMapping.prop('disabled', true); + handleSaveMapping($.parseJSON(mappingJsonStr)); + handleCancelMapping(); + + abp.notify.success( + '', + 'Edit mapping save successful. Reloading page to new version' + ); + + setTimeout(function () { + globalThis.location.href = location.href; + }, 500); + + } + catch (err) { + UIElements.btnSaveMapping.prop('disabled', false); + abp.notify.error( + '', + 'The JSON is not valid:' + err + ); + } + } + + function handleCancelMapping() { + UIElements.editMappingModal.removeClass('display-modal'); + } + + function handleSeearchBar(e) { + let filterValue = e.currentTarget.value; + let oTable = $('#ApplicationFormsTable').dataTable(); + oTable.fnFilter(filterValue); + } + + function handleSelectVersion(e) { + let chefsFormVersionGuid = e.currentTarget.value; + navigateToVersion(chefsFormVersionGuid); + } + + function navigateToVersion(chefsFormVersionGuid) { + abp.notify.success( + '', + 'Reloading page to new version' + ); + + setTimeout(function () { + const url = new URL(globalThis.location.href); + + // If this really is a GUID, validate it defensively + if (!/^[0-9a-fA-F-]{36}$/.test(chefsFormVersionGuid)) { + abp.notify.error("The CHEFS Form Version ID is not in a GUID format"); + return; // or handle error + } + + url.searchParams.set("ChefsFormVersionGuid", chefsFormVersionGuid); + globalThis.location.href = url.toString(); + }, 500); + } + + function bindExistingMaps() { + if (existingMappingString + "" != "undefined" && existingMappingString != null && existingMappingString != "") { + try { + let existingMapping = JSON.parse(existingMappingString); + let keys = Object.keys(existingMapping); + for (let key of keys) { + let intakeProperty = key; + let chefsMappingProperty = existingMapping[intakeProperty]; + let intakeMappingCard = document.getElementById("unity_" + intakeProperty); + let chefsMappingDiv = document.getElementById(chefsMappingProperty); + if (chefsMappingDiv != null && intakeMappingCard != null) { + chefsMappingDiv.appendChild(intakeMappingCard); + } else { + abp.notify.error( + '', + 'Could not map existing: ' + chefsMappingProperty + ); + } + } + } catch (err) { + console.log(err); + } + } + } + + + + function handleSync() { + let chefsFormVersionId = document.getElementById('chefsFormVersionId').value; + if (!validateGuid(chefsFormVersionId)) { + abp.notify.error( + '', + 'The Form Version ID is not in a GUID format' + ); + return; + } + + if (chefsFormVersionId == "") { + abp.notify.error( + '', + 'ChefsFormVersionGuid is neeeded - Mapping Not Synchronized Successful' + ); + + } else { + $.ajax( + { + url: `/api/app/form/${chefsFormId}/version/${chefsFormVersionId}`, + type: "POST", + success: function (data) { + let formVersion = data.formVersion; + let updatedApplicationFormName = data.updatedFormName; + let updatedNameMessage = updatedApplicationFormName ? 'Form name updated to ' + updatedApplicationFormName : 'Form name is unchanged'; + if (updatedApplicationFormName) { + document.getElementById('applicationFormName').textContent = updatedApplicationFormName; + } + + let availableChefsFields = JSON.parse(formVersion.availableChefsFields) + document.getElementById('availableChefsFields').value = JSON.stringify(availableChefsFields); + initializeIntakeMap(availableChefsFields); + + abp.notify.success( + '', + 'Synchronized Successful' + updatedNameMessage + ); + navigateToVersion(formVersion.chefsFormVersionGuid); + }, + error: function () { + abp.notify.error( + '', + 'Mapping Not Synchronized Successful' + ); + } + } + ); + } + } + + + + function handleSave() { + let mappingDivs = $('.map-div'); + let mappingJson = {}; + + for (let mappingDiv of mappingDivs) { + let chefMappingDiv = mappingDiv; + if (chefMappingDiv.childElementCount > 0) { + + let chefsKey = mappingDiv.id; + let intakeMappingChildren = chefMappingDiv.children; + + for (let intakeMappingChild of intakeMappingChildren) { + mappingJson[intakeMappingChild.id.replace('unity_', '')] = chefsKey; + } + } + } + handleSaveMapping(mappingJson); + } + + function handleSaveMapping(mappingJson) { + let formData = JSON.parse(document.getElementById('applicationFormVersionDtoString').value); + formData["submissionHeaderMapping"] = JSON.stringify(mappingJson); + formData["availableChefsFields"] = document.getElementById('availableChefsFields').value; + formData["ChefsApplicationFormGuid"] = document.getElementById('applicationFormId').value; + + UIElements.btnSave.prop('disabled', true); + $.ajax( + { + url: "/api/app/application-form-version/" + formVersionId, + data: JSON.stringify(formData), + contentType: "application/json", + type: "PUT", + success: function (data) { + $('#existingMapping').val(data.submissionHeaderMapping); + existingMappingString = data.submissionHeaderMapping; + abp.notify.success( + data.responseText, + 'Mapping Saved Successfully' + ); + }, + error: function (data) { + abp.notify.error( + data.responseText, + 'Mapping Not Saved Successful' + ); + }, + complete: function () { + UIElements.btnSave.prop('disabled', false); + } + } + ); + } + + function handleReset() { + $(intakeMapColumn).empty(); + $(worksheetMapColumn).empty(); + let availableChefsFields = availableChefFieldsString ? JSON.parse(availableChefFieldsString) : [] + initializeIntakeMap(availableChefsFields); + bindExistingMaps(); + } + + + function createIntakeFieldCard(intakeField) { + let intakeFieldJson = intakeField; + let dragableDiv = document.createElement('div'); + dragableDiv.id = 'unity_' + intakeFieldJson.Name; + dragableDiv.className = 'card mapping-field'; + dragableDiv.setAttribute("draggable", "true"); + + // Set icon HTML (internal code, safe) + dragableDiv.innerHTML = `${setTypeIndicator(intakeField)}`; + + // Append label as text node to prevent HTML injection + dragableDiv.appendChild(document.createTextNode(intakeFieldJson.Label)); + + // Append asterisk and route to the appropriate column based on custom status + if (intakeFieldJson.IsCustom) { + dragableDiv.appendChild(document.createTextNode(" *")); + dragableDiv.className += ' custom-field'; + worksheetMapColumn.appendChild(dragableDiv); + } else { + intakeMapColumn.appendChild(dragableDiv); + } + } + + function buildAvailableChefsFieldsRows(availableChefsFields) { + let rowsToAdd = []; + for (let key of Object.keys(availableChefsFields)) { + let jsonObj = JSON.parse(availableChefsFields[key]); + if (allowableTypes.has(jsonObj.type.trim())) { + rowsToAdd.push([stripHtml(jsonObj.label), key, jsonObj.type, key]); + } + } + return rowsToAdd; + } + + function initializeIntakeMap(availableChefsFields) { + try { + + let intakeFields = JSON.parse(intakeFieldsString); + + for (let intakeField of intakeFields) { + if (!excludedIntakeMappings.has(intakeField.Name)) { + createIntakeFieldCard(intakeField); + } + } + + dataTable.clear(); + + let rowsToAdd = buildAvailableChefsFieldsRows(availableChefsFields); + + if (rowsToAdd.length > 0) { + dataTable.rows.add(rowsToAdd); + } + dataTable.draw(); + } + catch (err) { + console.info('Mapping error: ' + err); + } + } + + + document.addEventListener('dragstart', function (ev) { + if (ev.target.classList.contains('non-drag')) { + ev.preventDefault(); + return; + } else if (ev.target.classList.contains('custom-field')) { + UIElements.customFieldsTab.trigger('click'); + } else if (!ev.target.classList.contains('custom-field')) { + UIElements.intakeFieldsTab.trigger('click'); + } + beingDragged(ev); + }); + + document.addEventListener('dragend', function (ev) { + if (ev.target.classList.contains('non-drag')) { + ev.preventDefault(); + return; + } + dragEnd(ev); + }); + + document.addEventListener('dragover', function (event) { + let beingDragged = document.querySelector('.dragging'); + if (event.target.matches('.card')) { + if (beingDragged.classList.contains('card')) { + allowDrop(event); + } + } + if (event.target.matches('.col')) { + if (beingDragged.classList.contains('card')) { + colDraggedOver(event); + } + if (beingDragged.classList.contains('col')) { + allowDrop(event); + } + } + }); + + + + function allowDrop(ev) { + ev.preventDefault(); + + let dragOver = ev.target; + let dragOverParent = dragOver.parentElement; + let beingDragged = document.querySelector('.dragging'); + let draggedParent = beingDragged.parentElement; + + let draggedIndex = whichChild(beingDragged); + let dragOverIndex = whichChild(dragOver); + + if (draggedParent === dragOverParent) { + if (draggedIndex < dragOverIndex) { + beingDragged.before(dragOver); + } else if (draggedIndex > dragOverIndex) { + beingDragged.after(dragOver); + } + } else { + dragOver.before(beingDragged); + } + } + + function colDraggedOver(event) { + let dragOver = event.target; + let beingDragged = document.querySelector('.dragging'); + let draggedParent = beingDragged.parentElement; + if ( + draggedParent.id !== dragOver.id && + draggedParent.classList.contains('col') && + dragOver.classList.contains('col') + ) { + if (dragOver.childElementCount == 0) { + dragOver.appendChild(beingDragged); + } + } + } + + + + + + + function handleMappingTabClick() { + loadMappingReview(false); + // Refresh the hidden field with the latest form version ID + let refreshAvailableWorkSheets = UIElements.refreshAvailableWorksheetsHidden.val(); + if (refreshAvailableWorkSheets && refreshAvailableWorkSheets !== "undefined") { + navigateToVersion(refreshAvailableWorkSheets); + } + } + + PubSub.subscribe( + 'refresh_available_worksheets', + (_, data) => { + UIElements.refreshAvailableWorksheetsHidden.val(data.chefsFormVersionId); + } + ); + + +}); + + +function handleBack() { + location.href = '/ApplicationForms'; +} + +function beingDragged(ev) { + let draggedEl = ev.target; + if (draggedEl.classList + "" !== "undefined") { + draggedEl.classList.add('dragging'); + } +} + +function dragEnd(ev) { + let draggedEl = ev.target; + if (draggedEl.classList + "" !== "undefined") { + draggedEl.classList.remove('dragging'); + } +} From c42b164542069106235156c871922b89b08f07c9 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Thu, 13 Aug 2026 08:46:12 -0700 Subject: [PATCH 080/121] AB#33864 add AI scoresheet suggestion review flow --- .../IApplicationFormVersionService.cs | 3 + .../Mapping/AiScoresheetReviewDto.cs | 30 +++ .../Mapping/CreateAiScoresheetDraftDto.cs | 16 ++ .../ApplicationFormVersionAppService.cs | 187 ++++++++++++++- .../FormScoresheetOperationExecutor.cs | 33 ++- .../AiSuggestionReviewModalModel.cs | 1 + .../Pages/ApplicationForms/Mapping.cshtml | 49 ++-- .../Pages/ApplicationForms/Mapping.css | 21 -- .../Pages/ApplicationForms/Mapping.js | 213 +++++++++++++++--- .../_AiSuggestionReviewModal.cshtml | 2 +- .../Pages/GrantApplications/Details.cshtml | 5 +- .../Pages/GrantApplications/Details.css | 35 --- .../ai-generation-button.css | 29 +++ .../AssessmentScoresWidget/Default.cshtml | 2 +- .../AssessmentScoresWidget/Default.js | 3 - .../Components/CustomFields/Default.css | 18 ++ ...FormVersionAppServiceMappingReviewTests.cs | 4 +- .../ApplicationFormVersionAppServiceTests.cs | 4 +- 18 files changed, 527 insertions(+), 128 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetReviewDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/CreateAiScoresheetDraftDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-generation-button.css diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormVersionService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormVersionService.cs index 5c40979306..2c9d1bc64a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormVersionService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/IApplicationFormVersionService.cs @@ -27,6 +27,9 @@ public interface IApplicationFormVersionAppService : ICrudAppService< Task GetPendingAiWorksheetAsync(Guid formVersionId); Task CreateAiWorksheetDraftAsync(Guid formVersionId, CreateAiWorksheetDraftDto input); Task DiscardAiWorksheetSuggestionsAsync(Guid formVersionId); + Task GetPendingAiScoresheetAsync(Guid formVersionId); + Task CreateAiScoresheetDraftAsync(Guid formVersionId, CreateAiScoresheetDraftDto input); + Task DiscardAiScoresheetSuggestionsAsync(Guid formVersionId); Task GetMappingReviewAsync(Guid formVersionId); Task AcceptMappingSuggestionsAsync(Guid formVersionId, AcceptMappingSuggestionsDto input); Task DiscardMappingSuggestionsAsync(Guid formVersionId); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetReviewDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetReviewDto.cs new file mode 100644 index 0000000000..5b4e5cf92c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetReviewDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Unity.GrantManager.ApplicationForms.Mapping; + +public sealed class AiScoresheetReviewDto +{ + public Guid SessionId { get; set; } + public string Title { get; set; } = string.Empty; + public List Sections { get; set; } = []; +} + +public sealed class AiScoresheetReviewSectionDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public uint Order { get; set; } + public List Questions { get; set; } = []; +} + +public sealed class AiScoresheetReviewQuestionDto +{ + public Guid Id { get; set; } + public Guid SectionId { get; set; } + public string Name { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public string? Description { get; set; } + public string Type { get; set; } = string.Empty; + public bool Selected { get; set; } = true; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/CreateAiScoresheetDraftDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/CreateAiScoresheetDraftDto.cs new file mode 100644 index 0000000000..435f4d9d72 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/CreateAiScoresheetDraftDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Unity.GrantManager.ApplicationForms.Mapping; + +public sealed class CreateAiScoresheetDraftDto +{ + public Guid SessionId { get; set; } + + [Required] + public string Title { get; set; } = string.Empty; + + [MinLength(1)] + public List SelectedQuestionIds { get; set; } = []; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs index d53134bbab..85695cad2c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs @@ -11,6 +11,8 @@ using Unity.AI.Generation; using Unity.AI.Permissions; using Unity.Flex.Domain.Worksheets; +using Unity.Flex.Domain.Scoresheets; +using Unity.Flex.Scoresheets.Enums; using Unity.GrantManager.ApplicationForms.Mapping; using Unity.GrantManager.Applications; using Unity.GrantManager.Forms; @@ -26,7 +28,6 @@ using Volo.Abp.Domain.Repositories; using Volo.Abp.Features; using Volo.Abp.Uow; -using Unity.Flex.Domain.Worksheets; using Unity.Flex.Domain.WorksheetLinks; using Unity.Modules.Shared.Correlation; @@ -45,7 +46,8 @@ public class ApplicationFormVersionAppService( IWorksheetRepository worksheetRepository, IRepository customFieldRepository, IGenerationReviewRepository generationReviewRepository, - IWorksheetLinkRepository worksheetLinkRepository) : + IWorksheetLinkRepository worksheetLinkRepository, + IScoresheetRepository scoresheetRepository) : CrudAppService< ApplicationFormVersion, ApplicationFormVersionDto, @@ -510,6 +512,7 @@ public virtual async Task ResetAiFlowAsync(Guid formVersionId) var formVersion = await Repository.GetAsync(formVersionId); var mappingReviews = await generationReviewRepository.GetListByOperationAndFormVersionAsync(AIGenerationOperations.FormMapping, formVersionId); var worksheetReviews = await generationReviewRepository.GetListByOperationAndFormVersionAsync(AIGenerationOperations.FormWorksheet, formVersionId); + var scoresheetReviews = await generationReviewRepository.GetListByOperationAndFormVersionAsync(AIGenerationOperations.FormScoresheet, formVersionId); var worksheetIds = worksheetReviews .SelectMany(review => GetWorksheetReviewPayload(review).DraftWorksheetIds) .Distinct() @@ -530,7 +533,15 @@ public virtual async Task ResetAiFlowAsync(Guid formVersionId) } } - await generationReviewRepository.DeleteManyAsync(mappingReviews.Concat(worksheetReviews), true); + var resetFormVersion = await formVersionRepository.GetAsync(formVersionId); + var suggestionScoresheet = await scoresheetRepository.GetByNameAsync( + BuildAiScoresheetSuggestionName(resetFormVersion.ApplicationFormId, resetFormVersion.Id), true); + if (suggestionScoresheet != null) + { + await scoresheetRepository.DeleteAsync(suggestionScoresheet, true); + } + + await generationReviewRepository.DeleteManyAsync(mappingReviews.Concat(worksheetReviews).Concat(scoresheetReviews), true); formVersion.SubmissionHeaderMapping = "{}"; await Repository.UpdateAsync(formVersion, true); } @@ -685,6 +696,176 @@ public virtual async Task DiscardAiWorksheetSuggestionsAsync(Guid formVersionId) } } + [HttpGet("api/app/application-form-version/pending-ai-scoresheet")] + public virtual async Task GetPendingAiScoresheetAsync(Guid formVersionId) + { + await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormScoresheet); + + var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( + AIGenerationOperations.FormScoresheet, + formVersionId); + if (review == null || review.Status != GenerationReviewStatus.Active) + { + return null; + } + + var formVersion = await formVersionRepository.GetAsync(formVersionId); + var scoresheet = await scoresheetRepository.GetByNameAsync( + BuildAiScoresheetSuggestionName(formVersion.ApplicationFormId, formVersion.Id), true); + return scoresheet?.Published == false ? MapAiScoresheetReview(scoresheet) : null; + } + + [HttpPost("api/app/application-form-version/create-ai-scoresheet-draft")] + public virtual async Task CreateAiScoresheetDraftAsync(Guid formVersionId, CreateAiScoresheetDraftDto input) + { + await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormScoresheet); + + var suggestion = await GetPendingAiScoresheetEntityAsync(formVersionId); + if (suggestion == null || suggestion.Id != input.SessionId) + { + throw new UserFriendlyException("The AI scoresheet is no longer available for review."); + } + + var title = input.Title?.Trim(); + if (string.IsNullOrWhiteSpace(title)) + { + throw new UserFriendlyException("A scoresheet title is required."); + } + + var selectedIds = input.SelectedQuestionIds?.ToHashSet() ?? []; + if (selectedIds.Count == 0) + { + throw new UserFriendlyException("Select at least one suggested question."); + } + + var questions = suggestion.Sections.SelectMany(section => section.Fields).ToList(); + if (selectedIds.Except(questions.Select(question => question.Id)).Any()) + { + throw new UserFriendlyException("The AI scoresheet selection is invalid."); + } + + var draft = new Scoresheet(GuidGenerator.Create(), title, $"ai-scoresheet-draft-{GuidGenerator.Create():N}"); + foreach (var sourceSection in suggestion.Sections.OrderBy(section => section.Order)) + { + var selectedQuestions = sourceSection.Fields + .Where(question => selectedIds.Contains(question.Id)) + .OrderBy(question => question.Order) + .ToList(); + if (selectedQuestions.Count == 0) + { + continue; + } + + var section = new ScoresheetSection(GuidGenerator.Create(), sourceSection.Name, sourceSection.Order); + draft.AddSection(section); + foreach (var sourceQuestion in selectedQuestions) + { + var draftQuestion = new Question( + GuidGenerator.Create(), + sourceQuestion.Name, + sourceQuestion.Label, + sourceQuestion.Type, + sourceQuestion.Order, + sourceQuestion.Description, + sourceQuestion.Definition) + { + SectionId = section.Id + }; + section.Fields.Add(draftQuestion); + } + } + + await scoresheetRepository.InsertAsync(draft, true); + + foreach (var question in questions.Where(question => selectedIds.Contains(question.Id)).ToList()) + { + var sourceSection = suggestion.Sections.First(section => section.Fields.Contains(question)); + sourceSection.Fields.Remove(question); + } + + if (suggestion.Sections.All(section => section.Fields.Count == 0)) + { + await scoresheetRepository.DeleteAsync(suggestion, true); + var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( + AIGenerationOperations.FormScoresheet, + formVersionId); + review?.Complete(); + if (review != null) + { + await generationReviewRepository.UpdateAsync(review, true); + } + } + else + { + await scoresheetRepository.UpdateAsync(suggestion, true); + } + } + + [HttpPost("api/app/application-form-version/discard-ai-scoresheet-suggestions")] + public virtual async Task DiscardAiScoresheetSuggestionsAsync(Guid formVersionId) + { + await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormScoresheet); + var suggestion = await GetPendingAiScoresheetEntityAsync(formVersionId); + if (suggestion == null) + { + return; + } + + await scoresheetRepository.DeleteAsync(suggestion, true); + var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( + AIGenerationOperations.FormScoresheet, + formVersionId); + if (review != null) + { + review.Discard(); + await generationReviewRepository.UpdateAsync(review, true); + } + } + + private async Task GetPendingAiScoresheetEntityAsync(Guid formVersionId) + { + var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( + AIGenerationOperations.FormScoresheet, + formVersionId); + if (review == null || review.Status != GenerationReviewStatus.Active) + { + return null; + } + + var formVersion = await formVersionRepository.GetAsync(formVersionId); + var scoresheet = await scoresheetRepository.GetByNameAsync( + BuildAiScoresheetSuggestionName(formVersion.ApplicationFormId, formVersion.Id), true); + return scoresheet?.Published == false ? scoresheet : null; + } + + private static string BuildAiScoresheetSuggestionName(Guid formId, Guid formVersionId) => + $"ai-form-{formId}-version-{formVersionId}-scoresheet"; + + private static AiScoresheetReviewDto MapAiScoresheetReview(Scoresheet scoresheet) => new() + { + SessionId = scoresheet.Id, + Title = scoresheet.Title, + Sections = scoresheet.Sections + .OrderBy(section => section.Order) + .Select(section => new AiScoresheetReviewSectionDto + { + Id = section.Id, + Name = section.Name, + Order = section.Order, + Questions = section.Fields.OrderBy(question => question.Order) + .Select(question => new AiScoresheetReviewQuestionDto + { + Id = question.Id, + SectionId = section.Id, + Name = question.Name, + Label = question.Label, + Description = question.Description, + Type = question.Type.ToString(), + Selected = true + }).ToList() + }).ToList() + }; + private async Task GetPendingAiWorksheetEntityAsync(Guid formVersionId) { var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Operations/FormScoresheet/FormScoresheetOperationExecutor.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Operations/FormScoresheet/FormScoresheetOperationExecutor.cs index 158ccd47ff..736802c167 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Operations/FormScoresheet/FormScoresheetOperationExecutor.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/Automation/Operations/FormScoresheet/FormScoresheetOperationExecutor.cs @@ -16,6 +16,7 @@ using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; +using Volo.Abp.Guids; using Unity.GrantManager.GrantApplications.Automation.BackgroundJobs; @@ -25,7 +26,9 @@ public sealed class FormScoresheetOperationExecutor( IApplicationFormVersionRepository applicationFormVersionRepository, IApplicationFormRepository applicationFormRepository, IScoresheetRepository scoresheetRepository, - IFormScoresheetService aiService) : AIGenerationOperationExecutor, ITransientDependency + IFormScoresheetService aiService, + IGenerationReviewRepository generationReviewRepository, + IGuidGenerator guidGenerator) : AIGenerationOperationExecutor, ITransientDependency { private static readonly JsonSerializerOptions CaseInsensitiveJsonOptions = new() { @@ -41,10 +44,15 @@ protected override async Task ExecuteAsync(AIGenerationBackgroundJobArgs a var formVersion = await applicationFormVersionRepository.GetAsync(applicationFormVersionId); var applicationForm = await applicationFormRepository.GetAsync(formVersion.ApplicationFormId); var scoresheetName = BuildScoresheetName(formVersion.Id, applicationForm.Id); - var existingScoresheet = await scoresheetRepository.GetByNameAsync(scoresheetName, true) - ?? (applicationForm.ScoresheetId.HasValue - ? await scoresheetRepository.GetWithChildrenAsync(applicationForm.ScoresheetId.Value) - : null); + var existingScoresheet = await scoresheetRepository.GetByNameAsync(scoresheetName, true); + var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( + AIGenerationOperations.FormScoresheet, + applicationFormVersionId); + + if (existingScoresheet != null && review?.Status == GenerationReviewStatus.Active) + { + return false; + } var promptData = new { @@ -95,7 +103,7 @@ protected override async Task ExecuteAsync(AIGenerationBackgroundJobArgs a var scoresheet = existingScoresheet == null ? BuildScoresheet(importDto, scoresheetJson, scoresheetName) : RebuildScoresheet(existingScoresheet, importDto, scoresheetJson, scoresheetName); - scoresheet.Published = true; + scoresheet.Published = false; if (existingScoresheet == null) { await scoresheetRepository.InsertAsync(scoresheet); @@ -105,8 +113,17 @@ protected override async Task ExecuteAsync(AIGenerationBackgroundJobArgs a await scoresheetRepository.UpdateAsync(scoresheet); } - applicationForm.ScoresheetId = scoresheet.Id; - await applicationFormRepository.UpdateAsync(applicationForm); + if (review == null || review.Status != GenerationReviewStatus.Active) + { + review = new GenerationReview( + guidGenerator.Create(), + AIGenerationOperations.FormScoresheet, + applicationFormVersionId, + review?.Sequence + 1 ?? 1); + await generationReviewRepository.InsertAsync(review); + } + + await generationReviewRepository.UpdateAsync(review, true); return true; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/AiSuggestionReviewModalModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/AiSuggestionReviewModalModel.cs index 6dcf374e2c..08a9343544 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/AiSuggestionReviewModalModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/AiSuggestionReviewModalModel.cs @@ -19,4 +19,5 @@ public sealed class AiSuggestionReviewModalModel public bool PrimaryActionDisabled { get; init; } public string ReviewLaterActionId { get; init; } = string.Empty; public string DiscardActionId { get; init; } = string.Empty; + public string? SectionDataAttribute { get; init; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml index f81cd3947e..e287d6f57d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml @@ -57,6 +57,27 @@ ReviewLaterActionId = "btn-review-later-ai-worksheet", DiscardActionId = "btn-discard-ai-worksheet" }; + var scoresheetReviewModal = new AiSuggestionReviewModalModel + { + ModalId = "aiScoresheetReviewModal", + ModalLabelId = "aiScoresheetReviewModalLabel", + Title = "Review AI Scoresheet Suggestions", + SourceColumnTitle = "Generated Question", + TargetColumnTitle = "Section", + FieldsId = "aiScoresheetReviewFields", + EmptyId = "aiScoresheetReviewEmpty", + EmptyText = "No scoresheet questions remain.", + SelectAllId = "aiScoresheetReviewSelectAll", + TitleInputId = "aiScoresheetTitle", + TitleInputLabel = "Scoresheet Title", + TitleInputPlaceholder = "e.g., Application Assessment", + PrimaryActionId = "btn-create-ai-scoresheet-draft", + PrimaryActionText = "Add to Scoresheet", + PrimaryActionDisabled = true, + ReviewLaterActionId = "btn-review-later-ai-scoresheet", + DiscardActionId = "btn-discard-ai-scoresheet", + SectionDataAttribute = "section-id" + }; } @section scripts { @@ -69,6 +90,7 @@ } @section styles { + } @@ -203,31 +225,17 @@ Review Final Mapping - - } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate)) - { - } @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate) && await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate)) { - } +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css index 6a1f0cdb2b..ad0d78d58c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.css @@ -31,17 +31,6 @@ margin-bottom: 0.5rem; } -.ai-button-content { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.ai-generate-btn { - height: 2.25rem; -} - .configuration-action-bar h5 { font-weight: 700; } @@ -159,16 +148,6 @@ tr:nth-child(even) {background-color: #f2f2f2;} display: block; } -.ai-generate-btn:disabled, -.ai-generate-btn.disabled, -.ai-generate-btn[data-ai-shared-generating='1'], -.ai-generate-btn[data-ai-cooldown-active='1'], -.ai-generate-btn[data-ai-cooldown-checking='1'] { - cursor: not-allowed; - opacity: 0.55; - pointer-events: none; -} - .buttons-div { display: inline-flex; padding: 20px; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index 8c852776fa..72afccfb4a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -61,11 +61,16 @@ btnGenerateWorksheet: $('#btn-generate-worksheet'), btnGenerateScoresheet: $('#btn-generate-scoresheet'), btnReviewWorksheet: $('#btn-review-worksheet'), + scoresheetReviewModal: $('#aiScoresheetReviewModal'), + scoresheetReviewFields: $('#aiScoresheetReviewFields'), + scoresheetReviewEmpty: $('#aiScoresheetReviewEmpty'), + scoresheetReviewSelectAll: $('#aiScoresheetReviewSelectAll'), + scoresheetTitle: $('#aiScoresheetTitle'), + btnCreateScoresheetDraft: $('#btn-create-ai-scoresheet-draft'), + btnDiscardScoresheet: $('#btn-discard-ai-scoresheet'), btnPublishAssignWorksheets: $('#btn-publish-assign-worksheets'), btnGenerateFinalMapping: $('#btn-generate-final-mapping'), btnReviewFinalMapping: $('#btn-review-final-mapping'), - btnGenerateNextMapping: $('#btn-generate-next-mapping'), - btnGenerateNextWorksheets: $('#btn-generate-next-worksheets'), btnRestartAiFlow: $('#btn-restart-ai-flow'), worksheetReviewModal: $('#aiWorksheetReviewModal'), mappingReviewModal: $('#aiMappingReviewModal'), @@ -107,6 +112,7 @@ setupTooltips(); initializeUIConfiguration(); loadMappingReview(false); + loadAiScoresheetReview(false); } function setupTooltips() { @@ -140,10 +146,14 @@ }); UIElements.btnGenerateWorksheet.on('click', queueFormWorksheet); UIElements.btnGenerateFinalMapping.on('click', finalizeMappingReview); - UIElements.btnGenerateNextMapping.on('click', queueFormMapping); - UIElements.btnGenerateNextWorksheets.on('click', queueFormWorksheet); UIElements.btnRestartAiFlow.on('click', restartAiFlow); UIElements.btnGenerateScoresheet.on('click', queueFormScoresheet); + UIElements.btnCreateScoresheetDraft.on('click', createAiScoresheetDraft); + UIElements.btnDiscardScoresheet.on('click', discardAiScoresheetSuggestions); + UIElements.scoresheetReviewFields.on('change', 'input[data-question-id]', updateAiScoresheetReview); + UIElements.scoresheetReviewFields.on('change', 'input[data-section-id]', toggleAiScoresheetSection); + UIElements.scoresheetReviewSelectAll.on('change', toggleAiScoresheetReviewAll); + UIElements.scoresheetTitle.on('input', updateAiScoresheetDraftButton); UIElements.btnReviewWorksheet.on('click', loadAiWorksheetReview); UIElements.btnAddMapping.on('click', addSelectedMappingSuggestion); UIElements.btnReviewLaterMapping.on('click', function () { @@ -232,6 +242,11 @@ const $button = $(buttonElement); const existingHtml = $button.html(); + if ($button.attr('data-ai-pending') === 'true') { + loadAiScoresheetReview(true); + return; + } + if ($button.prop('disabled')) { return; } @@ -275,7 +290,6 @@ } const buttonElement = triggerButton?.currentTarget || triggerButton?.target || triggerButton || UIElements.btnGenerateWorksheet?.get?.(0); - const $button = $(buttonElement); if (isAiWorksheetPending()) { loadAiWorksheetReview(); @@ -283,7 +297,7 @@ } startWorksheetPhase(function () { - queueFormWorksheetCore(triggerButton); + queueFormWorksheetCore(buttonElement); }); } @@ -294,10 +308,6 @@ const $button = $(buttonElement); const existingHtml = $button.html(); - if ($button.prop('disabled')) { - return; - } - globalThis.AIGenerationButtonState?.setGenerating($button); abp.ajax({ @@ -611,6 +621,7 @@ UIElements.mappingReviewModal.modal('hide'); abp.notify.success('', 'Publish and assign the worksheet drafts, then return here to generate mapping.'); loadMappingReview(false); + loadAiScoresheetReview(false); } function finalizeMappingReview() { @@ -660,10 +671,165 @@ } function refreshScoresheetAfterGeneration() { - abp.notify.success('', 'Scoresheet generated and assigned successfully. Reloading page.'); - setTimeout(function () { - globalThis.location.reload(); - }, 500); + setAiScoresheetPending(true); + abp.notify.success('', 'Scoresheet suggestions are ready for review.'); + loadAiScoresheetReview(true); + } + + function setAiScoresheetPending(isPending) { + UIElements.btnGenerateScoresheet.attr('data-ai-pending', isPending ? 'true' : 'false'); + UIElements.btnGenerateScoresheet.find('.ai-button-content span:last-child').text( + isPending ? 'Review Scoresheet' : 'Generate Scoresheet'); + } + + function loadAiScoresheetReview(showModal = true) { + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); + if (!validateGuid(formVersion)) { + return; + } + + return abp.ajax({ + url: `/api/app/application-form-version/pending-ai-scoresheet?formVersionId=${encodeURIComponent(formVersion)}`, + type: 'GET' + }).done(function (review) { + if (!review) { + setAiScoresheetPending(false); + UIElements.scoresheetReviewModal.modal('hide'); + return; + } + + setAiScoresheetPending(true); + renderAiScoresheetReview(review); + if (showModal) { + UIElements.scoresheetReviewModal.modal('show'); + } + }).fail(function () { + if (showModal) { + abp.notify.error('', 'Unable to load AI scoresheet suggestions.'); + } + }); + } + + function renderAiScoresheetReview(review) { + UIElements.scoresheetReviewFields.empty(); + UIElements.scoresheetReviewSelectAll.prop('checked', false); + UIElements.scoresheetTitle.val(review.title || ''); + UIElements.scoresheetReviewFields.attr('data-session-id', review.sessionId || ''); + + const sections = review.sections || []; + UIElements.scoresheetReviewEmpty.toggle(sections.length === 0); + sections.forEach(function (section) { + const $section = $('
', { class: 'ai-suggestion-review__section' }); + const $header = $('
', { class: 'ai-suggestion-review__section-header' }); + $('', { text: section.name || 'Section' }).appendTo($header); + $('
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml index 1f53b0b08a..4f5e8c5a80 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml @@ -12,9 +12,10 @@
Scoresheet & Worksheets Configuration
- @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.Generate)) + @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.View)) { } + @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.View)) + { + + } Date: Fri, 14 Aug 2026 10:06:46 -0700 Subject: [PATCH 100/121] AB#33864 use native JSON parsing in mapping --- .../Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index 149026f192..81da070e3c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -1323,10 +1323,10 @@ $(function () { function handleSaveEditMapping() { try { let jsonText = $('#jsonText').val(); - $.parseJSON(jsonText); + JSON.parse(jsonText); let mappingJsonStr = jsonText.replaceAll(/\s+/g, ' ').replaceAll(/(\r\n|\n|\r)/gm, ""); UIElements.btnSaveMapping.prop('disabled', true); - handleSaveMapping($.parseJSON(mappingJsonStr)); + handleSaveMapping(JSON.parse(mappingJsonStr)); handleCancelMapping(); abp.notify.success( From 27ffddb43c3176e79651d8a6ff1c724e98033466 Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Fri, 14 Aug 2026 10:08:17 -0700 Subject: [PATCH 101/121] AB#33864 log saved mapping apply failures --- .../src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index 81da070e3c..f6d6de9c81 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -1403,6 +1403,7 @@ $(function () { } } } catch (err) { + console.error('Unable to apply saved mapping.', err); abp.notify.error('', aiL('AI:SavedMappingApplyFailed')); } } From ba2096e9982ce7ce50ab1201f45c1e0d3367f28b Mon Sep 17 00:00:00 2001 From: Velang Date: Fri, 14 Aug 2026 12:34:16 -0700 Subject: [PATCH 102/121] inital commit --- .../cypress/regression/ApprovalFlow.cy.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts b/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts index 9e58c6ddeb..1f287c3ab7 100644 --- a/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts +++ b/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts @@ -619,6 +619,25 @@ const APPLICATIONS_PATH = "GrantApplications"; .enterSupplierNumber(TEST_CONFIG.supplierNumber) .clickElsewhere() .clickPaymentInfoSave(); + + // Saving the supplier number calls out to CAS to resolve it, which can + // transiently fail with "GetAuthTokenAsync: Error retrieving Token". + // When that happens the save silently doesn't attach a supplier, leaving + // SupplierId empty for the rest of the flow — dismiss and retry once. + cy.get("body").then(($body) => { + const hasTokenError = + $body.text().includes("GetAuthTokenAsync") || + $body.text().includes("Error retrieving Token"); + + if (hasTokenError) { + cy.log("⚠️ Transient CAS token error on payment save — retrying once"); + detailsPage.dismissErrorModalIfPresent(); + detailsPage + .enterSupplierNumber(TEST_CONFIG.supplierNumber) + .clickElsewhere() + .clickPaymentInfoSave(); + } + }); }); // Must use function() (not arrow) so this.skip() is accessible From cd1280992d93b43eaadb2a23c9cc36329ec8657d Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Fri, 14 Aug 2026 14:25:15 -0700 Subject: [PATCH 103/121] feature/AB#33998-AttachementScheduleNotifications --- .../Emails/EmailAttachmentService.cs | 57 ++++++++ .../Events/EmailNotificationHandler.cs | 20 +++ .../NotificationsSettingGroup/Default.js | 123 ++++++++++++++-- .../FormNotificationsApiController.cs | 35 ++++- .../FormConfiguration/Notifications.cshtml | 1 - .../Components/Notifications/Default.cshtml | 7 + .../Components/Notifications/Default.css | 133 +++++------------- .../Components/Notifications/Default.js | 121 +++++++++++++++- 8 files changed, 380 insertions(+), 117 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs index a68c3109cc..0eadcb2dfb 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs @@ -178,6 +178,63 @@ public async Task> GetAttachmentsAsync(Guid emailLogId) return await _emailLogAttachmentRepository.GetByEmailLogIdAsync(emailLogId); } + public async Task CopyTemplateAttachmentsAsync(Guid templateId, Guid emailLogId, Guid? tenantId) + { + var templateAttachments = await _emailLogAttachmentRepository.GetByTemplateIdAsync(templateId); + var existingAttachments = await _emailLogAttachmentRepository.GetByEmailLogIdAsync(emailLogId); + // Dedup by (FileName, FileSize, ContentType) rather than S3ObjectKey: each copy gets its own + // S3 object (see below), so a re-run of this method for the same emailLogId/templateId would + // never see a matching key even though the attachment was already copied. + var alreadyCopied = existingAttachments + .Where(a => a.OriginTemplateId == templateId) + .Select(a => (a.FileName, a.FileSize, a.ContentType)) + .ToHashSet(); + + var bucket = _configuration[S3BucketConfigKey]; + var copiedAttachmentCount = 0; + foreach (var templateAttachment in templateAttachments) + { + var identity = (templateAttachment.FileName, templateAttachment.FileSize, templateAttachment.ContentType); + if (!alreadyCopied.Add(identity)) + { + continue; + } + + // Physically duplicate the S3 object under a new key instead of pointing at the + // template attachment's own key. EmailLogAttachmentAppService.DeleteAsync deletes the + // underlying S3 object whenever a template attachment (TemplateId.HasValue) is removed; + // sharing the key would silently break the attachment on every scheduled email that had + // already copied it. + var copiedS3Key = BuildUserAttachmentS3Key( + tenantId, emailLogId, Guid.NewGuid(), templateAttachment.FileName ?? templateAttachment.DisplayName ?? "attachment"); + await _amazonS3Client.CopyObjectAsync(new CopyObjectRequest + { + SourceBucket = bucket, + SourceKey = templateAttachment.S3ObjectKey, + DestinationBucket = bucket, + DestinationKey = copiedS3Key + }); + + await _emailLogAttachmentRepository.InsertAsync(new EmailLogAttachment + { + EmailLogId = emailLogId, + TemplateId = null, + OriginTemplateId = templateId, + S3ObjectKey = copiedS3Key, + FileName = templateAttachment.FileName, + DisplayName = templateAttachment.DisplayName, + ContentType = templateAttachment.ContentType, + FileSize = templateAttachment.FileSize, + Time = DateTime.UtcNow, + UserId = Guid.Empty, + TenantId = tenantId + }); + copiedAttachmentCount++; + } + + return copiedAttachmentCount; + } + public async Task GetTotalFileSizeAsync(Guid? emailLogId, Guid? templateId) { if(emailLogId != null) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs index 580deb3c88..05be5543b9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationHandler.cs @@ -229,6 +229,26 @@ private async Task InitializeEmail(EmailInitParams p, string status) emailLog.ScheduledNotificationId = eventData.ScheduledNotificationId.Value; await emailLogsRepository.UpdateAsync(emailLog, autoSave: true); } + + if (eventData.ScheduledNotificationId.HasValue && eventData.TemplateId != Guid.Empty) + { + try + { + var copiedAttachmentCount = await emailAttachmentService.CopyTemplateAttachmentsAsync( + eventData.TemplateId, emailLog.Id, emailLog.TenantId); + _logger.LogInformation( + "Copied {AttachmentCount} template attachments for scheduled notification {ScheduledNotificationId}.", + copiedAttachmentCount, eventData.ScheduledNotificationId.Value); + } + catch (Exception ex) + { + _logger.LogError(ex, + "Failed to copy template attachments for scheduled notification {ScheduledNotificationId}. Email will be sent WITHOUT attachments.", + eventData.ScheduledNotificationId.Value); + // DO NOT THROW - matches InitializeEmailAndUploadAttachments: an attachment + // failure should not block the email from being created/sent. + } + } await StampClassificationAsync(emailLog); return emailLog; diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js index d62a88a5de..9a43495087 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js @@ -29,6 +29,7 @@ $(function () { let emailAttachmentsTable = null; let templatesDataTable = null; let originalFormValues = {}; + let attachmentChangesPending = false; function init() { $('#email-attachments-section').hide(); @@ -296,6 +297,7 @@ $(function () { UiElements.deleteButton.show(); $('#email-attachments-section').show(); + attachmentChangesPending = false; initEmailAttachmentsTable(data.id); // Recalculate table columns after initialization @@ -328,6 +330,7 @@ $(function () { $('#templateRecipientSelect').empty().val([]).trigger('change'); UiElements.deleteButton.hide(); $('#email-attachments-section').hide(); + attachmentChangesPending = false; // Don't load attachments for new templates - they have no ID yet } @@ -424,6 +427,7 @@ $(function () { }; const isNewTemplate = !templateId || templateId.trim() === ''; + const templateChangesPending = hasTemplateChanges(templateData) || attachmentChangesPending; // Check template name uniqueness before saving checkTemplateNameUnique(templateName.trim(), templateId, function (isUnique) { @@ -431,10 +435,56 @@ $(function () { markFieldError('templateName', 'Template name must be unique.'); return; } - performSave(isNewTemplate, templateId, templateData, templateName, sendFrom, subject, bodyHTML); + confirmTemplateAttachmentImpact(templateId, isNewTemplate, templateChangesPending) + .then(function (confirmed) { + if (confirmed) { + performSave(isNewTemplate, templateId, templateData, templateName, sendFrom, subject, bodyHTML); + } + }); }); }); + function hasTemplateChanges(templateData) { + const original = originalFormValues; + const fields = ['name', 'description', 'sendFrom', 'subject', 'bodyText', 'bodyHTML', 'recipientCategory', 'recipientIdentifier']; + + return fields.some(field => String(templateData[field] ?? '') !== String(original[field] ?? '')); + } + + function confirmTemplateAttachmentImpact(templateId, isNewTemplate, templateChangesPending) { + if (isNewTemplate || !templateChangesPending) { + return Promise.resolve(true); + } + + return $.ajax({ + url: `/api/form-notifications/template-notification-plans/${encodeURIComponent(templateId)}`, + type: 'GET', + dataType: 'json' + }).then(function (response) { + const planNames = response.notificationPlanNames || []; + if (planNames.length === 0) { + return true; + } + + return Swal.fire({ + icon: 'warning', + title: 'Template changes', + html: `

Warning: This template is currently associated with ${planNames.length} notification plan${planNames.length === 1 ? '' : 's'}. Any changes made to this template may impact these notification plan(s).

`, + showCancelButton: true, + confirmButtonText: 'OK', + cancelButtonText: 'Cancel', + customClass: { + confirmButton: 'btn btn-primary', + cancelButton: 'btn btn-secondary' + } + }).then(result => result.isConfirmed); + }).catch(function (e) { + console.warn('Failed to check template notification plans:', e); + abp.notify.error('Unable to verify whether this template is used by a notification plan. The template was not saved.'); + return false; + }); + } + function performSave(isNewTemplate, templateId, templateData, templateName, sendFrom, subject, bodyHTML) { if (isNewTemplate) { // Create new template @@ -472,6 +522,7 @@ $(function () { unity.notifications.templates.template .updateTemplate(templateId, templateData) .then(function () { + attachmentChangesPending = false; abp.notify.success('Template updated successfully.'); // Update original values after successful save originalFormValues = { @@ -481,7 +532,9 @@ $(function () { sendFrom: sendFrom, subject: subject, bodyText: '', - bodyHTML: bodyHTML + bodyHTML: bodyHTML, + recipientCategory: templateData.recipientCategory || '', + recipientIdentifier: templateData.recipientIdentifier || '' }; PubSub.publish('reload_templates_table_no_close'); }) @@ -969,6 +1022,7 @@ $(function () { $('#attachment-upload-progress').show(); }, success: function () { + attachmentChangesPending = true; PubSub.publish('reload_email_attachments_table'); }, error: function (xhr) { @@ -1047,6 +1101,10 @@ $(function () { reloadEmailAttachmentsTable(); }); + PubSub.subscribe('template_attachment_changed', () => { + attachmentChangesPending = true; + }); + function reloadEmailAttachmentsTable() { if (emailAttachmentsTable) { emailAttachmentsTable.ajax.reload(); @@ -1162,14 +1220,24 @@ function generateEmailAttachmentButtonContent(attachmentId) { * @param {string} attachmentId - Attachment ID to delete */ function deleteEmailAttachment(attachmentId) { - abp.message.confirm( - 'Are you sure you want to delete this attachment?', - 'Delete Attachment', - function (confirmed) { - if (confirmed) { + const templateId = $('#templateId').val(); + const planImpactCheck = isConfigurationManagementTemplateEditor() + ? checkScheduledPlanImpactForAttachmentDelete(templateId) + : Promise.resolve(true); + + planImpactCheck.then(function (confirmed) { + if (!confirmed) return; + + abp.message.confirm( + 'Are you sure you want to delete this attachment?', + 'Delete Attachment', + function (deleteConfirmed) { + if (!deleteConfirmed) return; + unity.notifications.emails.emailLogAttachment .delete(attachmentId) .then(function () { + PubSub.publish('template_attachment_changed'); abp.notify.success('Attachment deleted successfully.'); PubSub.publish('reload_email_attachments_table'); }) @@ -1178,8 +1246,45 @@ function deleteEmailAttachment(attachmentId) { abp.notify.error('Failed to delete attachment.'); }); } - } - ); + ); + }); +} + +function isConfigurationManagementTemplateEditor() { + return window.location.pathname.toLowerCase() === '/configurationmanagement' && + $('#nav-template').length > 0 && + $('#TemplatesTable').length > 0 && + $('#templateId').length > 0; +} + +function checkScheduledPlanImpactForAttachmentDelete(templateId) { + if (!templateId) return Promise.resolve(true); + + return $.ajax({ + url: `/api/form-notifications/template-notification-plans/${encodeURIComponent(templateId)}`, + type: 'GET', + dataType: 'json' + }).then(function (response) { + const planNames = response.notificationPlanNames || []; + if (planNames.length === 0) return true; + + return Swal.fire({ + icon: 'warning', + title: 'Scheduled notification impact', + html: `

Warning: This template is currently associated with ${planNames.length} notification plan${planNames.length === 1 ? '' : 's'}. Any changes made to this template may impact these notification plan(s).

`, + showCancelButton: true, + confirmButtonText: 'OK', + cancelButtonText: 'Cancel', + customClass: { + confirmButton: 'btn btn-primary', + cancelButton: 'btn btn-secondary' + } + }).then(result => result.isConfirmed); + }).catch(function (e) { + console.warn('Failed to check template notification plans:', e); + abp.notify.error('Unable to verify whether this template is used by a scheduled notification plan. The attachment was not deleted.'); + return false; + }); } /** diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs index 7e6533d639..200fe8a01d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Controllers/FormNotificationsApiController.cs @@ -364,18 +364,47 @@ public async Task> CanDeleteTemplate(Guid templateId) var result = await _automatedNotificationAppService.GetListAsync( new Notifications.GetNotificationsInput { MaxResultCount = 1000 }); - var inUse = result.Items.Any(n => n.EmailTemplateId == templateId); + var associatedPlans = result.Items + .Where(n => n.EmailTemplateId == templateId) + .Select(n => + string.IsNullOrWhiteSpace(n.TriggerDetail) + ? $"{n.TriggerType} notification" + : $"{n.TriggerType} notification - {n.TriggerDetail}") + .Distinct() + .ToList(); + + var inUse = associatedPlans.Count > 0; if (inUse) { return Ok(new { canDelete = false, - errorMessage = "This template cannot be deleted because it is assigned to one or more Scheduled Notifications. Please remove the template from all Scheduled Notifications before deleting." + errorMessage = "This template cannot be deleted because it is assigned to one or more Scheduled Notifications. Please remove the template from all Scheduled Notifications before deleting.", + notificationPlanNames = associatedPlans }); } - return Ok(new { canDelete = true, errorMessage = (string?)null }); + return Ok(new { canDelete = true, errorMessage = (string?)null, notificationPlanNames = Array.Empty() }); + } + + [HttpGet("template-notification-plans/{templateId:guid}")] + public async Task> GetTemplateNotificationPlans(Guid templateId) + { + var result = await _automatedNotificationAppService.GetListAsync( + new Notifications.GetNotificationsInput { MaxResultCount = 1000 }); + var template = await _templateService.GetTemplateById(templateId); + + var templateName = template?.Name ?? "Template"; + var planNames = result.Items + .Where(n => n.IsActive && n.EmailTemplateId == templateId) + .Select(n => string.IsNullOrWhiteSpace(n.TriggerDetail) + ? $"{templateName} - {n.TriggerType} notification" + : $"{templateName} - {n.TriggerType} notification - {n.TriggerDetail}") + .Distinct() + .ToList(); + + return Ok(new { notificationPlanNames = planNames }); } [HttpPut("{formId}/{id:guid}")] diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/FormConfiguration/Notifications.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/FormConfiguration/Notifications.cshtml index 15e47c29f1..130e0e221f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/FormConfiguration/Notifications.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/FormConfiguration/Notifications.cshtml @@ -134,7 +134,6 @@
- @* Placeholder for any extra widgets *@ @await Component.InvokeAsync("Notifications", new { formid = Model.FormId })
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml index 2cbf3eb2ad..9e6ac1a458 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml @@ -122,11 +122,18 @@
+ +
+
+
+
+ Note: Email templates and attachments cannot be edited here. To make changes, update the selected template in Configuration Management. +
Note: If Recipients are not found, then no email will be drafted or sent.
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css index 2b5c676da4..2abe9124f3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css @@ -4,15 +4,6 @@ background: #f8f9fb; font-family: var(--bs-body-font-family, 'BCSans', sans-serif); font-size: var(--bc-font-size, 1rem); - display: flex; - flex: 1 1 auto; - flex-direction: column; - height: 100%; - width: 100%; - min-width: 0; - min-height: 0; - position: relative; - overflow: hidden; } .notifications-widget .card { border: 0; } .notifications-widget .card .card-body { background: #fff; } @@ -90,11 +81,11 @@ min-width: 0; } -#notificationForm { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; +#notificationForm { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; } /* Modal styling */ @@ -116,7 +107,15 @@ .notification-modal-footer { flex-shrink: 0; - background-color: #f8f9fb; +} + +#notificationModal .notification-modal-footer { + padding-bottom: 0.67rem; +} + + +#notificationModal .modal-dialog .modal-footer { + padding: 1rem 1.5rem 2rem; } @media (max-width: 991.98px) { @@ -146,7 +145,7 @@ border: 1px solid #bee5eb; color: #0c5460; padding: 12px 16px; - margin: 16px; + margin: 6px; border-radius: 4px; font-size: 0.95rem; line-height: 1.5; @@ -170,98 +169,32 @@ box-sizing: border-box; } -/* Hidden sections (display:none handled by JavaScript) */ -.hidden-section { - display: none; +.template-attachments-section { + flex-shrink: 0; + margin-top: 0; + border: 1px solid #dee2e6; + background: #fff; + box-sizing: border-box; + padding: 12px; } -/* Notifications table styling */ -.notification-table { - width: 100%; +#templateAttachmentsLabel { + margin-top: 1rem; } -.notifications-widget .dt-container { - position: relative; - display: flex; - flex-direction: column; - flex: 1 1 auto; - overflow-y: hidden; - overflow-x: hidden; +.template-attachments-section .attachments-table { width: 100%; - min-width: 0; - min-height: 0; -} - -.notifications-widget .dt-scroll-body { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} - -.notifications-widget .dt-scroll { - flex: 1 1 auto; - min-width: 0; - min-height: 0; + margin-bottom: 0; } -.notifications-widget .dt-bootstrap5 > div.dt-layout-table { - flex-grow: 1; - overflow-y: auto; - min-height: 0; -} - - -.dt-unity-footer { - position: fixed; - bottom: 0px; - left: 0px; - width: 100%; - z-index: 99999; - background-color: rgb(255, 255, 255); - box-shadow: rgba(0, 0, 0, 0.1) 0px -2px 10px; - padding: 10px; - height: 60px; - display: flex; - visibility: visible; - opacity: 1; - overflow: visible; +/* Hidden sections (display:none handled by JavaScript) */ +.hidden-section { + display: none; } -.notifications-widget .dt-unity-footer { +/* Notifications table styling */ +.notification-table { width: 100%; - display: flex; - position: fixed; - bottom: 0; - left: 0px; - height: 60px; - z-index: 999999; - background-color: white; - align-items: center; - justify-content: center; -} - -div.dt-container { - padding-bottom: 120px; - overflow: visible; -} - -body { - height: auto; - overflow: visible; - padding-bottom: 100px; -} - -:root { - height: auto; - overflow: visible; -} - -div.dt-scroll-body { - max-height: calc(-80px + 100vh); - height: calc(-80px + 100vh); - overflow: visible; - overflow-y: auto; - position: relative; } /* Notification table action buttons */ @@ -278,13 +211,13 @@ div.dt-scroll-body { .dt-column-title, table.dataTable thead th { color: #fff; font-weight: 500; - font-size: 18px; + font-size: 16px; } table.dataTable td { word-wrap: break-word; max-width: 250px; - font-size: 15px; + font-size: 14px; } .unt-btn-outline-primary { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index 837d7effe6..4744a4895b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -1,6 +1,7 @@ (function () { let formId; let notificationsTable; + let templateAttachmentsTable; let select2Ready = false; let select2Loading = false; let initialized = false; // Guard against reinitializing @@ -553,12 +554,114 @@ const preview = document.getElementById('templatePreview'); if (sel === null || preview === null) return; const val = sel.value; + updateTemplateAttachments(val); fetch('/api/form-notifications/templates').then(r => r.json()).then(list => { const t = list.find(x => String(x.id) === String(val)); renderTemplatePreview(preview, t); }); } + function notifyAttachmentCount(count) { + if (count === 0) return; + + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'info', + text: count === 1 ? '1 attachment is associated with this template.' : `${count} attachments are associated with this template.`, + showConfirmButton: false, + timer: 3000, + timerProgressBar: true + }); + } + + function updateTemplateAttachments(templateId) { + const section = document.getElementById('template-attachments-section'); + const label = document.getElementById('templateAttachmentsLabel'); + const countLabel = document.getElementById('templateAttachmentsCount'); + const table = $('#TemplateAttachmentsTable'); + if (!section || !table.length) return; + + if ($.fn.dataTable.isDataTable(table)) { + table.DataTable().destroy(); + } + templateAttachmentsTable = null; + + section.classList.add('hidden-section'); + label?.classList.add('hidden-section'); + if (countLabel) countLabel.textContent = '0'; + + if (!templateId) { + return; + } + + templateAttachmentsTable = table.DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: false, + order: [[2, 'asc']], + searching: false, + paging: false, + select: false, + info: false, + scrollX: true, + scrollY: '80px', // ~2 rows visible before scrolling + scrollCollapse: true, + drawCallback: function () { + const count = this.api().rows().count(); + if (countLabel) countLabel.textContent = String(count); + section.classList.toggle('hidden-section', count === 0); + label?.classList.toggle('hidden-section', count === 0); + notifyAttachmentCount(count); + }, + ajax: abp.libs.datatables.createAjax( + unity.notifications.emails.emailLogAttachment.getListByTemplateId, + function () { return templateId; }, + function (result) { return { data: result }; } + ), + columnDefs: [ + { + title: '', + width: '40px', + className: 'text-center', + orderable: false, + render: function () { + return ''; + } + }, + { + title: 'Document Name', + data: 'fileName', + className: 'data-table-header text-break', + width: '55%' + }, + { + title: 'Date', + data: 'time', + className: 'data-table-header', + width: '130px', + render: function (data, type) { + if (type === 'display' || type === 'filter') { + return new Date(data).toDateString(); + } + return data; + } + }, + { + title: 'File Size', + data: 'fileSize', + className: 'data-table-header', + width: '90px', + render: function (data) { + if (!data) return '—'; + const mb = data * 0.000001; + return mb >= 1 ? mb.toFixed(2) + ' MB' : (data / 1024).toFixed(0) + ' KB'; + } + } + ] + }) + ); + } + function showModal() { resetValidationState(); @@ -575,6 +678,7 @@ document.getElementById('eventOptions')?.classList.add('hidden-section'); document.getElementById('recipientOptions')?.classList.add('hidden-section'); renderTemplatePreview(document.getElementById('templatePreview'), null); + updateTemplateAttachments(''); const modalEl = document.getElementById('notificationModal'); if (modalEl === null) return; @@ -659,9 +763,7 @@ if (modalEl && modalEl.parentElement !== document.body) { document.body.appendChild(modalEl); } - - console.debug('init() starting'); - + formId = document.getElementById('applicationFormId')?.value; if (!formId) { console.warn('formId not found, returning from init()'); @@ -675,7 +777,10 @@ if (modalEl) { // Always reset validation when modal is fully closed - modalEl.addEventListener('hidden.bs.modal', () => resetValidationState()); + modalEl.addEventListener('hidden.bs.modal', () => { + resetValidationState(); + updateTemplateAttachments(''); + }); // Also reset when modal starts opening modalEl.addEventListener('show.bs.modal', () => resetValidationState()); // Refresh select2 when modal is shown @@ -718,6 +823,14 @@ e.target.classList.remove('is-invalid'); updatePreview(); }); + document.getElementById('templateConfigurationLink')?.addEventListener('click', () => { + const templateId = document.getElementById('templateSelect')?.value; + if (templateId) { + localStorage.setItem('notifications-template-to-select', templateId); + } else { + localStorage.removeItem('notifications-template-to-select'); + } + }); ['dateType', 'moduleSelect', 'statusSelect'].forEach(id => { document.getElementById(id)?.addEventListener('change', (e) => { e.target.classList.remove('is-invalid'); From 30518fbc38ce59de95cbaed593a7c3e84ed02668 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Fri, 14 Aug 2026 14:50:42 -0700 Subject: [PATCH 104/121] feature/AB#33998-AttachementScheduleNotifications --- .../Shared/Components/Notifications/Default.cshtml | 13 +++++++++++-- .../Shared/Components/Notifications/Default.js | 5 +---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml index 9e6ac1a458..29d238290e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml @@ -122,9 +122,18 @@
- +
Attachments (0)
-
+ + + + + + + + + +
Document NameDateAttached byFile Size
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index 4744a4895b..5df6a905b3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -1,7 +1,6 @@ (function () { let formId; let notificationsTable; - let templateAttachmentsTable; let select2Ready = false; let select2Loading = false; let initialized = false; // Guard against reinitializing @@ -585,8 +584,6 @@ if ($.fn.dataTable.isDataTable(table)) { table.DataTable().destroy(); } - templateAttachmentsTable = null; - section.classList.add('hidden-section'); label?.classList.add('hidden-section'); if (countLabel) countLabel.textContent = '0'; @@ -595,7 +592,7 @@ return; } - templateAttachmentsTable = table.DataTable( + table.DataTable( abp.libs.datatables.normalizeConfiguration({ serverSide: false, order: [[2, 'asc']], From 74fc46c84a82f1eb06a812ac3750296dd2faee44 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Fri, 14 Aug 2026 14:55:46 -0700 Subject: [PATCH 105/121] feature/AB#33998-AttachementScheduleNotifications-Sonar --- .../Views/Settings/NotificationsSettingGroup/Default.js | 4 ++-- .../Views/Shared/Components/Notifications/Default.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js index 9a43495087..80346cc479 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.js @@ -469,7 +469,7 @@ $(function () { return Swal.fire({ icon: 'warning', title: 'Template changes', - html: `

Warning: This template is currently associated with ${planNames.length} notification plan${planNames.length === 1 ? '' : 's'}. Any changes made to this template may impact these notification plan(s).

`, + html: '

Warning: This template is currently associated with ' + planNames.length + ' notification plan' + (planNames.length === 1 ? '' : 's') + '. Any changes made to this template may impact these notification plan' + (planNames.length === 1 ? '' : 's') + '.

', showCancelButton: true, confirmButtonText: 'OK', cancelButtonText: 'Cancel', @@ -1271,7 +1271,7 @@ function checkScheduledPlanImpactForAttachmentDelete(templateId) { return Swal.fire({ icon: 'warning', title: 'Scheduled notification impact', - html: `

Warning: This template is currently associated with ${planNames.length} notification plan${planNames.length === 1 ? '' : 's'}. Any changes made to this template may impact these notification plan(s).

`, + html: '

Warning: This template is currently associated with ' + planNames.length + ' notification plan' + (planNames.length === 1 ? '' : 's') + '. Any changes made to this template may impact these notification plan' + (planNames.length === 1 ? '' : 's') + '.

', showCancelButton: true, confirmButtonText: 'OK', cancelButtonText: 'Cancel', diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js index 5df6a905b3..6a1e912ecc 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.js @@ -649,7 +649,7 @@ className: 'data-table-header', width: '90px', render: function (data) { - if (!data) return '—'; + if (data === null || data === undefined) return '—'; const mb = data * 0.000001; return mb >= 1 ? mb.toFixed(2) + ' MB' : (data / 1024).toFixed(0) + ' KB'; } @@ -821,6 +821,8 @@ updatePreview(); }); document.getElementById('templateConfigurationLink')?.addEventListener('click', () => { + localStorage.setItem('ConfigurationManagement_ActiveMenu', 'notifications-menu-item'); + localStorage.setItem('notifications-active-tab', 'nav-template-tab'); const templateId = document.getElementById('templateSelect')?.value; if (templateId) { localStorage.setItem('notifications-template-to-select', templateId); From ae7d26d4bbcf19a6861fb4e3799f3204f0acde8e Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Fri, 14 Aug 2026 16:00:06 -0700 Subject: [PATCH 106/121] feature/AB#33998-AttachementScheduleNotifications-Sonar --- .../Views/Shared/Components/Notifications/Default.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml index 29d238290e..d535f1f2f5 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.cshtml @@ -127,10 +127,10 @@ - - - - + + + +
Document NameDateAttached byFile SizeDocument NameDateAttached byFile Size
From af0f2bacb44adfaa878750740dd389bbc8e2f200 Mon Sep 17 00:00:00 2001 From: Velang Date: Fri, 14 Aug 2026 16:09:25 -0700 Subject: [PATCH 107/121] Make ApprovalFlow.cy.ts self-seeding and fix transient CAS token failure Extracts the CHEFS submission-seeding logic from chefs-api-submission.cy.ts into a reusable cy.seedApprovalFlowSubmission() command so ApprovalFlow can fall back to seeding a fresh submission when no existing one matches its search criteria, instead of depending on a separate seed step running first. Also retries the payment-info supplier save once when it hits a transient "GetAuthTokenAsync: Error retrieving Token" CAS error, which was silently leaving SupplierId empty and cascading into unrelated failures downstream. --- .../cypress/regression/ApprovalFlow.cy.ts | 21 +- .../scripts/chefs-api-submission.cy.ts | 327 +----------------- .../Unity.AutoUI/cypress/support/commands.ts | 305 +++++++++++++++- .../Unity.AutoUI/cypress/support/index.d.ts | 13 +- 4 files changed, 340 insertions(+), 326 deletions(-) diff --git a/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts b/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts index 1f287c3ab7..0efc3c2bd7 100644 --- a/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts +++ b/applications/Unity.AutoUI/cypress/regression/ApprovalFlow.cy.ts @@ -506,10 +506,25 @@ const APPLICATIONS_PATH = "GrantApplications"; return; } - // Priority 3: fetch the latest matching submission from the Unity API + // Priority 3: fetch the latest matching submission from the Unity API, + // falling back to seeding a fresh one via CHEFS if none currently + // match (e.g. every existing seeded submission has already been + // approved by a prior run) — so this spec is self-sufficient and + // doesn't depend on a separate seed step running first. cy.fetchDynamicSubmission(TEST_CONFIG.fetchOptions).then((id) => { - submissionId = id; - cy.log(`✅ Fetched dynamic submission ID: ${submissionId}`); + if (id) { + submissionId = id; + cy.log(`✅ Fetched dynamic submission ID: ${submissionId}`); + return; + } + + cy.log( + "⚠️ No matching submission found — seeding a fresh one via CHEFS", + ); + cy.seedApprovalFlowSubmission().then((seededId) => { + submissionId = seededId; + cy.log(`✅ Seeded and using submission ID: ${submissionId}`); + }); }); }, ); diff --git a/applications/Unity.AutoUI/cypress/scripts/chefs-api-submission.cy.ts b/applications/Unity.AutoUI/cypress/scripts/chefs-api-submission.cy.ts index 0b7124aaf0..a309bdd85e 100644 --- a/applications/Unity.AutoUI/cypress/scripts/chefs-api-submission.cy.ts +++ b/applications/Unity.AutoUI/cypress/scripts/chefs-api-submission.cy.ts @@ -3,337 +3,26 @@ export {}; /** - * CHEFS Form Submission Seeder + * CHEFS Form Submission Seeder (standalone entry point) * - * Creates exactly one submitted form entry in CHEFS via API and writes its - * confirmation ID to cypress/scripts/last-submission-id.json so that - * ApprovalFlow.cy.ts can pick it up without a dynamic API lookup. + * Thin wrapper around cy.seedApprovalFlowSubmission() so `npm run test:seed` + * / `npm run test:approval-flow` can still seed a submission as a separate + * step before ApprovalFlow.cy.ts runs. The actual seeding logic lives in + * cypress/support/commands.ts so ApprovalFlow.cy.ts can also call it + * directly as a fallback when no existing submission matches its search + * criteria, without requiring this spec to run first. * * Configuration files: * - cypress/scripts/chefs-submission-payload.json — form submission data * - cypress/scripts/chefs-api-config.json — API config and headers */ -interface ChefsEnvironment { - baseURL: string; - formId: string; - versionId: string; -} - -interface ChefsApiConfig { - environments: Record; - headers: Record; -} - -interface ChefsSubmissionPayload { - draft?: boolean; - submission: { - state: string; - metadata: { - origin: string; - referrer: string; - }; - data: Record; - }; -} - -const TOKEN_PROPERTY_KEYS = [ - "access_token", - "accessToken", - "token", - "id_token", - "idToken", -]; - -function isJwtLike(value: string): boolean { - return /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(value); -} - -function extractTokenFromString(value: string): string { - const trimmed = value.trim(); - - if (trimmed.toLowerCase().startsWith("bearer ")) { - const bearerToken = trimmed.replace(/^Bearer\s+/i, "").trim(); - if (isJwtLike(bearerToken)) { - return bearerToken; - } - } - - if (isJwtLike(trimmed)) { - return trimmed; - } - - try { - return extractTokenFromValue(JSON.parse(trimmed)); - } catch { - return ""; - } -} - -function extractTokenFromArray(values: unknown[]): string { - for (const value of values) { - const token = extractTokenFromValue(value); - if (token) { - return token; - } - } - - return ""; -} - -function extractTokenFromObject(value: Record): string { - for (const key of TOKEN_PROPERTY_KEYS) { - const token = extractTokenFromValue(value[key]); - if (token) { - return token; - } - } - - return extractTokenFromArray(Object.values(value)); -} - -function extractTokenFromValue(value: unknown): string { - if (typeof value === "string") { - return extractTokenFromString(value); - } - - if (Array.isArray(value)) { - return extractTokenFromArray(value); - } - - if (value && typeof value === "object") { - return extractTokenFromObject(value as Record); - } - - return ""; -} - -function extractTokenFromStorage(win: Window): string { - const storages = [win.localStorage, win.sessionStorage]; - - for (const storage of storages) { - for (let index = 0; index < storage.length; index += 1) { - const key = storage.key(index); - if (!key) { - continue; - } - - const value = storage.getItem(key); - if (!value) { - continue; - } - - const token = extractTokenFromValue(value); - if (token) { - return token; - } - } - } - - return ""; -} - -function getChefsHostname(baseURL: string): string { - return new URL(baseURL).hostname; -} - -function waitForIdentityRedirectOrAuthenticatedChefsPage( - baseURL: string, - timeout: number, -): void { - const chefsHostname = getChefsHostname(baseURL); - - cy.location("hostname", { timeout }).should((hostname) => { - const onChefs = hostname === chefsHostname; - const onBcGovIdentity = hostname.endsWith("gov.bc.ca"); - - expect( - onChefs || onBcGovIdentity, - `Expected CHEFS or BC Gov identity host, got '${hostname}'`, - ).to.eq(true); - }); -} - -function completeChefsLogin(environment: ChefsEnvironment, timeout: number): void { - const chefsHostname = getChefsHostname(environment.baseURL); - - cy.visit(`${environment.baseURL}/app`); - - // The header auth button hydrates asynchronously after an auth-state check; - // wait for its label rather than just its (empty) wrapper to exist. - cy.get("#loginButton", { timeout }).should("be.visible").click(); - - // CHEFS shows an identity-provider picker (IDIR / IDIR MFA / BC Services - // Card / BCeID); each button carries a stable data-test attribute. - cy.get('[data-test="idir"]', { timeout }).should("be.visible").click(); - - waitForIdentityRedirectOrAuthenticatedChefsPage(environment.baseURL, timeout); - - cy.location("hostname", { timeout }).then((hostname) => { - if (hostname === chefsHostname) { - cy.log("Already logged in to CHEFS"); - return; - } - - cy.get("#user", { timeout }) - .should("be.visible") - .clear() - .type(Cypress.env("test1username"), { log: false }); - - cy.get("#password", { timeout }) - .should("be.visible") - .clear() - .type(Cypress.env("test1password"), { log: false }); - - cy.contains("Continue", { timeout }).should("be.visible").click(); - - cy.location("hostname", { timeout }).should("eq", chefsHostname); - }); -} - -function visitChefsForm(environment: ChefsEnvironment, timeout: number): void { - cy.visit(`${environment.baseURL}/app/form/submit?f=${environment.formId}`); - cy.location("hostname", { timeout }).should( - "eq", - getChefsHostname(environment.baseURL), - ); - cy.location("pathname", { timeout }).should("include", "/app"); -} - const isProd = (Cypress.env("CHEFS_ENV") || Cypress.env("environment") || "").toLowerCase() === "prod"; (isProd ? describe.skip : describe)("CHEFS Approval Flow Seeder", () => { - let apiConfig: ChefsApiConfig; - let submissionPayload: ChefsSubmissionPayload; - let environment: ChefsEnvironment; - let authToken: string; - - before(() => { - const authTimeout = 60000; - - cy.readFile("cypress/scripts/chefs-api-config.json").then((config) => { - apiConfig = config; - - const envKey = ( - Cypress.env("CHEFS_ENV") || - Cypress.env("environment") || - "test" - ).toLowerCase(); - - environment = config.environments[envKey]; - - expect( - environment, - `Missing CHEFS environment configuration for '${envKey}'`, - ).to.exist; - - cy.log(`Using environment: ${envKey}`); - cy.log(`Base URL: ${environment.baseURL}`); - cy.log(`Form ID: ${environment.formId}`); - cy.log(`Version ID: ${environment.versionId}`); - - cy.readFile("cypress/scripts/chefs-submission-payload.json").then( - (payload) => { - submissionPayload = payload; - submissionPayload.submission.metadata.origin = environment.baseURL; - submissionPayload.submission.metadata.referrer = `${environment.baseURL}/app/form/submit?f=${environment.formId}`; - - cy.log( - `Payload loaded with ${ - Object.keys(payload.submission.data).length - } data fields`, - ); - cy.log(`Metadata origin set to: ${environment.baseURL}`); - }, - ); - - let capturedToken = ""; - - cy.intercept("**/app/api/v1/**", (req) => { - const authHeader = req.headers["authorization"] as string; - if (authHeader && !capturedToken) { - capturedToken = authHeader.replace(/^Bearer\s+/i, ""); - } - }).as("chefsApiCalls"); - - completeChefsLogin(environment, authTimeout); - visitChefsForm(environment, authTimeout); - - cy.window({ timeout: authTimeout }) - .should((win) => { - const tokenFromStorage = extractTokenFromStorage(win); - const resolvedToken = capturedToken || tokenFromStorage; - - expect( - resolvedToken, - "Waiting for authenticated CHEFS API token from request or browser storage", - ).to.not.equal(""); - - if (!capturedToken && tokenFromStorage) { - capturedToken = tokenFromStorage; - } - }) - .then(() => { - authToken = capturedToken; - cy.log("✅ Auth token captured from CHEFS login"); - }); - }); - }); - - // Creates the single submission that ApprovalFlow.cy.ts will process. - // The confirmation ID is written to last-submission-id.json and consumed - // by the "Fetch submission ID from API" step in ApprovalFlow.cy.ts. it("Create approval flow submission", () => { - const submissionUrl = `${environment.baseURL}/app/api/v1/forms/${environment.formId}/versions/${environment.versionId}/submissions`; - - cy.log(`Submitting to: ${submissionUrl}`); - - cy.request({ - method: "POST", - url: submissionUrl, - headers: { - ...apiConfig.headers, - Authorization: `Bearer ${authToken}`, - Origin: environment.baseURL, - Referer: `${environment.baseURL}/app/form/submit?f=${environment.formId}`, - }, - body: submissionPayload, - failOnStatusCode: false, - }).then((response) => { - cy.log(`Response Status: ${response.status}`); - cy.log( - `Response Body: ${JSON.stringify(response.body).substring(0, 200)}...`, - ); - - if (response.status === 401) { - cy.log("❌ 401 Unauthorized - Token is expired or invalid"); - cy.log("📖 See cypress/scripts/README.md for token refresh instructions"); - throw new Error( - "Authentication failed (401). Check that test1username/test1password credentials in cypress.env.json are valid and that the CHEFS UI login succeeded during test setup.", - ); - } - - expect(response.status).to.be.oneOf([200, 201]); - expect(response.body).to.have.property("id"); - - const confirmationId = response.body.confirmationId || response.body.id; - cy.log(`✅ Submission created with ID: ${response.body.id}`); - cy.log(`✅ Confirmation ID: ${confirmationId}`); - - cy.writeFile("cypress/scripts/last-submission-id.json", { - submissionId: confirmationId, - createdAt: new Date().toISOString(), - }); - - expect(response.body).to.have.property("formVersionId", environment.versionId); - - if (response.body.formId) { - expect(response.body.formId).to.eq(environment.formId); - } else { - cy.log("⚠️ Response doesn't include formId (CHEFS version-dependent)"); - } - }); + cy.seedApprovalFlowSubmission(); }); }); diff --git a/applications/Unity.AutoUI/cypress/support/commands.ts b/applications/Unity.AutoUI/cypress/support/commands.ts index 07edc350ba..b07311dc45 100644 --- a/applications/Unity.AutoUI/cypress/support/commands.ts +++ b/applications/Unity.AutoUI/cypress/support/commands.ts @@ -331,9 +331,11 @@ Cypress.Commands.add( } if (applications.length === 0) { - throw new Error( - "No applications found matching the specified criteria", - ); + Cypress.log({ + name: "fetch", + message: "⚠️ No applications found matching the specified criteria", + }); + return ""; } // Sort applications (default: by submissionDate descending for latest first) @@ -386,3 +388,300 @@ Cypress.Commands.add( Cypress.Commands.add("fetchAllSubmissions", () => { return fetchGrantApplications(); }); + +// ============ CHEFS Submission Seeding ============ + +interface ChefsSeedEnvironment { + baseURL: string; + formId: string; + versionId: string; +} + +interface ChefsSeedApiConfig { + environments: Record; + headers: Record; +} + +interface ChefsSeedSubmissionPayload { + draft?: boolean; + submission: { + state: string; + metadata: { + origin: string; + referrer: string; + }; + data: Record; + }; +} + +const CHEFS_SEED_TOKEN_PROPERTY_KEYS = [ + "access_token", + "accessToken", + "token", + "id_token", + "idToken", +]; + +function isChefsSeedJwtLike(value: string): boolean { + return /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(value); +} + +function extractChefsSeedTokenFromValue(value: unknown): string { + if (typeof value === "string") { + const trimmed = value.trim(); + + if (trimmed.toLowerCase().startsWith("bearer ")) { + const bearerToken = trimmed.replace(/^Bearer\s+/i, "").trim(); + if (isChefsSeedJwtLike(bearerToken)) { + return bearerToken; + } + } + + if (isChefsSeedJwtLike(trimmed)) { + return trimmed; + } + + try { + return extractChefsSeedTokenFromValue(JSON.parse(trimmed)); + } catch { + return ""; + } + } + + if (Array.isArray(value)) { + for (const item of value) { + const token = extractChefsSeedTokenFromValue(item); + if (token) { + return token; + } + } + return ""; + } + + if (value && typeof value === "object") { + const obj = value as Record; + for (const key of CHEFS_SEED_TOKEN_PROPERTY_KEYS) { + const token = extractChefsSeedTokenFromValue(obj[key]); + if (token) { + return token; + } + } + return extractChefsSeedTokenFromValue(Object.values(obj)); + } + + return ""; +} + +function extractChefsSeedTokenFromStorage(win: Window): string { + const storages = [win.localStorage, win.sessionStorage]; + + for (const storage of storages) { + for (let index = 0; index < storage.length; index += 1) { + const key = storage.key(index); + if (!key) { + continue; + } + + const value = storage.getItem(key); + if (!value) { + continue; + } + + const token = extractChefsSeedTokenFromValue(value); + if (token) { + return token; + } + } + } + + return ""; +} + +function getChefsSeedHostname(baseURL: string): string { + return new URL(baseURL).hostname; +} + +function completeChefsSeedLogin( + environment: ChefsSeedEnvironment, + timeout: number, +): void { + const chefsHostname = getChefsSeedHostname(environment.baseURL); + + cy.visit(`${environment.baseURL}/app`); + + // A session from an earlier CHEFS visit this run can already be + // authenticated (LOGOUT button visible instead of LOGIN) — only drive the + // login flow when it's actually needed. + cy.get("body", { timeout }).then(($body) => { + if ($body.find("#loginButton").length === 0) { + cy.log("Already logged in to CHEFS"); + return; + } + + cy.get("#loginButton", { timeout }).should("be.visible").click(); + cy.get('[data-test="idir"]', { timeout }).should("be.visible").click(); + + cy.location("hostname", { timeout }).should((hostname) => { + const onChefs = hostname === chefsHostname; + const onBcGovIdentity = hostname.endsWith("gov.bc.ca"); + expect( + onChefs || onBcGovIdentity, + `Expected CHEFS or BC Gov identity host, got '${hostname}'`, + ).to.eq(true); + }); + + cy.location("hostname", { timeout }).then((hostname) => { + if (hostname === chefsHostname) { + cy.log("Already logged in to CHEFS"); + return; + } + + cy.get("#user", { timeout }) + .should("be.visible") + .clear() + .type(Cypress.env("test1username"), { log: false }); + + cy.get("#password", { timeout }) + .should("be.visible") + .clear() + .type(Cypress.env("test1password"), { log: false }); + + cy.contains("Continue", { timeout }).should("be.visible").click(); + + cy.location("hostname", { timeout }).should("eq", chefsHostname); + }); + }); +} + +function visitChefsSeedForm( + environment: ChefsSeedEnvironment, + timeout: number, +): void { + cy.visit(`${environment.baseURL}/app/form/submit?f=${environment.formId}`); + cy.location("hostname", { timeout }).should( + "eq", + getChefsSeedHostname(environment.baseURL), + ); + cy.location("pathname", { timeout }).should("include", "/app"); +} + +/** + * Seeds exactly one submission in CHEFS via API (logs in once via the CHEFS + * UI to capture an auth token, then POSTs the submission directly) and + * writes the confirmation ID to cypress/scripts/last-submission-id.json. + * + * This is the same logic cypress/scripts/chefs-api-submission.cy.ts runs as + * a standalone spec — extracted here so ApprovalFlow.cy.ts (or any other + * spec) can call it directly as a fallback when no existing submission + * matches its search criteria, without needing a separate seed step run + * first. + * + * @returns Chainable containing the confirmation ID of the created submission + */ +Cypress.Commands.add("seedApprovalFlowSubmission", () => { + const envKey = ( + Cypress.env("CHEFS_ENV") || + Cypress.env("environment") || + "test" + ).toLowerCase(); + + if (envKey === "prod") { + throw new Error( + "seedApprovalFlowSubmission() is disabled for PROD — seeding real submissions in production is not supported.", + ); + } + + const authTimeout = 60000; + + return cy + .readFile("cypress/scripts/chefs-api-config.json") + .then((apiConfig) => { + const environment = apiConfig.environments[envKey]; + + expect( + environment, + `Missing CHEFS environment configuration for '${envKey}'`, + ).to.exist; + + cy.log(`🌱 Seeding submission — environment: ${envKey}`); + + return cy + .readFile( + "cypress/scripts/chefs-submission-payload.json", + ) + .then((submissionPayload) => { + submissionPayload.submission.metadata.origin = environment.baseURL; + submissionPayload.submission.metadata.referrer = `${environment.baseURL}/app/form/submit?f=${environment.formId}`; + + let capturedToken = ""; + + cy.intercept("**/app/api/v1/**", (req) => { + const authHeader = req.headers["authorization"] as string; + if (authHeader && !capturedToken) { + capturedToken = authHeader.replace(/^Bearer\s+/i, ""); + } + }).as("chefsSeedApiCalls"); + + completeChefsSeedLogin(environment, authTimeout); + visitChefsSeedForm(environment, authTimeout); + + return cy + .window({ timeout: authTimeout }) + .should((win) => { + const tokenFromStorage = extractChefsSeedTokenFromStorage(win); + const resolvedToken = capturedToken || tokenFromStorage; + + expect( + resolvedToken, + "Waiting for authenticated CHEFS API token from request or browser storage", + ).to.not.equal(""); + + if (!capturedToken && tokenFromStorage) { + capturedToken = tokenFromStorage; + } + }) + .then(() => { + const authToken = capturedToken; + const submissionUrl = `${environment.baseURL}/app/api/v1/forms/${environment.formId}/versions/${environment.versionId}/submissions`; + + return cy + .request({ + method: "POST", + url: submissionUrl, + headers: { + ...apiConfig.headers, + Authorization: `Bearer ${authToken}`, + Origin: environment.baseURL, + Referer: `${environment.baseURL}/app/form/submit?f=${environment.formId}`, + }, + body: submissionPayload, + failOnStatusCode: false, + }) + .then((response) => { + if (response.status === 401) { + throw new Error( + "Authentication failed (401) while seeding a submission via cy.seedApprovalFlowSubmission(). Check that test1username/test1password credentials are valid and that the CHEFS UI login succeeded.", + ); + } + + expect(response.status).to.be.oneOf([200, 201]); + expect(response.body).to.have.property("id"); + + const confirmationId = + response.body.confirmationId || response.body.id; + Cypress.log({ + name: "seed", + message: `✅ Seeded submission with confirmation ID: ${confirmationId}`, + }); + + return cy + .writeFile("cypress/scripts/last-submission-id.json", { + submissionId: confirmationId, + createdAt: new Date().toISOString(), + }) + .then(() => confirmationId as string); + }); + }); + }); + }); +}); diff --git a/applications/Unity.AutoUI/cypress/support/index.d.ts b/applications/Unity.AutoUI/cypress/support/index.d.ts index 979f84c4a5..5dd1ba1d2a 100644 --- a/applications/Unity.AutoUI/cypress/support/index.d.ts +++ b/applications/Unity.AutoUI/cypress/support/index.d.ts @@ -89,7 +89,8 @@ declare namespace Cypress { * Uses session cookies automatically from Cypress. * * @param options - Optional filters for selecting submissions - * @returns Chainable containing the confirmation ID + * @returns Chainable containing the confirmation ID, or an empty string + * if no application matches the given filters (does not throw) * * @example * // Get first available submission @@ -108,5 +109,15 @@ declare namespace Cypress { * @returns Chainable containing array of grant applications */ fetchAllSubmissions(): Chainable; + + /** + * Seeds exactly one submission in CHEFS via API (logs in once via the + * CHEFS UI to capture an auth token, then POSTs the submission directly) + * and writes the confirmation ID to + * cypress/scripts/last-submission-id.json. Disabled for PROD. + * + * @returns Chainable containing the confirmation ID of the created submission + */ + seedApprovalFlowSubmission(): Chainable; } } From 2a24d4540ec44a6426d665a6a835beceba691593 Mon Sep 17 00:00:00 2001 From: JamesPasta Date: Fri, 14 Aug 2026 16:50:15 -0700 Subject: [PATCH 108/121] feature/AB#33998-AttachementScheduleNotifications-Sonar --- .../NotificationsSettingGroup/Default.css | 26 +++++++++++++++++++ .../Components/Notifications/Default.css | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css index 0f18cea086..a08da5ac7f 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.css @@ -189,6 +189,10 @@ body { justify-content: center; } +.split-container.split-active .left-pane { + flex: 0 0 48%; +} + /* ── Right pane ───────────────────────────────────────────────────────────── */ .right-pane { flex-shrink: 0; @@ -205,6 +209,19 @@ body { height: calc(100vh - 230px); } +.right-pane > .card { + min-height: 0; +} + +.right-pane .editor-body { + overflow-x: hidden; + overflow-y: auto; +} + +#email-attachments-section { + flex-shrink: 0; +} + /* ── Editor header ────────────────────────────────────────────────── */ .editor-header { @@ -222,6 +239,15 @@ body { padding: 2px; } +#email-attachments-section .d-flex.justify-content-end.mt-2.mb-1 { + padding-bottom: 50px !important; + display: flex !important; +} + +.right-pane .card-body.editor-body.overflow-auto { + padding-bottom: 20px !important; +} + /* ── Resizable textareas ──────────────────────────────────────────────────── */ .textarea { resize: vertical; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css index 2abe9124f3..84c4bdd445 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/Notifications/Default.css @@ -99,7 +99,8 @@ } .notification-modal-body { - overflow: hidden; + overflow-y: auto; + overflow-x: hidden; flex: 1 1 auto; display: flex; flex-direction: column; From a7920010371794573e5c7c33a2efd5db26547358 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:04:51 -0700 Subject: [PATCH 109/121] [AB#33234] Fix ApplicationForm Unit Tests --- .../SubmissionInfoDataProvider.cs | 67 ++++---- .../Applications/ApplicationForm.cs | 1 - .../ExternalLinksConfigValidationTests.cs | 155 ++++++++++++++++++ .../ApplicationForms/ApplicationFormTests.cs | 71 +++++++- 4 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/ApplicationForms/ExternalLinksConfigValidationTests.cs 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 6ced1a0a5d..11b953c8e6 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 @@ -66,49 +66,56 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id submission.CreationTime, submission.Submission, application.ReferenceNo, + application.EligibleForRenewal, FormName = form.ApplicationFormName ?? string.Empty, + form.ExternalLinks, Status = application.ExternalStatusVisibility ? status.NotifiedStatus ?? status.ExternalStatus : status.ExternalStatus, - RenewalLink = application.EligibleForRenewal ? form.ExternalLinks - .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal) - .Select(x => new ExternalLinkDto - { - Uri = x.Uri, - 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 - .Select(x => new ExternalLinkDto - { - Uri = x.Uri, - Order = x.Order, - Title = x.Title, - Description = x.Description - }) }).ToListAsync(); - dto.Submissions.AddRange(results.Select(s => new SubmissionInfoItemDto + // ExternalLinks is a JSON-mapped complex collection; filtering it as part of the join + // requires an APPLY operation that the SQLite test provider does not support, so the + // form's external links are resolved in-memory below instead of within the query. + dto.Submissions.AddRange(results.Select(s => { - Id = s.Id, - LinkId = s.LinkId, - ReceivedTime = s.CreationTime, - SubmissionTime = ResolveSubmissionTime(s.Submission, s.CreationTime), - ReferenceNo = s.ReferenceNo, - Type = s.FormName, - Status = s.Status, - RenewalLink = s.RenewalLink, - RelatedLinks = [.. s.RelatedLinks] + var renewalLink = s.EligibleForRenewal ? s.ExternalLinks + .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal) + .Select(ToExternalLinkDto) + .FirstOrDefault() : null; + + var relatedLinks = s.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 + .Select(ToExternalLinkDto) + .ToList(); + + return new SubmissionInfoItemDto + { + Id = s.Id, + LinkId = s.LinkId, + ReceivedTime = s.CreationTime, + SubmissionTime = ResolveSubmissionTime(s.Submission, s.CreationTime), + ReferenceNo = s.ReferenceNo, + Type = s.FormName, + Status = s.Status, + RenewalLink = renewalLink, + RelatedLinks = relatedLinks + }; })); } return dto; } + private static ExternalLinkDto ToExternalLinkDto(ExternalLink link) => new() + { + Uri = link.Uri, + Order = link.Order, + Title = link.Title, + Description = link.Description + }; + /// /// Derives the CHEFS form view URL from the INTAKE_API_BASE dynamic URL setting. /// e.g. https://chefs-dev.apps.silver.devops.gov.bc.ca/app/api/v1 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 df416ff948..120334d604 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -117,7 +117,6 @@ public ApplicationForm SetExternalLinks(ExternalLink? renewalLink, List _applicationFormRepository; + + public ExternalLinksConfigValidationTests(ITestOutputHelper outputHelper) : base(outputHelper) + { + _applicationFormAppService = GetRequiredService(); + _applicationFormRepository = GetRequiredService>(); + } + + [Fact] + public async Task PatchExternalLinksConfigAsync_ShouldSaveValidRenewalLinkAndRelatedLinks() + { + await _applicationFormAppService.PatchExternalLinksConfigAsync( + GrantManagerTestData.ApplicationForm1_Id, + new ExternalLinksConfigDto + { + RenewalLink = new ExternalLinkConfigDto + { + Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/form", + Title = "Renew Now", + Description = "Please renew before the deadline.", + Published = true, + ExternalLinkType = ExternalLinkType.Renewal + }, + RelatedLinks = + [ + new ExternalLinkConfigDto + { + Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/related-1", + Title = "Related One", + Description = "First related link.", + Published = true, + ExternalLinkType = ExternalLinkType.Related + }, + new ExternalLinkConfigDto + { + Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/related-2", + Title = "Related Two", + Description = "Second related link.", + Published = false, + ExternalLinkType = ExternalLinkType.Related + } + ] + }); + + var form = await _applicationFormRepository.GetAsync(GrantManagerTestData.ApplicationForm1_Id); + + form.ExternalLinks.Count.ShouldBe(3); + var renewalLink = form.ExternalLinks.Single(l => l.ExternalLinkType == ExternalLinkType.Renewal); + renewalLink.Uri.ShouldBe("https://chefs-test.apps.silver.devops.gov.bc.ca/app/form"); + renewalLink.Title.ShouldBe("Renew Now"); + renewalLink.Description.ShouldBe("Please renew before the deadline."); + renewalLink.Published.ShouldBeTrue(); + + var relatedLinks = form.ExternalLinks + .Where(l => l.ExternalLinkType == ExternalLinkType.Related) + .OrderBy(l => l.Order) + .ToList(); + relatedLinks.Count.ShouldBe(2); + relatedLinks[0].Title.ShouldBe("Related One"); + relatedLinks[0].Order.ShouldBe(-1); + relatedLinks[1].Title.ShouldBe("Related Two"); + relatedLinks[1].Order.ShouldBe(-1); + } + + [Fact] + public async Task PatchExternalLinksConfigAsync_ShouldReplaceRelatedLinks_OnSubsequentSave() + { + await _applicationFormAppService.PatchExternalLinksConfigAsync( + GrantManagerTestData.ApplicationForm1_Id, + new ExternalLinksConfigDto + { + RelatedLinks = + [ + new ExternalLinkConfigDto { Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/first" } + ] + }); + + await _applicationFormAppService.PatchExternalLinksConfigAsync( + GrantManagerTestData.ApplicationForm1_Id, + new ExternalLinksConfigDto + { + RelatedLinks = + [ + new ExternalLinkConfigDto { Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/second" }, + new ExternalLinkConfigDto { Uri = "https://chefs-test.apps.silver.devops.gov.bc.ca/app/third" } + ] + }); + + var form = await _applicationFormRepository.GetAsync(GrantManagerTestData.ApplicationForm1_Id); + var relatedLinks = form.ExternalLinks.Where(l => l.ExternalLinkType == ExternalLinkType.Related).ToList(); + + relatedLinks.Count.ShouldBe(2); + relatedLinks.ShouldNotContain(l => l.Uri.EndsWith("first", StringComparison.Ordinal)); + } + + [Fact] + public async Task PatchExternalLinksConfigAsync_ShouldReject_WhenRenewalLinkVisibleWithoutUri() + { + await Should.ThrowAsync( + _applicationFormAppService.PatchExternalLinksConfigAsync( + GrantManagerTestData.ApplicationForm1_Id, + new ExternalLinksConfigDto + { + RenewalLink = new ExternalLinkConfigDto + { + Uri = string.Empty, + Published = true + } + })); + } + + [Fact] + public async Task PatchExternalLinksConfigAsync_ShouldReject_WhenExceedingMaxRelatedLinks() + { + var relatedLinks = Enumerable.Range(1, ApplicationForm.MaxRelatedExternalLinks + 1) + .Select(i => new ExternalLinkConfigDto { Uri = $"https://chefs-test.apps.silver.devops.gov.bc.ca/app/link-{i}" }) + .ToList(); + + await Should.ThrowAsync( + _applicationFormAppService.PatchExternalLinksConfigAsync( + GrantManagerTestData.ApplicationForm1_Id, + new ExternalLinksConfigDto { RelatedLinks = relatedLinks })); + } + + [Fact] + public async Task PatchExternalLinksConfigAsync_ShouldReject_WhenUriIsScriptScheme() + { + var dto = new ExternalLinksConfigDto + { + RenewalLink = new ExternalLinkConfigDto + { + Uri = "javascript:alert(1)" + } + }; + + await Should.ThrowAsync( + _applicationFormAppService.PatchExternalLinksConfigAsync(GrantManagerTestData.ApplicationForm1_Id, dto)); + } +} diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs index 9675be6a74..7d4abc0452 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs @@ -1,6 +1,10 @@ -using Unity.GrantManager.Applications; -using Xunit; +using Shouldly; +using System.Collections.Generic; +using Unity.GrantManager.ApplicantProfile; +using Unity.GrantManager.Applications; using Unity.GrantManager.GrantApplications; +using Volo.Abp; +using Xunit; namespace Unity.GrantManager.ApplicationForms { @@ -33,6 +37,69 @@ public void GetAvailableElectoralDistrictAddressTypesReturnsExpected() x => x.AddressType == AddressType.MailingAddress ); } + + [Fact] + public void SetExternalLinks_ShouldThrow_WhenRenewalLinkPublishedWithoutUri() + { + var form = new ApplicationForm(); + + var exception = Should.Throw(() => + form.SetExternalLinks( + new ExternalLink { Uri = string.Empty, Published = true }, + [])); + + exception.Code.ShouldBe(GrantManagerDomainErrorCodes.RenewalLinkRequiredForVisibility); + } + + [Fact] + public void SetExternalLinks_ShouldThrow_WhenRelatedLinkPublishedWithoutUri() + { + var form = new ApplicationForm(); + + var exception = Should.Throw(() => + form.SetExternalLinks( + null, + [new ExternalLink { Uri = string.Empty, Published = true }])); + + exception.Code.ShouldBe(GrantManagerDomainErrorCodes.RelatedLinkInvalidUri); + } + + [Fact] + public void SetExternalLinks_ShouldThrow_WhenExceedingMaxRelatedLinks() + { + var form = new ApplicationForm(); + var relatedLinks = new List(); + for (var i = 0; i <= ApplicationForm.MaxRelatedExternalLinks; i++) + { + relatedLinks.Add(new ExternalLink { Uri = $"https://example.com/{i}" }); + } + + var exception = Should.Throw(() => + form.SetExternalLinks(null, relatedLinks)); + + exception.Code.ShouldBe(GrantManagerDomainErrorCodes.TooManyRelatedLinks); + } + + [Fact] + public void SetExternalLinks_ShouldAssignOrderAndTypes_WhenValid() + { + var form = new ApplicationForm(); + + form.SetExternalLinks( + new ExternalLink { Uri = "https://example.com/renew", Published = true }, + [ + new ExternalLink { Uri = "https://example.com/one", Order = 2 }, + new ExternalLink { Uri = "https://example.com/two", Order = 1} + ]); + + form.ExternalLinks.Count.ShouldBe(3); + form.ExternalLinks[0].ExternalLinkType.ShouldBe(ExternalLinkType.Renewal); + form.ExternalLinks[0].Order.ShouldBe(-1); + form.ExternalLinks[1].ExternalLinkType.ShouldBe(ExternalLinkType.Related); + form.ExternalLinks[1].Order.ShouldBe(2); + form.ExternalLinks[2].ExternalLinkType.ShouldBe(ExternalLinkType.Related); + form.ExternalLinks[2].Order.ShouldBe(1); + } } } From 6f4523734bab6a36fbbc6e66b86c3924738a77a2 Mon Sep 17 00:00:00 2001 From: Velang Date: Mon, 17 Aug 2026 15:07:23 -0700 Subject: [PATCH 110/121] initial commit to push the changes to fix the approval flow seeder --- .../cypress/config/dev.json.example | 3 +- .../cypress/config/test.json.example | 3 +- .../cypress/config/uat.json.example | 3 +- .../Unity.AutoUI/cypress/support/commands.ts | 278 ++++-------------- .../Unity.AutoUI/cypress/support/index.d.ts | 10 +- 5 files changed, 67 insertions(+), 230 deletions(-) diff --git a/applications/Unity.AutoUI/cypress/config/dev.json.example b/applications/Unity.AutoUI/cypress/config/dev.json.example index b34148f6e7..48035f0b27 100644 --- a/applications/Unity.AutoUI/cypress/config/dev.json.example +++ b/applications/Unity.AutoUI/cypress/config/dev.json.example @@ -7,5 +7,6 @@ "test2password": "", "TEST_EMAIL_TO": "", "TEST_EMAIL_CC": "", - "TEST_EMAIL_BCC": "" + "TEST_EMAIL_BCC": "", + "chefsApiKey": "" } diff --git a/applications/Unity.AutoUI/cypress/config/test.json.example b/applications/Unity.AutoUI/cypress/config/test.json.example index 0b012b37d0..5d9b2c47a1 100644 --- a/applications/Unity.AutoUI/cypress/config/test.json.example +++ b/applications/Unity.AutoUI/cypress/config/test.json.example @@ -7,5 +7,6 @@ "test2password": "", "TEST_EMAIL_TO": "", "TEST_EMAIL_CC": "", - "TEST_EMAIL_BCC": "" + "TEST_EMAIL_BCC": "", + "chefsApiKey": "" } diff --git a/applications/Unity.AutoUI/cypress/config/uat.json.example b/applications/Unity.AutoUI/cypress/config/uat.json.example index 2ab1fe261f..2510724582 100644 --- a/applications/Unity.AutoUI/cypress/config/uat.json.example +++ b/applications/Unity.AutoUI/cypress/config/uat.json.example @@ -7,5 +7,6 @@ "test2password": "", "TEST_EMAIL_TO": "", "TEST_EMAIL_CC": "", - "TEST_EMAIL_BCC": "" + "TEST_EMAIL_BCC": "", + "chefsApiKey": "" } diff --git a/applications/Unity.AutoUI/cypress/support/commands.ts b/applications/Unity.AutoUI/cypress/support/commands.ts index b07311dc45..9d17908f68 100644 --- a/applications/Unity.AutoUI/cypress/support/commands.ts +++ b/applications/Unity.AutoUI/cypress/support/commands.ts @@ -414,167 +414,21 @@ interface ChefsSeedSubmissionPayload { }; } -const CHEFS_SEED_TOKEN_PROPERTY_KEYS = [ - "access_token", - "accessToken", - "token", - "id_token", - "idToken", -]; - -function isChefsSeedJwtLike(value: string): boolean { - return /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(value); -} - -function extractChefsSeedTokenFromValue(value: unknown): string { - if (typeof value === "string") { - const trimmed = value.trim(); - - if (trimmed.toLowerCase().startsWith("bearer ")) { - const bearerToken = trimmed.replace(/^Bearer\s+/i, "").trim(); - if (isChefsSeedJwtLike(bearerToken)) { - return bearerToken; - } - } - - if (isChefsSeedJwtLike(trimmed)) { - return trimmed; - } - - try { - return extractChefsSeedTokenFromValue(JSON.parse(trimmed)); - } catch { - return ""; - } - } - - if (Array.isArray(value)) { - for (const item of value) { - const token = extractChefsSeedTokenFromValue(item); - if (token) { - return token; - } - } - return ""; - } - - if (value && typeof value === "object") { - const obj = value as Record; - for (const key of CHEFS_SEED_TOKEN_PROPERTY_KEYS) { - const token = extractChefsSeedTokenFromValue(obj[key]); - if (token) { - return token; - } - } - return extractChefsSeedTokenFromValue(Object.values(obj)); - } - - return ""; -} - -function extractChefsSeedTokenFromStorage(win: Window): string { - const storages = [win.localStorage, win.sessionStorage]; - - for (const storage of storages) { - for (let index = 0; index < storage.length; index += 1) { - const key = storage.key(index); - if (!key) { - continue; - } - - const value = storage.getItem(key); - if (!value) { - continue; - } - - const token = extractChefsSeedTokenFromValue(value); - if (token) { - return token; - } - } - } - - return ""; -} - -function getChefsSeedHostname(baseURL: string): string { - return new URL(baseURL).hostname; -} - -function completeChefsSeedLogin( - environment: ChefsSeedEnvironment, - timeout: number, -): void { - const chefsHostname = getChefsSeedHostname(environment.baseURL); - - cy.visit(`${environment.baseURL}/app`); - - // A session from an earlier CHEFS visit this run can already be - // authenticated (LOGOUT button visible instead of LOGIN) — only drive the - // login flow when it's actually needed. - cy.get("body", { timeout }).then(($body) => { - if ($body.find("#loginButton").length === 0) { - cy.log("Already logged in to CHEFS"); - return; - } - - cy.get("#loginButton", { timeout }).should("be.visible").click(); - cy.get('[data-test="idir"]', { timeout }).should("be.visible").click(); - - cy.location("hostname", { timeout }).should((hostname) => { - const onChefs = hostname === chefsHostname; - const onBcGovIdentity = hostname.endsWith("gov.bc.ca"); - expect( - onChefs || onBcGovIdentity, - `Expected CHEFS or BC Gov identity host, got '${hostname}'`, - ).to.eq(true); - }); - - cy.location("hostname", { timeout }).then((hostname) => { - if (hostname === chefsHostname) { - cy.log("Already logged in to CHEFS"); - return; - } - - cy.get("#user", { timeout }) - .should("be.visible") - .clear() - .type(Cypress.env("test1username"), { log: false }); - - cy.get("#password", { timeout }) - .should("be.visible") - .clear() - .type(Cypress.env("test1password"), { log: false }); - - cy.contains("Continue", { timeout }).should("be.visible").click(); - - cy.location("hostname", { timeout }).should("eq", chefsHostname); - }); - }); -} - -function visitChefsSeedForm( - environment: ChefsSeedEnvironment, - timeout: number, -): void { - cy.visit(`${environment.baseURL}/app/form/submit?f=${environment.formId}`); - cy.location("hostname", { timeout }).should( - "eq", - getChefsSeedHostname(environment.baseURL), - ); - cy.location("pathname", { timeout }).should("include", "/app"); -} - /** - * Seeds exactly one submission in CHEFS via API (logs in once via the CHEFS - * UI to capture an auth token, then POSTs the submission directly) and - * writes the confirmation ID to cypress/scripts/last-submission-id.json. + * Seeds exactly one submission in CHEFS via its form-level Basic Auth API + * key (formId:apiKey) — a single direct POST, no browser/IDIR login + * required — and writes the confirmation ID to + * cypress/scripts/last-submission-id.json. + * + * This is the same auth approach used by cypress- seeder/support/apiCalls.ts, + * applied here so ApprovalFlow.cy.ts (or any other spec) can call it + * directly as a fallback when no existing submission matches its search + * criteria, without needing a separate seed step — and without the + * IDIR/MFA-picker flakiness a UI-driven login carries. * - * This is the same logic cypress/scripts/chefs-api-submission.cy.ts runs as - * a standalone spec — extracted here so ApprovalFlow.cy.ts (or any other - * spec) can call it directly as a fallback when no existing submission - * matches its search criteria, without needing a separate seed step run - * first. + * Requires a `chefsApiKey` value for the current environment, set in + * cypress/config/{env}.json (gitignored) — the form's API key from CHEFS + * form management settings. * * @returns Chainable containing the confirmation ID of the created submission */ @@ -591,7 +445,12 @@ Cypress.Commands.add("seedApprovalFlowSubmission", () => { ); } - const authTimeout = 60000; + const apiKey = Cypress.env("chefsApiKey") as string | undefined; + + expect( + apiKey, + `Missing chefsApiKey for '${envKey}' — set it in cypress/config/${envKey}.json`, + ).to.exist; return cy .readFile("cypress/scripts/chefs-api-config.json") @@ -603,7 +462,7 @@ Cypress.Commands.add("seedApprovalFlowSubmission", () => { `Missing CHEFS environment configuration for '${envKey}'`, ).to.exist; - cy.log(`🌱 Seeding submission — environment: ${envKey}`); + cy.log(`🌱 Seeding submission via CHEFS API key — environment: ${envKey}`); return cy .readFile( @@ -613,74 +472,47 @@ Cypress.Commands.add("seedApprovalFlowSubmission", () => { submissionPayload.submission.metadata.origin = environment.baseURL; submissionPayload.submission.metadata.referrer = `${environment.baseURL}/app/form/submit?f=${environment.formId}`; - let capturedToken = ""; - - cy.intercept("**/app/api/v1/**", (req) => { - const authHeader = req.headers["authorization"] as string; - if (authHeader && !capturedToken) { - capturedToken = authHeader.replace(/^Bearer\s+/i, ""); - } - }).as("chefsSeedApiCalls"); - - completeChefsSeedLogin(environment, authTimeout); - visitChefsSeedForm(environment, authTimeout); + const basicCredentials = btoa(`${environment.formId}:${apiKey}`); + const submissionUrl = `${environment.baseURL}/app/api/v1/forms/${environment.formId}/versions/${environment.versionId}/submissions`; return cy - .window({ timeout: authTimeout }) - .should((win) => { - const tokenFromStorage = extractChefsSeedTokenFromStorage(win); - const resolvedToken = capturedToken || tokenFromStorage; - - expect( - resolvedToken, - "Waiting for authenticated CHEFS API token from request or browser storage", - ).to.not.equal(""); - - if (!capturedToken && tokenFromStorage) { - capturedToken = tokenFromStorage; - } + .request({ + method: "POST", + url: submissionUrl, + headers: { + ...apiConfig.headers, + Authorization: `Basic ${basicCredentials}`, + }, + body: { + ...submissionPayload, + createdBy: `${Cypress.env("test1username")}@idir`, + updatedBy: `${Cypress.env("test1username")}@idir`, + }, + failOnStatusCode: false, }) - .then(() => { - const authToken = capturedToken; - const submissionUrl = `${environment.baseURL}/app/api/v1/forms/${environment.formId}/versions/${environment.versionId}/submissions`; + .then((response) => { + if (response.status === 401) { + throw new Error( + "Authentication failed (401) while seeding a submission via cy.seedApprovalFlowSubmission(). Check that chefsApiKey is valid for this environment's form.", + ); + } + + expect(response.status).to.be.oneOf([200, 201]); + expect(response.body).to.have.property("id"); + + const confirmationId = + response.body.confirmationId || response.body.id; + Cypress.log({ + name: "seed", + message: `✅ Seeded submission with confirmation ID: ${confirmationId}`, + }); return cy - .request({ - method: "POST", - url: submissionUrl, - headers: { - ...apiConfig.headers, - Authorization: `Bearer ${authToken}`, - Origin: environment.baseURL, - Referer: `${environment.baseURL}/app/form/submit?f=${environment.formId}`, - }, - body: submissionPayload, - failOnStatusCode: false, + .writeFile("cypress/scripts/last-submission-id.json", { + submissionId: confirmationId, + createdAt: new Date().toISOString(), }) - .then((response) => { - if (response.status === 401) { - throw new Error( - "Authentication failed (401) while seeding a submission via cy.seedApprovalFlowSubmission(). Check that test1username/test1password credentials are valid and that the CHEFS UI login succeeded.", - ); - } - - expect(response.status).to.be.oneOf([200, 201]); - expect(response.body).to.have.property("id"); - - const confirmationId = - response.body.confirmationId || response.body.id; - Cypress.log({ - name: "seed", - message: `✅ Seeded submission with confirmation ID: ${confirmationId}`, - }); - - return cy - .writeFile("cypress/scripts/last-submission-id.json", { - submissionId: confirmationId, - createdAt: new Date().toISOString(), - }) - .then(() => confirmationId as string); - }); + .then(() => confirmationId as string); }); }); }); diff --git a/applications/Unity.AutoUI/cypress/support/index.d.ts b/applications/Unity.AutoUI/cypress/support/index.d.ts index 5dd1ba1d2a..194e6a9216 100644 --- a/applications/Unity.AutoUI/cypress/support/index.d.ts +++ b/applications/Unity.AutoUI/cypress/support/index.d.ts @@ -111,10 +111,12 @@ declare namespace Cypress { fetchAllSubmissions(): Chainable; /** - * Seeds exactly one submission in CHEFS via API (logs in once via the - * CHEFS UI to capture an auth token, then POSTs the submission directly) - * and writes the confirmation ID to - * cypress/scripts/last-submission-id.json. Disabled for PROD. + * Seeds exactly one submission in CHEFS via its form-level Basic Auth + * API key (formId:apiKey) — a single direct POST, no browser/IDIR login + * required — and writes the confirmation ID to + * cypress/scripts/last-submission-id.json. Disabled for PROD. Requires + * a `chefsApiKey` value for the current environment in + * cypress/config/{env}.json. * * @returns Chainable containing the confirmation ID of the created submission */ From 0a990d8ce0bb87e8c2f3eebd2d99248045773c69 Mon Sep 17 00:00:00 2001 From: Stephan McColm Date: Mon, 17 Aug 2026 16:48:59 -0700 Subject: [PATCH 111/121] bugfix/AB#34018_fix_approval_seeder - Multi-platform compatibility. Now works in both Windows 11 and MacOS. Invoke via npx.cmd cypress run --browser chrome on Win11 --- applications/Unity.AutoUI/cypress.config.ts | 33 ++++++++++++++++++- .../Unity.AutoUI/cypress/support/auth.ts | 10 ++++-- applications/Unity.AutoUI/package.json | 16 ++++----- .../Unity.AutoUI/scripts/run-cypress.js | 25 ++++++++++++++ 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 applications/Unity.AutoUI/scripts/run-cypress.js diff --git a/applications/Unity.AutoUI/cypress.config.ts b/applications/Unity.AutoUI/cypress.config.ts index 53936349c8..4a8fb6e4eb 100644 --- a/applications/Unity.AutoUI/cypress.config.ts +++ b/applications/Unity.AutoUI/cypress.config.ts @@ -3,10 +3,30 @@ import FormData from "form-data"; import fs from "fs"; import path from "path"; +function loadLocalEnvironmentConfig(): Record { + const environmentName = ( + process.env.UNITY_CYPRESS_ENV || "dev" + ).toLowerCase(); + const environmentFilePath = path.resolve( + "cypress", + "config", + `${environmentName}.json`, + ); + + try { + const content = fs.readFileSync(environmentFilePath, "utf-8"); + return JSON.parse(content) as Record; + } catch { + return {}; + } +} + // https://docs.cypress.io/guides/references/configuration export default defineConfig({ e2e: { - setupNodeEvents(on) { + setupNodeEvents(on, config) { + const environmentConfig = loadLocalEnvironmentConfig(); + on("task", { readJsonIfExists(filePath: string): Record | null { try { @@ -59,6 +79,17 @@ export default defineConfig({ return response.json(); }, }); + + return { + ...config, + baseUrl: + (environmentConfig["webapp.url"] as string | undefined) || + config.baseUrl, + env: { + ...config.env, + ...environmentConfig, + }, + }; }, specPattern: [ "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", diff --git a/applications/Unity.AutoUI/cypress/support/auth.ts b/applications/Unity.AutoUI/cypress/support/auth.ts index aa47482b8a..8e5be6aeea 100644 --- a/applications/Unity.AutoUI/cypress/support/auth.ts +++ b/applications/Unity.AutoUI/cypress/support/auth.ts @@ -191,7 +191,10 @@ function ensureGrantApplicationsPage(timeout: number): void { * Performs the actual login flow */ function performLogin(options: LoginOptions = {}): void { - const baseUrl = options.baseUrl || (Cypress.env("webapp.url") as string); + const baseUrl = + options.baseUrl || + (Cypress.env("webapp.url") as string | undefined) || + Cypress.config("baseUrl"); const useMfa = options.useMfa || false; const timeout = options.timeout || 20000; @@ -237,7 +240,10 @@ export function loginIfNeeded( options: LoginOptions = {}, ): void { const username = options.username || (Cypress.env("test1username") as string); - const baseUrl = options.baseUrl || (Cypress.env("webapp.url") as string); + const baseUrl = + options.baseUrl || + (Cypress.env("webapp.url") as string | undefined) || + Cypress.config("baseUrl"); const sessionId = `unity-${baseUrl}-${username}`; cy.session( diff --git a/applications/Unity.AutoUI/package.json b/applications/Unity.AutoUI/package.json index cbdade5aeb..9ec62d2b54 100644 --- a/applications/Unity.AutoUI/package.json +++ b/applications/Unity.AutoUI/package.json @@ -1,13 +1,13 @@ { "scripts": { - "test": "env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/e2e/**/*.cy.ts' --browser chrome", - "test:e2e": "env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/e2e/**/*.cy.ts' --browser chrome", - "test:regression-headed": "env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/regression/**/*.cy.ts' --headed --browser chrome", - "test:regression-headless": "env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/regression/**/*.cy.ts' --headless --browser chrome", - "test:open": "env -u ELECTRON_RUN_AS_NODE cypress open --browser chrome", - "test:seed": "env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/scripts/chefs-api-submission.cy.ts' --browser chrome", - "test:approval-flow": "npm run test:seed && env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/regression/ApprovalFlow.cy.ts' --headed --browser chrome", - "test:approval-flow-headless": "npm run test:seed && env -u ELECTRON_RUN_AS_NODE cypress run --spec 'cypress/regression/ApprovalFlow.cy.ts' --browser chrome" + "test": "node ./scripts/run-cypress.js run --spec \"cypress/e2e/**/*.cy.ts\" --browser chrome", + "test:e2e": "node ./scripts/run-cypress.js run --spec \"cypress/e2e/**/*.cy.ts\" --browser chrome", + "test:regression-headed": "node ./scripts/run-cypress.js run --spec \"cypress/regression/**/*.cy.ts\" --headed --browser chrome", + "test:regression-headless": "node ./scripts/run-cypress.js run --spec \"cypress/regression/**/*.cy.ts\" --headless --browser chrome", + "test:open": "node ./scripts/run-cypress.js open --browser chrome", + "test:seed": "node ./scripts/run-cypress.js run --spec \"cypress/scripts/chefs-api-submission.cy.ts\" --browser chrome", + "test:approval-flow": "npm run test:seed && node ./scripts/run-cypress.js run --spec \"cypress/regression/ApprovalFlow.cy.ts\" --headed --browser chrome", + "test:approval-flow-headless": "npm run test:seed && node ./scripts/run-cypress.js run --spec \"cypress/regression/ApprovalFlow.cy.ts\" --browser chrome" }, "dependencies": { "form-data": "^4.0.5", diff --git a/applications/Unity.AutoUI/scripts/run-cypress.js b/applications/Unity.AutoUI/scripts/run-cypress.js new file mode 100644 index 0000000000..cbe1f4801e --- /dev/null +++ b/applications/Unity.AutoUI/scripts/run-cypress.js @@ -0,0 +1,25 @@ +const path = require("path"); +const { spawnSync } = require("child_process"); + +delete process.env.ELECTRON_RUN_AS_NODE; + +const cypressCli = path.resolve( + __dirname, + "..", + "node_modules", + "cypress", + "bin", + "cypress", +); + +const result = spawnSync(process.execPath, [cypressCli, ...process.argv.slice(2)], { + stdio: "inherit", + env: process.env, + shell: false, +}); + +if (result.error) { + throw result.error; +} + +process.exit(result.status ?? 1); From ae8afee0a97ca3258c337167a09d7ce073fe92be Mon Sep 17 00:00:00 2001 From: Jacob Smith Date: Mon, 17 Aug 2026 16:51:15 -0700 Subject: [PATCH 112/121] AB#33864 AI form scoresheet/worksheet/mapping flow standardization - Fix Razor bool hidden inputs rendering as literal 'value' by using .ToString().ToLower() for all six AI capability flags - Add missing ai-generation-api.js script include to Mapping page - Fix setAiScoresheetPending inverted if(isPending) guard so generate button is enabled when no pending review exists - Enable generate button on loadAiScoresheetReview fail and early-exit (no view permission) so button is always clickable when appropriate - Add EnsureAiOperationAccessAsync (feature + permission) to ApplicationFormVersionAppService and GrantApplicationAppService - Extract AiScoresheetSuggestionName static class; rename worksheet suffix from -field-suggestions to -worksheet for consistency - Add AIFormWorkflowApi JS wrapper for all application-form-version AI workflow endpoints; eliminate all raw abp.ajax calls in Mapping.js - Add queueFormMapping/Worksheet/Scoresheet to AIGenerationApi wrapper - Pass scoresheet capability flags through CustomFieldsViewComponent params; remove IFeatureChecker/IPermissionChecker from component - Gate Generate/Review scoresheet buttons on separate CanGenerateScoresheet and CanViewScoresheet flags - Default all suggestion toggles (mapping/worksheet/scoresheet) to unselected - Clear scoresheet title on modal render; require manual entry like worksheet - Align modal titles to 'Review X Suggestions' pattern - Align placeholder text to 'e.g., Project x' pattern - Remove unused QueueAttachmentSummaryRequestDto and legacy ensure helpers - Remove unused IApplicationChefsFileAttachmentRepository injection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Mapping/AiScoresheetSuggestionName.cs | 9 + .../Mapping/AiWorksheetSuggestionName.cs | 2 +- .../QueueAttachmentSummaryRequestDto.cs | 10 - .../ApplicationFormVersionAppService.cs | 43 +-- .../GrantApplicationAppService.cs | 96 +++---- .../Pages/ApplicationForms/Mapping.cshtml | 51 ++-- .../Pages/ApplicationForms/Mapping.js | 272 ++++++++++-------- .../ApplicationForms/ai-form-workflow-api.js | 84 ++++++ .../GrantApplications/ai-generation-api.js | 18 ++ .../CustomFields/CustomFieldsViewComponent.cs | 8 +- .../CustomFields/CustomFieldsViewModel.cs | 2 + .../Components/CustomFields/Default.cshtml | 7 +- 12 files changed, 372 insertions(+), 230 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetSuggestionName.cs delete mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/ai-form-workflow-api.js diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetSuggestionName.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetSuggestionName.cs new file mode 100644 index 0000000000..637caa2dae --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiScoresheetSuggestionName.cs @@ -0,0 +1,9 @@ +using System; + +namespace Unity.GrantManager.ApplicationForms.Mapping; + +public static class AiScoresheetSuggestionName +{ + public static string Build(Guid formId, Guid formVersionId) => + $"ai-form-{formId}-version-{formVersionId}-scoresheet"; +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiWorksheetSuggestionName.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiWorksheetSuggestionName.cs index c85450926a..956fd1cb7b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiWorksheetSuggestionName.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/Mapping/AiWorksheetSuggestionName.cs @@ -5,5 +5,5 @@ namespace Unity.GrantManager.ApplicationForms.Mapping; public static class AiWorksheetSuggestionName { public static string Build(Guid formId, Guid formVersionId) => - $"ai-form-{formId}-version-{formVersionId}-field-suggestions"; + $"ai-form-{formId}-version-{formVersionId}-worksheet"; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs deleted file mode 100644 index dd89554bfe..0000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/QueueAttachmentSummaryRequestDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Unity.GrantManager.GrantApplications; - -public class QueueAttachmentSummaryRequestDto -{ - public Guid ApplicationId { get; set; } - public List? AttachmentIds { get; set; } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs index 1a8192eb69..b014b68d96 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs @@ -13,9 +13,9 @@ using Unity.AI.Localization; using Unity.AI.Generation; using Unity.AI.Operations; -using Unity.AI.Permissions; using Unity.AI.Requests; using Unity.AI.Runtime.Execution; +using Unity.AI.Settings; using Unity.Flex.Domain.Worksheets; using Unity.Flex.Domain.Scoresheets; using Unity.Flex.Scoresheets.Enums; @@ -51,6 +51,7 @@ public class ApplicationFormVersionAppService( IApplicationFormSubmissionRepository formSubmissionRepository, IReportingFieldsGeneratorService reportingFieldsGeneratorService, IFeatureChecker featureChecker, + AIFeatureGuard aiFeatureGuard, IStringLocalizer localizer, IAIGenerationAppService aiGenerationAppService, IWorksheetRepository worksheetRepository, @@ -70,6 +71,13 @@ public class ApplicationFormVersionAppService( { private readonly IAIGenerationAppService _aiGenerationAppService = aiGenerationAppService; + private async Task EnsureAiOperationAccessAsync(string operationType, bool requiresGeneratePermission) + { + var operation = AIGenerationOperations.Get(operationType); + await aiFeatureGuard.EnsureEnabledAsync(operation.FeatureName, operation.DisabledLocalizationKey); + await CheckPolicyAsync(requiresGeneratePermission ? operation.GeneratePermission : operation.ViewPermission); + } + public override async Task CreateAsync(CreateUpdateApplicationFormVersionDto input) => await base.CreateAsync(input); @@ -379,7 +387,7 @@ await _aiGenerationAppService.SubmitAsync( [HttpGet("api/app/application-form-version/mapping-review")] public virtual async Task GetMappingReviewAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.ViewFormMapping); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: false); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormMapping, formVersionId); @@ -391,7 +399,7 @@ public virtual async Task AcceptMappingSugges Guid formVersionId, AcceptMappingSuggestionsDto input) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormMapping); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: true); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormMapping, formVersionId) @@ -439,7 +447,7 @@ public virtual async Task AcceptMappingSugges [HttpPost("api/app/application-form-version/discard-mapping-suggestions")] public virtual async Task DiscardMappingSuggestionsAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormMapping); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: true); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormMapping, formVersionId); @@ -458,7 +466,7 @@ public virtual async Task DiscardMappingSuggestionsAsync(Guid formVersionId) [HttpPost("api/app/application-form-version/mapping-review-phase")] public virtual async Task SetMappingReviewPhaseAsync(Guid formVersionId, FormMappingReviewPhase phase) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormMapping); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: true); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormMapping, formVersionId); @@ -511,8 +519,8 @@ public virtual async Task SetMappingReviewPhaseAsync(Guid formVersionId, FormMap [HttpPost("api/app/application-form-version/reset-ai-flow")] public virtual async Task ResetAiFlowAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormMapping); - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormWorksheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: true); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormWorksheet, requiresGeneratePermission: true); var formVersion = await Repository.GetAsync(formVersionId); var mappingReviews = await generationReviewRepository.GetListByOperationAndFormVersionAsync(AIGenerationOperations.FormMapping, formVersionId); var worksheetReviews = await generationReviewRepository.GetListByOperationAndFormVersionAsync(AIGenerationOperations.FormWorksheet, formVersionId); @@ -545,7 +553,7 @@ public virtual async Task ResetAiFlowAsync(Guid formVersionId) [HttpPost("api/app/application-form-version/finalize-mapping-review")] public virtual async Task FinalizeMappingReviewAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormMapping); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormMapping, requiresGeneratePermission: true); var formVersion = await Repository.GetAsync(formVersionId); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormMapping, @@ -577,7 +585,7 @@ await _aiGenerationAppService.SubmitAsync( [HttpGet("api/app/application-form-version/pending-ai-worksheet")] public virtual async Task GetPendingAiWorksheetAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.ViewFormWorksheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormWorksheet, requiresGeneratePermission: false); var worksheet = await GetPendingAiWorksheetEntityAsync(formVersionId); return worksheet == null ? null : MapAiWorksheetReview(worksheet); @@ -586,7 +594,7 @@ await _aiGenerationAppService.SubmitAsync( [HttpPost("api/app/application-form-version/create-ai-worksheet-draft")] public virtual async Task CreateAiWorksheetDraftAsync(Guid formVersionId, CreateAiWorksheetDraftDto input) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormWorksheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormWorksheet, requiresGeneratePermission: true); var worksheet = await GetPendingAiWorksheetEntityAsync(formVersionId); if (worksheet == null || worksheet.Id != input.SessionId) { @@ -675,7 +683,7 @@ public virtual async Task CreateAiWorksheetDraftAsync(Guid formVersionId, Create [HttpPost("api/app/application-form-version/discard-ai-worksheet-suggestions")] public virtual async Task DiscardAiWorksheetSuggestionsAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormWorksheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormWorksheet, requiresGeneratePermission: true); var worksheet = await GetPendingAiWorksheetEntityAsync(formVersionId); if (worksheet != null) { @@ -694,7 +702,7 @@ public virtual async Task DiscardAiWorksheetSuggestionsAsync(Guid formVersionId) [HttpGet("api/app/application-form-version/pending-ai-scoresheet")] public virtual async Task GetPendingAiScoresheetAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.ViewFormScoresheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormScoresheet, requiresGeneratePermission: false); var review = await generationReviewRepository.FindLatestByOperationAndFormVersionAsync( AIGenerationOperations.FormScoresheet, @@ -706,14 +714,14 @@ public virtual async Task DiscardAiWorksheetSuggestionsAsync(Guid formVersionId) var formVersion = await formVersionRepository.GetAsync(formVersionId); var scoresheet = await scoresheetRepository.GetByNameAsync( - BuildAiScoresheetSuggestionName(formVersion.ApplicationFormId, formVersion.Id), true); + AiScoresheetSuggestionName.Build(formVersion.ApplicationFormId, formVersion.Id), true); return scoresheet?.Published == false ? MapAiScoresheetReview(scoresheet) : null; } [HttpPost("api/app/application-form-version/create-ai-scoresheet-draft")] public virtual async Task CreateAiScoresheetDraftAsync(Guid formVersionId, CreateAiScoresheetDraftDto input) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormScoresheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormScoresheet, requiresGeneratePermission: true); var suggestion = await GetPendingAiScoresheetEntityAsync(formVersionId); if (suggestion == null || suggestion.Id != input.SessionId) @@ -800,7 +808,7 @@ public virtual async Task CreateAiScoresheetDraftAsync(Guid formVersionId, Creat [HttpPost("api/app/application-form-version/discard-ai-scoresheet-suggestions")] public virtual async Task DiscardAiScoresheetSuggestionsAsync(Guid formVersionId) { - await CheckPolicyAsync(AIPermissions.Analysis.GenerateFormScoresheet); + await EnsureAiOperationAccessAsync(AIGenerationOperations.FormScoresheet, requiresGeneratePermission: true); var suggestion = await GetPendingAiScoresheetEntityAsync(formVersionId); if (suggestion == null) { @@ -830,13 +838,10 @@ public virtual async Task DiscardAiScoresheetSuggestionsAsync(Guid formVersionId var formVersion = await formVersionRepository.GetAsync(formVersionId); var scoresheet = await scoresheetRepository.GetByNameAsync( - BuildAiScoresheetSuggestionName(formVersion.ApplicationFormId, formVersion.Id), true); + AiScoresheetSuggestionName.Build(formVersion.ApplicationFormId, formVersion.Id), true); return scoresheet?.Published == false ? scoresheet : null; } - private static string BuildAiScoresheetSuggestionName(Guid formId, Guid formVersionId) => - $"ai-form-{formId}-version-{formVersionId}-scoresheet"; - private static AiScoresheetReviewDto MapAiScoresheetReview(Scoresheet scoresheet) => new() { SessionId = scoresheet.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 536b503fce..fc36d12956 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs @@ -12,8 +12,10 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Unity.AI.Generation; using Unity.AI.Models; using Unity.AI.Responses; +using Unity.AI.Settings; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; using Unity.GrantManager.Applicants; @@ -46,7 +48,6 @@ namespace Unity.GrantManager.GrantApplications; public class GrantApplicationAppService( IApplicationManager applicationManager, IApplicationRepository applicationRepository, - IApplicationChefsFileAttachmentRepository applicationChefsFileAttachmentRepository, IApplicationStatusRepository applicationStatusRepository, IApplicationFormSubmissionRepository applicationFormSubmissionRepository, IApplicantRepository applicantRepository, @@ -55,7 +56,8 @@ public class GrantApplicationAppService( IApplicantAddressRepository applicantAddressRepository, IApplicantSupplierAppService applicantSupplierService, IPaymentRequestAppService paymentRequestService, - IFeatureChecker featureChecker) + IFeatureChecker featureChecker, + AIFeatureGuard aiFeatureGuard) : GrantManagerAppService, IGrantApplicationAppService #pragma warning restore S107 // Methods should not have too many parameters { @@ -69,6 +71,25 @@ public class GrantApplicationAppService( WriteIndented = true }; + private async Task EnsureAiOperationAccessAsync(string operationType, bool requiresGeneratePermission) + { + var operation = AIGenerationOperations.Get(operationType); + await aiFeatureGuard.EnsureEnabledAsync(operation.FeatureName, operation.DisabledLocalizationKey); + await CheckPolicyAsync(requiresGeneratePermission ? operation.GeneratePermission : operation.ViewPermission); + } + + private async Task CanAccessAiOperationAsync(string operationType, bool requiresGeneratePermission) + { + var operation = AIGenerationOperations.Get(operationType); + if (!await featureChecker.IsEnabledAsync(operation.FeatureName)) + { + return false; + } + + var permission = requiresGeneratePermission ? operation.GeneratePermission : operation.ViewPermission; + return await AuthorizationService.IsGrantedAsync(permission); + } + public async Task> GetListAsync(GrantApplicationListInputDto input) { var listRecords = await applicationRepository.GetApplicationListRecordsAsync( @@ -352,7 +373,15 @@ public async Task GetAsync(Guid id) appDto.SectorSubSectorIndustryDesc = application.Applicant.SectorSubSectorIndustryDesc; } - appDto.AIAnalysisData = ParseAiAnalysisData(appDto.AIAnalysis); + if (await CanAccessAiOperationAsync(AIGenerationOperations.ApplicationAnalysis, requiresGeneratePermission: false)) + { + appDto.AIAnalysisData = ParseAiAnalysisData(appDto.AIAnalysis); + } + else + { + appDto.AIAnalysis = null; + appDto.AIAnalysisData = null; + } return appDto; } @@ -1200,65 +1229,6 @@ await LocalEventBus.PublishAsync( return applicationManager.GetWorkflowDiagram(isDirectApproval); } - private async Task EnsureAttachmentSummariesEnabledAsync() - { - if (!await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries")) - { - throw new UserFriendlyException("AI attachment summaries are not enabled."); - } - } - - private async Task> ResolveAttachmentSummaryIdsAsync(QueueAttachmentSummaryRequestDto input) - { - if (input == null) - { - throw new UserFriendlyException("Attachment summary request is required."); - } - - if (input.ApplicationId == Guid.Empty) - { - throw new UserFriendlyException("Application id is required."); - } - - var applicationAttachmentIds = (await applicationChefsFileAttachmentRepository.GetListAsync(a => a.ApplicationId == input.ApplicationId)) - .Select(a => a.Id) - .ToList(); - - if (applicationAttachmentIds.Count == 0) - { - throw new UserFriendlyException("No attachments were found to generate summaries."); - } - - if (input.AttachmentIds is not { Count: > 0 }) - { - return applicationAttachmentIds; - } - - var applicationAttachmentIdSet = applicationAttachmentIds.ToHashSet(); - var selectedAttachmentIds = input.AttachmentIds.Distinct().ToList(); - if (selectedAttachmentIds.Any(id => !applicationAttachmentIdSet.Contains(id))) - { - throw new UserFriendlyException("One or more selected attachments do not belong to the application."); - } - - return selectedAttachmentIds; - } - - private async Task EnsureAIAnalysisEnabledAsync() - { - if (!await featureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis")) - { - throw new UserFriendlyException("AI application analysis is not enabled."); - } - } - - private async Task EnsureScoringEnabledAsync() - { - if (!await featureChecker.IsEnabledAsync("Unity.AI.Scoring")) - { - throw new UserFriendlyException("AI scoring is not enabled."); - } - } #endregion APPLICATION WORKFLOW public async Task> GetAllApplicationsAsync() @@ -1329,11 +1299,13 @@ private static Dictionary ExtractCustomFieldsForWorksheet(dynami public async Task DismissAIAnalysisItemAsync(Guid applicationId, string itemId) { + await EnsureAiOperationAccessAsync(AIGenerationOperations.ApplicationAnalysis, requiresGeneratePermission: true); return await UpdateAIAnalysisItemDismissedStateAsync(applicationId, itemId, isDismissed: true); } public async Task RestoreAIAnalysisItemAsync(Guid applicationId, string itemId) { + await EnsureAiOperationAccessAsync(AIGenerationOperations.ApplicationAnalysis, requiresGeneratePermission: true); return await UpdateAIAnalysisItemDismissedStateAsync(applicationId, itemId, isDismissed: false); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml index 2d9e883c5c..c825bd8936 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml @@ -21,12 +21,18 @@ PageLayout.Content.MenuItemName = "GrantManager.ApplicationForms"; PageLayout.Content.Title = "Application Mapping"; ViewBag.PageTitle = "Application Forms Mapping"; + var canViewFormMapping = await FeatureChecker.IsEnabledAsync("Unity.AI.FormMapping") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.View); + var canGenerateFormMapping = await FeatureChecker.IsEnabledAsync("Unity.AI.FormMapping") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate); + var canViewFormWorksheet = await FeatureChecker.IsEnabledAsync("Unity.AI.FormWorksheet") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.View); + var canGenerateFormWorksheet = await FeatureChecker.IsEnabledAsync("Unity.AI.FormWorksheet") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate); + var canViewFormScoresheet = await FeatureChecker.IsEnabledAsync("Unity.AI.FormScoresheet") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.View); + var canGenerateFormScoresheet = await FeatureChecker.IsEnabledAsync("Unity.AI.FormScoresheet") && await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.Generate); var mappingReviewModal = new AiSuggestionReviewModalModel { ModalId = "aiMappingReviewModal", ModalLabelId = "aiMappingReviewModalLabel", - CanMutate = await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate), - Title = "Review AI Mapping Suggestions", + CanMutate = canGenerateFormMapping, + Title = "Review Mapping Suggestions", SourceColumnTitle = "CHEFS Field", TargetColumnTitle = "Unity Core Field", FieldsId = "aiMappingReviewFields", @@ -43,8 +49,8 @@ { ModalId = "aiWorksheetReviewModal", ModalLabelId = "aiWorksheetReviewModalLabel", - CanMutate = await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate), - Title = "Create Worksheet Draft", + CanMutate = canGenerateFormWorksheet, + Title = "Review Worksheet Suggestions", SourceColumnTitle = "Source Field", TargetColumnTitle = "Worksheet Field", FieldsId = "aiWorksheetReviewFields", @@ -54,7 +60,7 @@ SelectAllId = "aiWorksheetReviewSelectAll", TitleInputId = "aiWorksheetTitle", TitleInputLabel = "Worksheet Title", - TitleInputPlaceholder = "e.g., Project details", + TitleInputPlaceholder = "e.g., Project worksheet", PrimaryActionId = "btn-create-ai-worksheet-draft", PrimaryActionText = "Create Draft", PrimaryActionDisabled = true, @@ -65,8 +71,8 @@ { ModalId = "aiScoresheetReviewModal", ModalLabelId = "aiScoresheetReviewModalLabel", - CanMutate = await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.Generate), - Title = "Review AI Scoresheet Suggestions", + CanMutate = canGenerateFormScoresheet, + Title = "Review Scoresheet Suggestions", SourceColumnTitle = "Generated Question", TargetColumnTitle = "Section", FieldsId = "aiScoresheetReviewFields", @@ -76,7 +82,7 @@ SelectAllId = "aiScoresheetReviewSelectAll", TitleInputId = "aiScoresheetTitle", TitleInputLabel = "Scoresheet Title", - TitleInputPlaceholder = "e.g., Application Assessment", + TitleInputPlaceholder = "e.g., Project scoresheet", PrimaryActionId = "btn-create-ai-scoresheet-draft", PrimaryActionText = "Add to Scoresheet", PrimaryActionDisabled = true, @@ -90,6 +96,8 @@ { + + @@ -108,6 +116,12 @@ + + + + + + @@ -167,7 +181,7 @@
Mapping Configuration
- @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate)) + @if (canGenerateFormMapping) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.View)) + @if (canViewFormMapping) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate)) + @if (canGenerateFormWorksheet) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.View)) + @if (canViewFormWorksheet) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate)) + @if (canGenerateFormWorksheet) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate)) + @if (canGenerateFormMapping) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.View)) + @if (canViewFormMapping) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormMapping.Generate) - && await PermissionChecker.IsGrantedAsync(AIPermissions.FormWorksheet.Generate)) + @if (canGenerateFormMapping && canGenerateFormWorksheet) {
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js index f6d6de9c81..0ef1a0139a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js @@ -1,5 +1,4 @@ $(function () { - const aiL = abp.localization.getResource('AI'); let availableChefFieldsString = document.getElementById('availableChefsFields').value; let existingMappingString = document.getElementById('existingMapping').value; let intakeFieldsString = document.getElementById('intakeProperties').value; @@ -146,10 +145,7 @@ $(function () { primaryText: 'Add to Scoresheet', continueReview: function () { const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/discard-ai-scoresheet-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { + globalThis.AIFormWorkflowApi.discardScoresheetSuggestions(formVersion).done(function () { setAiScoresheetPending(false); UIElements.scoresheetReviewModal.modal('hide'); loadMappingReview(false); @@ -172,6 +168,7 @@ $(function () { bindExistingMaps(); setupTooltips(); initializeUIConfiguration(); + restoreActiveGenerationMonitors(); loadMappingReview(false); loadAiScoresheetReview(false); } @@ -182,13 +179,86 @@ $(function () { }); } - function startWorksheetPhase(callback) { + function isCapabilityEnabled(flagId) { + return String($(`#${flagId}`).val() ?? '').toLowerCase() === 'true'; + } + + function isAiFormScoresheetViewEnabled() { + return isCapabilityEnabled('AIFormScoresheetViewEnabled'); + } + + function isAiFormScoresheetGenerateEnabled() { + return isCapabilityEnabled('AIFormScoresheetGenerateEnabled'); + } + + function isAiFormMappingViewEnabled() { + return isCapabilityEnabled('AIFormMappingViewEnabled'); + } + + function isAiFormMappingGenerateEnabled() { + return isCapabilityEnabled('AIFormMappingGenerateEnabled'); + } + + function isAiFormWorksheetViewEnabled() { + return isCapabilityEnabled('AIFormWorksheetViewEnabled'); + } + + function isAiFormWorksheetGenerateEnabled() { + return isCapabilityEnabled('AIFormWorksheetGenerateEnabled'); + } + + function resumeGenerationMonitorOnLoad({ isEnabled, $button, operationType, monitorGeneration }) { + if (!isEnabled || !$button?.length) { + return; + } + + const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); + if (!validateGuid(applicationId)) { + return; + } + + globalThis.AIGenerationApi.getStatus(applicationId, operationType).done(function (generationStatus) { + if (generationStatus?.generationRequest?.isActive !== true) { + return; + } + + const existingHtml = $button.html(); + globalThis.AIGenerationButtonState?.setGenerating($button); + monitorGeneration(applicationId, $button, existingHtml); + }); + } + + function restoreActiveGenerationMonitors() { + resumeGenerationMonitorOnLoad({ + isEnabled: isAiFormMappingGenerateEnabled(), + $button: UIElements.btnGenerate, + operationType: 'form-mapping', + monitorGeneration: monitorFormMappingGeneration + }); + + resumeGenerationMonitorOnLoad({ + isEnabled: isAiFormWorksheetGenerateEnabled(), + $button: UIElements.btnGenerateWorksheet, + operationType: 'form-worksheet', + monitorGeneration: monitorFormWorksheetGeneration + }); + + resumeGenerationMonitorOnLoad({ + isEnabled: isAiFormScoresheetGenerateEnabled(), + $button: UIElements.btnGenerateScoresheet, + operationType: 'form-scoresheet', + monitorGeneration: monitorFormScoresheetGeneration + }); + } + + function transitionToWorksheetReview(onSuccess, fallbackErrorMessage) { const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, - type: 'POST' - }).done(callback).fail(function () { - abp.notify.error('', 'Unable to start worksheet generation.'); + return globalThis.AIFormWorkflowApi.startWorksheetReviewPhase(formVersion).done(function (result) { + if (typeof onSuccess === 'function') { + onSuccess(result); + } + }).fail(function (error) { + abp.notify.error('', error?.responseJSON?.error?.message || fallbackErrorMessage); }); } @@ -209,11 +279,6 @@ $(function () { UIElements.btnGenerateFinalMapping.on('click', finalizeMappingReview); UIElements.btnRestartAiFlow.on('click', restartAiFlow); UIElements.btnGenerateScoresheet.on('click', function () { - if (UIElements.btnGenerateScoresheet.attr('data-ai-can-generate') !== 'true') { - abp.notify.error('', aiL('AI:GenerateFormScoresheetPermissionRequired')); - return; - } - queueFormScoresheet(this); }); UIElements.btnReviewScoresheet.on('click', function () { @@ -311,6 +376,10 @@ $(function () { } function queueFormMapping(triggerButton = null) { + if (!isAiFormMappingGenerateEnabled() || !UIElements.btnGenerate.length) { + return; + } + if (UIElements.btnGenerate.attr('data-ai-pending') === 'true') { loadMappingReview(true); return; @@ -342,10 +411,7 @@ $(function () { globalThis.AIGenerationButtonState?.setGenerating($button); - abp.ajax({ - url: `/api/app/ai/generation/form-mapping?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) + globalThis.AIGenerationApi.queueFormMapping(applicationId, formVersion) .done(function (generationStatus) { const request = generationStatus?.generationRequest; const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; @@ -371,6 +437,10 @@ $(function () { } function queueFormWorksheet(triggerButton = null) { + if (!isAiFormWorksheetGenerateEnabled() || !UIElements.btnGenerateWorksheet.length) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); if (!validateGuid(formVersion) || !validateGuid(applicationId)) { @@ -385,9 +455,9 @@ $(function () { return; } - startWorksheetPhase(function () { + transitionToWorksheetReview(function () { queueFormWorksheetCore(buttonElement); - }); + }, 'Unable to start worksheet generation.'); } function queueFormWorksheetCore(triggerButton = null) { @@ -399,10 +469,7 @@ $(function () { globalThis.AIGenerationButtonState?.setGenerating($button); - abp.ajax({ - url: `/api/app/ai/generation/form-worksheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) + globalThis.AIGenerationApi.queueFormWorksheet(applicationId, formVersion) .done(function (generationStatus) { const request = generationStatus?.generationRequest; const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; @@ -427,13 +494,14 @@ $(function () { } function monitorFormWorksheetGeneration(applicationId, $button, existingHtml) { + if (!isAiFormWorksheetGenerateEnabled() || !$button || !$button.length) { + return; + } + globalThis.AIGenerationButtonState?.monitor({ $button, originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-worksheet`, - type: 'GET' - }), + getStatus: () => globalThis.AIGenerationApi.getStatus(applicationId, 'form-worksheet'), onComplete: function () { refreshWorksheetAfterGeneration(); }, @@ -447,6 +515,10 @@ $(function () { } function queueFormScoresheet(triggerButton = null) { + if (!isAiFormScoresheetGenerateEnabled() || !UIElements.btnGenerateScoresheet.length) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); if (!validateGuid(formVersion) || !validateGuid(applicationId)) { @@ -464,10 +536,7 @@ $(function () { globalThis.AIGenerationButtonState?.setGenerating($button); - abp.ajax({ - url: `/api/app/ai/generation/form-scoresheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - }) + globalThis.AIGenerationApi.queueFormScoresheet(applicationId, formVersion) .done(function (generationStatus) { const request = generationStatus?.generationRequest; const status = globalThis.AIGenerationButtonState?.resolveStatus(request?.status) ?? ''; @@ -492,13 +561,14 @@ $(function () { } function monitorFormScoresheetGeneration(applicationId, $button, existingHtml) { + if (!isAiFormScoresheetGenerateEnabled() || !$button || !$button.length) { + return; + } + globalThis.AIGenerationButtonState?.monitor({ $button, originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-scoresheet`, - type: 'GET' - }), + getStatus: () => globalThis.AIGenerationApi.getStatus(applicationId, 'form-scoresheet'), onComplete: function () { refreshScoresheetAfterGeneration(); }, @@ -532,16 +602,17 @@ $(function () { } function loadAiWorksheetReview() { + if (!isAiFormWorksheetViewEnabled() || !UIElements.btnReviewWorksheet.length) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); if (!validateGuid(formVersion)) { abp.notify.error('', 'Unable to review the worksheet because the Form Version ID is invalid.'); return; } - abp.ajax({ - url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) + globalThis.AIFormWorkflowApi.getPendingWorksheet(formVersion) .done(function (worksheet) { if (!worksheet) { setAiWorksheetPending(false); @@ -587,7 +658,7 @@ $(function () { .attr('id', fieldId) .attr('data-field-id', field.id) .attr('aria-label', `Include ${field.label || field.key || 'field'}`) - .prop('checked', field.selected !== false) + .prop('checked', false) .appendTo($switchContainer); $switchContainer.appendTo($switch); $switch.appendTo($row); @@ -636,12 +707,7 @@ $(function () { UIElements.btnCreateWorksheetDraft.prop('disabled', true); UIElements.btnDiscardWorksheet.prop('disabled', true); - abp.ajax({ - url: `/api/app/application-form-version/create-ai-worksheet-draft?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ sessionId, title, selectedFieldIds }) - }) + globalThis.AIFormWorkflowApi.createWorksheetDraft(formVersion, { sessionId, title, selectedFieldIds }) .done(function () { UIElements.worksheetTitle.val(''); abp.notify.success('', 'Draft worksheet created.'); @@ -657,10 +723,7 @@ $(function () { } function refreshAiWorksheetReviewAfterDraftCreation(formVersion) { - abp.ajax({ - url: `/api/app/application-form-version/pending-ai-worksheet?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) + globalThis.AIFormWorkflowApi.getPendingWorksheet(formVersion) .done(function (worksheet) { if (!worksheet) { UIElements.worksheetReviewModal.modal('hide'); @@ -692,10 +755,7 @@ $(function () { UIElements.btnCreateWorksheetDraft.prop('disabled', true); UIElements.btnDiscardWorksheet.prop('disabled', true); - abp.ajax({ - url: `/api/app/application-form-version/discard-ai-worksheet-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }) + globalThis.AIFormWorkflowApi.discardWorksheetSuggestions(formVersion) .done(function () { setAiWorksheetPending(false); UIElements.worksheetReviewModal.modal('hide'); @@ -720,12 +780,13 @@ $(function () { } function finalizeMappingReview() { + if (!isAiFormMappingGenerateEnabled()) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); const applicationId = String(document.getElementById('applicationFormId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/finalize-mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { + globalThis.AIFormWorkflowApi.finalizeMappingReview(formVersion).done(function () { monitorFormMappingGeneration(applicationId, UIElements.btnGenerate, UIElements.btnGenerate.html()); }).fail(function (error) { abp.notify.error('', error?.responseJSON?.error?.message || 'Publish and assign all AI worksheet drafts before generating mapping.'); @@ -734,11 +795,12 @@ $(function () { } function checkMappingReviewComplete() { + if (!isAiFormMappingViewEnabled()) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }).done(function (review) { + globalThis.AIFormWorkflowApi.getMappingReview(formVersion).done(function (review) { if (!review?.pendingSuggestions?.length) { UIElements.mappingReviewModal.modal('hide'); if (isFinalMappingPhase(review.phase)) { @@ -752,10 +814,7 @@ $(function () { function completeMappingReview() { const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); - abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=Completed`, - type: 'POST' - }).done(function () { + globalThis.AIFormWorkflowApi.completeMappingReviewPhase(formVersion).done(function () { UIElements.btnGenerate.attr('data-ai-pending', 'false'); abp.notify.success('', 'AI mapping review completed.'); }); @@ -774,7 +833,7 @@ $(function () { UIElements.btnGenerateScoresheet.toggleClass('d-none', isPending); UIElements.btnReviewScoresheet.toggleClass('d-none', !isPending); - if (isPending) { + if (!isPending) { UIElements.btnGenerateScoresheet .removeAttr('data-ai-cooldown-checking data-ai-rate-limit-disabled') .prop('disabled', false); @@ -782,15 +841,17 @@ $(function () { } function loadAiScoresheetReview(showModal = true, showEmpty = true) { + if (!isAiFormScoresheetViewEnabled() || !UIElements.btnReviewScoresheet.length) { + setAiScoresheetPending(false); + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); if (!validateGuid(formVersion)) { return; } - return abp.ajax({ - url: `/api/app/application-form-version/pending-ai-scoresheet?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }).done(function (review) { + return globalThis.AIFormWorkflowApi.getPendingScoresheet(formVersion).done(function (review) { if (!review) { setAiScoresheetPending(false); if (showModal && showEmpty) { @@ -814,6 +875,7 @@ $(function () { } } }).fail(function () { + setAiScoresheetPending(false); if (showModal) { abp.notify.error('', 'Unable to load AI scoresheet suggestions.'); } @@ -824,7 +886,7 @@ $(function () { resetEmptyReviewModal(reviewConfigs.scoresheet); UIElements.scoresheetReviewFields.empty(); UIElements.scoresheetReviewSelectAll.prop('checked', false); - UIElements.scoresheetTitle.val(review.title || ''); + UIElements.scoresheetTitle.val(''); UIElements.scoresheetReviewFields.attr('data-session-id', review.sessionId || ''); const sections = review.sections || []; @@ -850,7 +912,7 @@ $(function () { class: 'form-check-input', 'data-question-id': question.id, 'data-question-section-id': section.id, - checked: question.selected !== false + checked: false }); $('
', { class: 'ai-suggestion-review__source', text: question.label || question.name || '' }).appendTo($row); if (!UIElements.scoresheetReviewModal.find('.ai-suggestion-review__panel').attr('data-hide-target-column')) { @@ -918,11 +980,10 @@ $(function () { } UIElements.btnCreateScoresheetDraft.prop('disabled', true); - abp.ajax({ - url: `/api/app/application-form-version/create-ai-scoresheet-draft?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ sessionId: sessionId, title: title, selectedQuestionIds: selectedQuestionIds }) + globalThis.AIFormWorkflowApi.createScoresheetDraft(formVersion, { + sessionId: sessionId, + title: title, + selectedQuestionIds: selectedQuestionIds }).done(function () { abp.notify.success('', 'Scoresheet draft created.'); loadAiScoresheetReview(true, false); @@ -941,10 +1002,7 @@ $(function () { .then(function (confirmed) { if (!confirmed) return; UIElements.btnDiscardScoresheet.prop('disabled', true); - abp.ajax({ - url: `/api/app/application-form-version/discard-ai-scoresheet-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { + globalThis.AIFormWorkflowApi.discardScoresheetSuggestions(formVersion).done(function () { setAiScoresheetPending(false); UIElements.scoresheetReviewModal.modal('hide'); abp.notify.success('', 'Remaining AI scoresheet suggestions discarded.'); @@ -957,13 +1015,14 @@ $(function () { } function monitorFormMappingGeneration(applicationId, $button, existingHtml) { + if (!isAiFormMappingGenerateEnabled() || !$button || !$button.length) { + return; + } + globalThis.AIGenerationButtonState?.monitor({ $button, originalHtml: existingHtml, - getStatus: () => abp.ajax({ - url: `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=form-mapping`, - type: 'GET' - }), + getStatus: () => globalThis.AIGenerationApi.getStatus(applicationId, 'form-mapping'), onComplete: function () { refreshMappingAfterGeneration(applicationId); }, @@ -977,15 +1036,16 @@ $(function () { } function loadMappingReview(showModal = true) { + if (!isAiFormMappingViewEnabled() || (!UIElements.btnReviewMapping.length && !UIElements.btnReviewFinalMapping.length)) { + return; + } + const formVersion = String(document.getElementById('formVersionId')?.value ?? '').trim(); if (!validateGuid(formVersion)) { return; } - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'GET' - }) + return globalThis.AIFormWorkflowApi.getMappingReview(formVersion) .done(function (review) { setAiWorkflowReady(); updateWorkflowActions(review); @@ -1183,12 +1243,7 @@ $(function () { UIElements.btnAddMapping.prop('disabled', true); let result; try { - result = await abp.ajax({ - url: `/api/app/application-form-version/accept-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ suggestionIds }) - }); + result = await globalThis.AIFormWorkflowApi.acceptMappingSuggestions(formVersion, suggestionIds); } catch (error) { abp.notify.error( '', @@ -1219,10 +1274,7 @@ $(function () { return; } - return abp.ajax({ - url: `/api/app/application-form-version/discard-mapping-suggestions?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }) + return globalThis.AIFormWorkflowApi.discardMappingSuggestions(formVersion) .done(function () { UIElements.mappingReviewModal.modal('hide'); if (isFinalMappingPhase(UIElements.mappingReviewFields.attr('data-phase'))) { @@ -1252,10 +1304,7 @@ $(function () { } UIElements.btnRestartAiFlow.prop('disabled', true); - return abp.ajax({ - url: `/api/app/application-form-version/reset-ai-flow?formVersionId=${encodeURIComponent(formVersion)}`, - type: 'POST' - }).done(function () { + return globalThis.AIFormWorkflowApi.resetAiFlow(formVersion).done(function () { globalThis.location.reload(); }).fail(function (error) { abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to restart the AI flow.'); @@ -1271,14 +1320,9 @@ $(function () { return; } - return abp.ajax({ - url: `/api/app/application-form-version/mapping-review-phase?formVersionId=${encodeURIComponent(formVersion)}&phase=WorksheetReview`, - type: 'POST' - }).done(function () { + return transitionToWorksheetReview(function () { loadMappingReview(false); - }).fail(function (error) { - abp.notify.error('', error?.responseJSON?.error?.message || 'Unable to continue to worksheet generation.'); - }); + }, 'Unable to continue to worksheet generation.'); } function refreshMappingAfterGeneration(applicationId, formVersion = null) { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/ai-form-workflow-api.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/ai-form-workflow-api.js new file mode 100644 index 0000000000..1b772aaecc --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/ai-form-workflow-api.js @@ -0,0 +1,84 @@ +(function (global) { + function request(url, type, data = null, contentType = null) { + const options = { url, type }; + if (data !== null) { + options.data = contentType === 'application/json' && typeof data !== 'string' + ? JSON.stringify(data) + : data; + } + if (contentType) { + options.contentType = contentType; + } + + return abp.ajax(options); + } + + function workflowUrl(path, formVersionId, query = '') { + return `/api/app/application-form-version/${path}?formVersionId=${encodeURIComponent(formVersionId)}${query}`; + } + + global.AIFormWorkflowApi = { + startWorksheetReviewPhase(formVersionId) { + return request(workflowUrl('mapping-review-phase', formVersionId, '&phase=WorksheetReview'), 'POST'); + }, + completeMappingReviewPhase(formVersionId) { + return request(workflowUrl('mapping-review-phase', formVersionId, '&phase=Completed'), 'POST'); + }, + getMappingReview(formVersionId) { + return request(workflowUrl('mapping-review', formVersionId), 'GET'); + }, + finalizeMappingReview(formVersionId) { + return request(workflowUrl('finalize-mapping-review', formVersionId), 'POST'); + }, + acceptMappingSuggestions(formVersionId, suggestionIds) { + return request( + workflowUrl('accept-mapping-suggestions', formVersionId), + 'POST', + { suggestionIds }, + 'application/json' + ); + }, + discardMappingSuggestions(formVersionId) { + return request(workflowUrl('discard-mapping-suggestions', formVersionId), 'POST'); + }, + resetAiFlow(formVersionId) { + return request(workflowUrl('reset-ai-flow', formVersionId), 'POST'); + }, + getPendingWorksheet(formVersionId) { + return request(workflowUrl('pending-ai-worksheet', formVersionId), 'GET'); + }, + createWorksheetDraft(formVersionId, input) { + return request( + workflowUrl('create-ai-worksheet-draft', formVersionId), + 'POST', + { + sessionId: input.sessionId, + title: input.title, + selectedFieldIds: input.selectedFieldIds + }, + 'application/json' + ); + }, + discardWorksheetSuggestions(formVersionId) { + return request(workflowUrl('discard-ai-worksheet-suggestions', formVersionId), 'POST'); + }, + getPendingScoresheet(formVersionId) { + return request(workflowUrl('pending-ai-scoresheet', formVersionId), 'GET'); + }, + createScoresheetDraft(formVersionId, input) { + return request( + workflowUrl('create-ai-scoresheet-draft', formVersionId), + 'POST', + { + sessionId: input.sessionId, + title: input.title, + selectedQuestionIds: input.selectedQuestionIds + }, + 'application/json' + ); + }, + discardScoresheetSuggestions(formVersionId) { + return request(workflowUrl('discard-ai-scoresheet-suggestions', formVersionId), 'POST'); + } + }; +})(globalThis); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-generation-api.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-generation-api.js index 1d592800e7..ba4d4fed47 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-generation-api.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/ai-generation-api.js @@ -38,6 +38,24 @@ 'application/json' ); }, + queueFormMapping(applicationId, applicationFormVersionId) { + return request( + `/api/app/ai/generation/form-mapping?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(applicationFormVersionId)}`, + 'POST' + ); + }, + queueFormWorksheet(applicationId, applicationFormVersionId) { + return request( + `/api/app/ai/generation/form-worksheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(applicationFormVersionId)}`, + 'POST' + ); + }, + queueFormScoresheet(applicationId, applicationFormVersionId) { + return request( + `/api/app/ai/generation/form-scoresheet?applicationId=${encodeURIComponent(applicationId)}&applicationFormVersionId=${encodeURIComponent(applicationFormVersionId)}`, + 'POST' + ); + }, getStatus(applicationId, operationType) { return request( `/api/app/ai/generation/status?applicationId=${encodeURIComponent(applicationId)}&operationType=${encodeURIComponent(operationType)}`, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewComponent.cs index 5650982d31..819485be00 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewComponent.cs @@ -33,7 +33,11 @@ public class CustomFieldsViewComponent(IApplicationFormVersionAppService applica public string AccountCodeList { get; set; } = string.Empty; - public async Task InvokeAsync(string? formVersionId, string formName) + public async Task InvokeAsync( + string? formVersionId, + string formName, + bool canViewScoresheet = false, + bool canGenerateScoresheet = false) { var model = new CustomFieldsViewModel { }; model.ChefsFormVersionId = Guid.Parse(formVersionId ?? Guid.Empty.ToString()); @@ -67,6 +71,8 @@ public async Task InvokeAsync(string? formVersionId, strin ? await applicationFormRepository.FindAsync(x => x.Id == applicationFormId.Value) : null; model.ScoresheetId = applicationForm?.ScoresheetId; + model.CanViewScoresheet = canViewScoresheet; + model.CanGenerateScoresheet = canGenerateScoresheet; var scoresheets = await scoresheetAppService.GetAllPublishedScoresheetsAsync(); model.ScoresheetOptionsList = []; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewModel.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewModel.cs index c0e544c163..85c8b68a7d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewModel.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/CustomFieldsViewModel.cs @@ -38,6 +38,8 @@ public class CustomFieldsViewModel public List? CustomTabLinks { get; set; } public bool HasPendingAiWorksheet { get; set; } + public bool CanViewScoresheet { get; set; } + public bool CanGenerateScoresheet { get; set; } [Display(Name = "")] public Guid? ScoresheetId { get; set; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml index 6bc5f0d442..5ad74efbf3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/CustomFields/Default.cshtml @@ -1,6 +1,4 @@ @using Unity.GrantManager.Web.Views.Shared.Components.CustomFields @* Suppress:S1128 *@ -@using Unity.AI.Permissions -@inject Volo.Abp.Authorization.Permissions.IPermissionChecker PermissionChecker @model CustomFieldsViewModel @{ @@ -12,10 +10,9 @@
Scoresheet & Worksheets Configuration
- @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.View)) + @if (Model.CanGenerateScoresheet) { } - @if (await PermissionChecker.IsGrantedAsync(AIPermissions.FormScoresheet.View)) + @if (Model.CanViewScoresheet) {
@@ -181,7 +181,7 @@
-
+
@L["ApplicationForms.Configuration:OtherLinks"].Value
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js index b6d0358e0d..a2527cd6b0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js @@ -117,6 +117,7 @@ const toggleInput = document.createElement('input'); toggleInput.type = 'checkbox'; toggleInput.className = 'form-check-input related-link-published'; + toggleInput.setAttribute('aria-label', l('ApplicationForms.Configuration:ShowOtherLinksInPortal')); toggleInput.style.cursor = 'pointer'; toggleInput.checked = data.published; switchWrapper.appendChild(toggleInput); @@ -127,6 +128,7 @@ const removeButton = document.createElement('button'); removeButton.type = 'button'; removeButton.className = 'btn btn-sm btn-outline-danger btn-remove-related-link'; + removeButton.setAttribute('aria-label', 'Remove Link'); const removeIcon = document.createElement('i'); removeIcon.className = 'fl fl-trash'; removeButton.appendChild(removeIcon); @@ -138,16 +140,26 @@ row.appendChild(toggleCol); row.appendChild(removeCol); - removeButton.addEventListener('click', function () { - row.remove(); - updateAddButtonState(); - saveButton.disabled = false; - cancelButton.disabled = false; - }); - return row; } + relatedLinksContainer.addEventListener('click', function (event) { + const removeButton = event.target.closest('.btn-remove-related-link'); + if (!removeButton || !relatedLinksContainer.contains(removeButton)) { + return; + } + + const row = removeButton.closest('.related-link-row'); + if (!row) { + return; + } + + row.remove(); + updateAddButtonState(); + saveButton.disabled = false; + cancelButton.disabled = false; + }); + function rebuildRelatedLinkRows(links) { relatedLinksContainer.innerHTML = ''; links.forEach(function (link) { @@ -379,7 +391,7 @@ }) .catch(function () { // Keep the form dirty so the user can retry after a partial failure. - abp.notify.error('Failed to save other configuration.'); + abp.notify.error('Failed to save configuration.'); saveButton.disabled = false; cancelButton.disabled = false; }) diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs index effd117a15..6bf57cd2ed 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs @@ -117,13 +117,24 @@ private static ApplicationStatus CreateStatus(Guid id, string externalStatus, st return entity; } - private static ApplicationForm CreateForm(Guid id, string formName) + private static ApplicationForm CreateForm(Guid id, string formName, Action? configure = null) { var entity = new ApplicationForm { ApplicationFormName = formName }; EntityHelper.TrySetId(entity, () => id); + configure?.Invoke(entity); return entity; } + private static ExternalLink CreateExternalLink( + ExternalLinkType type, bool published, int order = -1, string uri = "https://example.com") + => new() + { + Uri = uri, + ExternalLinkType = type, + Published = published, + Order = order + }; + [Fact] public async Task GetDataAsync_ShouldChangeTenant() { @@ -504,5 +515,214 @@ public async Task GetDataAsync_ShouldFallBackToCreationTimeWhenSubmissionIsNullO var dto = result.ShouldBeOfType(); dto.Submissions[0].SubmissionTime.ShouldBe(creationTime); } + + [Fact] + public async Task GetDataAsync_ShouldReturnRenewalLink_WhenEligibleAndPublished() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => + { + a.ApplicationFormId = formId; + a.EligibleForRenewal = true; + })], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com")])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + dto.Submissions[0].RenewalLink.ShouldNotBeNull(); + dto.Submissions[0].RenewalLink!.Uri.ShouldBe("https://renewal.example.com"); + } + + [Fact] + public async Task GetDataAsync_ShouldNotReturnRenewalLink_WhenNotEligibleForRenewal() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => + { + a.ApplicationFormId = formId; + a.EligibleForRenewal = false; + })], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [CreateExternalLink(ExternalLinkType.Renewal, published: true)])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + dto.Submissions[0].RenewalLink.ShouldBeNull(); + } + + [Fact] + public async Task GetDataAsync_ShouldNotReturnRenewalLink_WhenUnpublished() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => + { + a.ApplicationFormId = formId; + a.EligibleForRenewal = true; + })], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [CreateExternalLink(ExternalLinkType.Renewal, published: false)])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + dto.Submissions[0].RenewalLink.ShouldBeNull(); + } + + [Fact] + public async Task GetDataAsync_ShouldExcludeUnpublishedRelatedLinks() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://published.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: false, order: 2, uri: "https://unpublished.example.com") + ])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + dto.Submissions[0].RelatedLinks.Count.ShouldBe(1); + dto.Submissions[0].RelatedLinks[0].Uri.ShouldBe("https://published.example.com"); + } + + [Fact] + public async Task GetDataAsync_ShouldReturnRelatedLinksInConfiguredOrder() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: 2, uri: "https://second.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://first.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://middle.example.com") + ])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + var relatedLinks = dto.Submissions[0].RelatedLinks; + relatedLinks.Count.ShouldBe(3); + relatedLinks[0].Uri.ShouldBe("https://first.example.com"); + relatedLinks[1].Uri.ShouldBe("https://middle.example.com"); + relatedLinks[2].Uri.ShouldBe("https://second.example.com"); + } + + [Fact] + public async Task GetDataAsync_ShouldOrderUnorderedRelatedLinksLast() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: -1, uri: "https://unordered.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://ordered.example.com") + ])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + var relatedLinks = dto.Submissions[0].RelatedLinks; + relatedLinks.Count.ShouldBe(2); + relatedLinks[0].Uri.ShouldBe("https://ordered.example.com"); + relatedLinks[1].Uri.ShouldBe("https://unordered.example.com"); + } + + [Fact] + public async Task GetDataAsync_ShouldNotMixRenewalAndRelatedLinks() + { + // Arrange + var request = CreateRequest(); + var applicationId = Guid.NewGuid(); + var formId = Guid.NewGuid(); + var statusId = Guid.NewGuid(); + + SetupQueryables( + [CreateSubmission(applicationId, "TESTUSER")], + [CreateApplication(applicationId, statusId, a => + { + a.ApplicationFormId = formId; + a.EligibleForRenewal = true; + })], + [CreateForm(formId, "Form", f => f.ExternalLinks = + [ + CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, uri: "https://related.example.com") + ])], + [CreateStatus(statusId, "Submitted")]); + + // Act + var result = await _provider.GetDataAsync(request); + + // Assert + var dto = result.ShouldBeOfType(); + dto.Submissions[0].RenewalLink.ShouldNotBeNull(); + dto.Submissions[0].RenewalLink!.Uri.ShouldBe("https://renewal.example.com"); + dto.Submissions[0].RelatedLinks.Count.ShouldBe(1); + dto.Submissions[0].RelatedLinks[0].Uri.ShouldBe("https://related.example.com"); + } } } From c02afd3a94ec95ed68b6b3e4ec850b1276153854 Mon Sep 17 00:00:00 2001 From: Patrick <135162612+plavoie-BC@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:12:30 -0700 Subject: [PATCH 116/121] [AB#33234] Application Forms UI Small Fixes --- .../Components/ApplicationFormConfigWidget/Default.cshtml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml index 695380f102..2b72ebd1d8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.cshtml @@ -178,7 +178,7 @@
- + NOTE: @L["ApplicationForms.Configuration.Notes:LinkVisibilityRequiresUrl"].Value
@@ -249,8 +249,7 @@ }
- NOTE: @L["ApplicationForms.Configuration.Notes:LinkVisibilityRequiresUrl"].Value - NOTE: @L["ApplicationForms.Configuration.Notes:MaxRelatedLinks"].Value + NOTE: @L["ApplicationForms.Configuration.Notes:MaxRelatedLinks"].Value From 2f8eea00a0dae3b777c9bc2a6fb5712956939d7a Mon Sep 17 00:00:00 2001 From: Stephan McColm Date: Tue, 18 Aug 2026 13:26:31 -0700 Subject: [PATCH 117/121] feature/AB#33297_Modify_Cypress_Launcher: Improved support for local Cypress environments and cross-environment test configuration --- .gitignore | 7 +++- .../Unity.AutoUI/CypressTestLauncher.bat | 3 +- .../Unity.AutoUI/cypress/e2e/login.cy.ts | 4 +- .../Unity.AutoUI/cypress/e2e/navigation.cy.ts | 3 +- .../cypress/fixtures/metabase.json | 2 +- .../cypress/scripts/chefs-api-config.json | 42 ++++++++++++------- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 8cae5022ff..587bfe8b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,9 @@ appsettings.json /applications/Orchestrator *.env -/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package-lock.json \ No newline at end of file +/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package-lock.json +/applications/Unity.AutoUI/cypress/config/dev.json +/applications/Unity.AutoUI/cypress/config/dev2.json +/applications/Unity.AutoUI/cypress/config/test.json +/applications/Unity.AutoUI/cypress/config/uat.json +/applications/Unity.AutoUI/cypress/config/prod.json diff --git a/applications/Unity.AutoUI/CypressTestLauncher.bat b/applications/Unity.AutoUI/CypressTestLauncher.bat index a88219b77b..035acfc7ef 100644 --- a/applications/Unity.AutoUI/CypressTestLauncher.bat +++ b/applications/Unity.AutoUI/CypressTestLauncher.bat @@ -1,5 +1,6 @@ @echo off +@echo off setlocal cd /d "%~dp0" -powershell -NoProfile -ExecutionPolicy Bypass -NoExit -Command "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$projectPath=(Get-Location).Path;$form=New-Object System.Windows.Forms.Form;$form.Text='Cypress Test Launcher';$form.Size=New-Object System.Drawing.Size(420,240);$form.StartPosition='CenterScreen';$form.AutoScaleMode=[System.Windows.Forms.AutoScaleMode]::None;$envLabel=New-Object System.Windows.Forms.Label;$envLabel.Text='Select Environment:';$envLabel.AutoSize=$true;$envLabel.Location=New-Object System.Drawing.Point(15,15);$envBox=New-Object System.Windows.Forms.ComboBox;$envBox.Location=New-Object System.Drawing.Point(15,35);$envBox.Size=New-Object System.Drawing.Size(380,25);$envBox.DropDownStyle='DropDownList';$envBox.Items.Add('Please select an environment');$envBox.Items.Add('DEV');$envBox.Items.Add('DEV2');$envBox.Items.Add('TEST');$envBox.Items.Add('UAT');$envBox.Items.Add('PROD');$envBox.SelectedIndex=0;$modeLabel=New-Object System.Windows.Forms.Label;$modeLabel.Text='Select Mode:';$modeLabel.AutoSize=$true;$modeLabel.Location=New-Object System.Drawing.Point(15,70);$modeBox=New-Object System.Windows.Forms.ComboBox;$modeBox.Location=New-Object System.Drawing.Point(15,90);$modeBox.Size=New-Object System.Drawing.Size(380,25);$modeBox.DropDownStyle='DropDownList';$modeBox.Items.Add('Please select a mode');$modeBox.Items.Add('GUI');$modeBox.Items.Add('Headless');$modeBox.SelectedIndex=0;$run=New-Object System.Windows.Forms.Button;$run.Text='Launch Cypress';$run.Location=New-Object System.Drawing.Point(15,135);$run.Size=New-Object System.Drawing.Size(380,32);$run.Add_Click({try{if($envBox.SelectedIndex -eq 0 -or $modeBox.SelectedIndex -eq 0){[System.Windows.Forms.MessageBox]::Show('Please select both an environment and a mode.','Missing Selection');return};Set-Location $projectPath;$envName=$envBox.SelectedItem;$envFile='.\\cypress.'+$envName+'.env.json';if(Test-Path $envFile){Copy-Item $envFile '.\\cypress.env.json' -Force}else{[System.Windows.Forms.MessageBox]::Show('Environment file not found: '+$envFile,'Missing Env File');return};if($modeBox.SelectedItem -eq 'Headless'){Start-Process powershell -ArgumentList '-NoExit','-Command',\"cd '$projectPath'; npx cypress run\"}else{Start-Process powershell -ArgumentList '-NoExit','-Command',\"cd '$projectPath'; npx cypress open\"}}catch{[System.Windows.Forms.MessageBox]::Show($_.Exception.Message,'Cypress Launcher Error')}});$form.Controls.Add($envLabel);$form.Controls.Add($envBox);$form.Controls.Add($modeLabel);$form.Controls.Add($modeBox);$form.Controls.Add($run);$form.ShowDialog()" +powershell -NoProfile -ExecutionPolicy Bypass -NoExit -Command "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$projectPath=(Get-Location).Path;$form=New-Object System.Windows.Forms.Form;$form.Text='Cypress Test Launcher';$form.Size=New-Object System.Drawing.Size(420,240);$form.StartPosition='CenterScreen';$form.AutoScaleMode=[System.Windows.Forms.AutoScaleMode]::None;$envLabel=New-Object System.Windows.Forms.Label;$envLabel.Text='Select Environment:';$envLabel.AutoSize=$true;$envLabel.Location=New-Object System.Drawing.Point(15,15);$envBox=New-Object System.Windows.Forms.ComboBox;$envBox.Location=New-Object System.Drawing.Point(15,35);$envBox.Size=New-Object System.Drawing.Size(380,25);$envBox.DropDownStyle='DropDownList';$envBox.Items.Add('Please select an environment');$envBox.Items.Add('DEV');$envBox.Items.Add('DEV2');$envBox.Items.Add('TEST');$envBox.Items.Add('UAT');$envBox.Items.Add('PROD');$envBox.SelectedIndex=0;$modeLabel=New-Object System.Windows.Forms.Label;$modeLabel.Text='Select Mode:';$modeLabel.AutoSize=$true;$modeLabel.Location=New-Object System.Drawing.Point(15,70);$modeBox=New-Object System.Windows.Forms.ComboBox;$modeBox.Location=New-Object System.Drawing.Point(15,90);$modeBox.Size=New-Object System.Drawing.Size(380,25);$modeBox.DropDownStyle='DropDownList';$modeBox.Items.Add('Please select a mode');$modeBox.Items.Add('GUI');$modeBox.Items.Add('Headless');$modeBox.SelectedIndex=0;$run=New-Object System.Windows.Forms.Button;$run.Text='Launch Cypress';$run.Location=New-Object System.Drawing.Point(15,135);$run.Size=New-Object System.Drawing.Size(380,32);$run.Add_Click({try{if($envBox.SelectedIndex -eq 0 -or $modeBox.SelectedIndex -eq 0){[System.Windows.Forms.MessageBox]::Show('Please select both an environment and a mode.','Missing Selection');return};Set-Location $projectPath;$envName=$envBox.SelectedItem.ToString().ToLowerInvariant();$envFile=Join-Path $projectPath ('cypress\config\'+$envName+'.json');if(-not (Test-Path $envFile)){[System.Windows.Forms.MessageBox]::Show('Environment file not found: '+$envFile,'Missing Env File');return};if($modeBox.SelectedItem -eq 'Headless'){Start-Process powershell -ArgumentList '-NoExit','-ExecutionPolicy','Bypass','-Command',\"`$env:UNITY_CYPRESS_ENV='$envName'; cd '$projectPath'; node .\\scripts\\run-cypress.js run --browser chrome\"}else{Start-Process powershell -ArgumentList '-NoExit','-ExecutionPolicy','Bypass','-Command',\"`$env:UNITY_CYPRESS_ENV='$envName'; cd '$projectPath'; node .\\scripts\\run-cypress.js open --browser chrome\"}}catch{[System.Windows.Forms.MessageBox]::Show($_.Exception.Message,'Cypress Launcher Error')}});$form.Controls.Add($envLabel);$form.Controls.Add($envBox);$form.Controls.Add($modeLabel);$form.Controls.Add($modeBox);$form.Controls.Add($run);$form.ShowDialog()" diff --git a/applications/Unity.AutoUI/cypress/e2e/login.cy.ts b/applications/Unity.AutoUI/cypress/e2e/login.cy.ts index 3735041179..b88cb0bbf3 100644 --- a/applications/Unity.AutoUI/cypress/e2e/login.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/login.cy.ts @@ -1,3 +1,4 @@ +/// import { LoginPageInstance, NavigationPageInstance } from "../utilities"; describe('Grant Manager Login and Logout', () => { @@ -8,10 +9,9 @@ describe('Grant Manager Login and Logout', () => { loginPage.login() loginPage.verifyOnGrantApplications() - // Verify Default Grant Program tenant is selected + navPage.switchToDefaultGrantsProgramIfAvailable() navPage.verifyCurrentTenant('Default Grants Program') - // Logout (terminal action) loginPage.quickLogout() }) }) diff --git a/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts b/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts index 6bcf9a65df..5549ae3f16 100644 --- a/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts +++ b/applications/Unity.AutoUI/cypress/e2e/navigation.cy.ts @@ -11,10 +11,9 @@ describe('Grant Manager Login and Top Navigation', () => { it('Verify navigation options in the top banner', () => { - // 3.) Verify Default Grant Program tenant is selected. + navPage.switchToDefaultGrantsProgramIfAvailable() navPage.verifyCurrentTenant('Default Grants Program') - // 4.) Ensure all expected headings are present. navPage.verifyAllNavItemsExist() // 5.) Applications diff --git a/applications/Unity.AutoUI/cypress/fixtures/metabase.json b/applications/Unity.AutoUI/cypress/fixtures/metabase.json index 051308782f..1ba2314590 100644 --- a/applications/Unity.AutoUI/cypress/fixtures/metabase.json +++ b/applications/Unity.AutoUI/cypress/fixtures/metabase.json @@ -18,7 +18,7 @@ }, { "unityEnv": "PROD", - "baseURL": "https://unity-reporting.apps.gold.devops.gov.bc.ca/" + "baseURL": "https://unity-reporting.apps.silver.devops.gov.bc.ca/" } ] } \ No newline at end of file diff --git a/applications/Unity.AutoUI/cypress/scripts/chefs-api-config.json b/applications/Unity.AutoUI/cypress/scripts/chefs-api-config.json index 0bf9000101..50d724e8d8 100644 --- a/applications/Unity.AutoUI/cypress/scripts/chefs-api-config.json +++ b/applications/Unity.AutoUI/cypress/scripts/chefs-api-config.json @@ -1,21 +1,31 @@ { - "environments": { - "test": { - "baseURL": "https://chefs-test.apps.silver.devops.gov.bc.ca", - "formId": "46e25863-0ead-4aa8-897f-51e45f79e137", - "versionId": "4ef52ead-2cc3-4bdb-a7b7-73be983a7838" - }, - "dev": { - "baseURL": "https://chefs-dev.apps.silver.devops.gov.bc.ca", - "formId": "233f47f9-b566-46c3-926a-73d565bf710f", - "versionId": "1e209d6b-46f5-4ddb-bc79-6e04033231cb" - }, - "uat": { - "baseURL": "https://chefs-test.apps.silver.devops.gov.bc.ca", - "formId": "f2f45aa7-62c5-49ca-8846-b214e02adb46", - "versionId": "1d4d73ec-00e7-4b57-98c9-49d1e0c7d15b" - } + "environments": { + "dev": { + "baseURL": "https://chefs-dev.apps.silver.devops.gov.bc.ca", + "formId": "233f47f9-b566-46c3-926a-73d565bf710f", + "versionId": "1e209d6b-46f5-4ddb-bc79-6e04033231cb" }, + "dev2": { + "baseURL": "https://chefs-dev.apps.silver.devops.gov.bc.ca", + "formId": "92c0df4c-34f9-4a9a-a1b4-61c12495749c", + "versionId": "8218c285-7fa3-42df-b445-7e8ae835fad0" + }, + "test": { + "baseURL": "https://chefs-test.apps.silver.devops.gov.bc.ca", + "formId": "46e25863-0ead-4aa8-897f-51e45f79e137", + "versionId": "4ef52ead-2cc3-4bdb-a7b7-73be983a7838" + }, + "uat": { + "baseURL": "https://chefs-test.apps.silver.devops.gov.bc.ca", + "formId": "f2f45aa7-62c5-49ca-8846-b214e02adb46", + "versionId": "1d4d73ec-00e7-4b57-98c9-49d1e0c7d15b" + }, + "prod": { + "baseURL": "https://submit.digital.gov.bc.ca", + "formId": "4defd3bf-f57c-4969-94a1-ced745a99ccf", + "versionId": "d8736cde-a11a-41be-8800-084f2dfcb825" + } + }, "headers": { "Accept": "application/json", "Content-Type": "application/json" From 0b62d54bedb186e0abf91ac7434b4f1c842be312 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 18 Aug 2026 13:39:47 -0700 Subject: [PATCH 118/121] AB#33935 update links structure for Applicant Portal --- .../ProfileData/SubmissionInfoItemDto.cs | 2 + .../ApplicationForms/ApplicationFormDto.cs | 1 + .../ExternalLinksConfigDto.cs | 3 + .../SubmissionInfoDataProvider.cs | 20 +++--- .../ApplicationFormAppService.cs | 17 ++++- .../GrantManagerApplicationMapperlyProfile.cs | 8 ++- .../ApplicantProfile/ExternalLink.cs | 4 +- .../ApplicantProfile/ExternalLinksConfig.cs | 18 +++++ .../Applications/ApplicationForm.cs | 10 ++- .../GrantTenantDbContext.cs | 6 +- .../ApplicationFormConfigWidget.cs | 2 +- .../ApplicationFormConfigWidget/Default.js | 4 +- .../SubmissionInfoDataProviderTests.cs | 72 ++++++++++++------- .../ExternalLinksConfigValidationTests.cs | 12 ++-- .../ApplicationForms/ApplicationFormTests.cs | 19 ++--- 15 files changed, 138 insertions(+), 60 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs 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 2334c37133..9bce8b13f2 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 @@ -14,4 +14,6 @@ public class SubmissionInfoItemDto public string Status { get; set; } = string.Empty; public ExternalLinkDto? RenewalLink { get; set; } public List RelatedLinks { get; set; } = []; + public bool EligibleForRenewal { get; set; } + public string ApplicantMessage { get; set; } = string.Empty; } 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 20a7bcf93a..07f08b9859 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 @@ -34,5 +34,6 @@ public class ApplicationFormDto : EntityDto public SuffixConfigType? SuffixType { get; set; } public int? DefaultPaymentGroup { get; set; } public List ExternalLinks { get; set; } = []; + public string ApplicantMessage { get; set; } = string.Empty; } } 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 index fb5f205f61..0429e526fd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/ApplicationForms/ExternalLinksConfigDto.cs @@ -12,6 +12,9 @@ public class ExternalLinksConfigDto : IValidatableObject public List RelatedLinks { get; set; } = []; + [MaxLength(512)] + public string ApplicantMessage { get; set; } = string.Empty; + public IEnumerable Validate(ValidationContext validationContext) { if (RenewalLink is { Published: true } && !ExternalLinkUriValidator.IsValidHttpUri(RenewalLink.Uri)) 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 11b953c8e6..4c5fe02c7b 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 @@ -68,23 +68,25 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id application.ReferenceNo, application.EligibleForRenewal, FormName = form.ApplicationFormName ?? string.Empty, - form.ExternalLinks, + form.ExternalLinksConfig, Status = application.ExternalStatusVisibility ? status.NotifiedStatus ?? status.ExternalStatus : status.ExternalStatus, }).ToListAsync(); - // ExternalLinks is a JSON-mapped complex collection; filtering it as part of the join - // requires an APPLY operation that the SQLite test provider does not support, so the - // form's external links are resolved in-memory below instead of within the query. + // ExternalLinksConfig is a JSON-mapped complex property; filtering its nested links as + // part of the join requires an APPLY operation that the SQLite test provider does not + // support, so the form's external links are resolved in-memory below instead of within + // the query. dto.Submissions.AddRange(results.Select(s => { - var renewalLink = s.EligibleForRenewal ? s.ExternalLinks + var renewalLinkEntity = s.EligibleForRenewal ? s.ExternalLinksConfig.Links .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Renewal) - .Select(ToExternalLinkDto) .FirstOrDefault() : null; - var relatedLinks = s.ExternalLinks + var renewalLink = renewalLinkEntity is null ? null : ToExternalLinkDto(renewalLinkEntity); + + var relatedLinks = s.ExternalLinksConfig.Links .Where(x => x.Published && x.ExternalLinkType == ExternalLinkType.Related) .OrderBy(x => x.Order == -1 ? int.MaxValue : x.Order) // Links without an order default to -1 .Select(ToExternalLinkDto) @@ -100,7 +102,9 @@ join status in statusesQuery on application.ApplicationStatusId equals status.Id Type = s.FormName, Status = s.Status, RenewalLink = renewalLink, - RelatedLinks = relatedLinks + RelatedLinks = relatedLinks, + EligibleForRenewal = s.EligibleForRenewal, + ApplicantMessage = renewalLinkEntity is null ? string.Empty : s.ExternalLinksConfig.ApplicantMessage }; })); } 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 a7ee7409ed..0081f20ae6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs @@ -126,6 +126,11 @@ public override async Task GetAsync(Guid id) var dto = await base.GetAsync(id); dto.ApiKey = _stringEncryptionService.Decrypt(dto.ApiKey); dto.ApiToken = _stringEncryptionService.Decrypt(dto.ApiToken); + + var form = await Repository.GetAsync(id); + dto.ExternalLinks = form.ExternalLinksConfig.Links.Select(MapToExternalLinkConfigDto).ToList(); + dto.ApplicantMessage = form.ExternalLinksConfig.ApplicantMessage; + return dto; } @@ -198,7 +203,7 @@ public async Task PatchExternalLinksConfigAsync(Guid id, ExternalLinksConfigDto var renewalLink = config.RenewalLink is null ? null : MapToExternalLink(config.RenewalLink); var relatedLinks = (config.RelatedLinks ?? []).Select(MapToExternalLink).ToList(); - form.SetExternalLinks(renewalLink, relatedLinks); + form.SetExternalLinks(renewalLink, relatedLinks, config.ApplicantMessage); await Repository.UpdateAsync(form); } @@ -213,6 +218,16 @@ public async Task PatchExternalLinksConfigAsync(Guid id, ExternalLinksConfigDto Order = dto.Order }; + private static ExternalLinkConfigDto MapToExternalLinkConfigDto(ExternalLink link) => new() + { + Uri = link.Uri, + Title = link.Title, + Description = link.Description, + Published = link.Published, + ExternalLinkType = link.ExternalLinkType, + Order = link.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 50aa3dc156..2da0edb47e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationMapperlyProfile.cs @@ -345,11 +345,15 @@ public partial class ApplicationFormToDtoMapper : 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 index 1f8f7c36fa..e3b086b63e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Unity.GrantManager.ApplicantProfile; @@ -21,4 +21,4 @@ public class ExternalLink 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/ExternalLinksConfig.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs new file mode 100644 index 0000000000..8a28c705fc --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Unity.GrantManager.ApplicantProfile; + +/// +/// Represents the Applicant Portal external links configuration for an application form, +/// including the message shown to applicants alongside the renewal link. +/// +[ComplexType] +public class ExternalLinksConfig +{ + [MaxLength(512)] + public string ApplicantMessage { get; set; } = string.Empty; + + public List Links { 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 120334d604..b5d6139c57 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationForm.cs @@ -34,7 +34,7 @@ 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 ExternalLinksConfig ExternalLinksConfig { get; set; } = new(); public bool AutomaticallyGenerateAIAnalysis { get; set; } = false; public bool ManuallyInitiateAIAnalysis { get; set; } = false; @@ -90,7 +90,7 @@ public static AddressType GetDefaultElectoralDistrictAddressType() /// 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) + public ApplicationForm SetExternalLinks(ExternalLink? renewalLink, List relatedLinks, string applicantMessage = "") { ArgumentNullException.ThrowIfNull(relatedLinks); @@ -126,7 +126,11 @@ public ApplicationForm SetExternalLinks(ExternalLink? renewalLink, List x.ApplicationFormName).IsRequired().HasMaxLength(255); - b.ComplexCollection(x => x.ExternalLinks, e => e.ToJson()); + b.ComplexProperty(x => x.ExternalLinksConfig, cb => + { + cb.ComplexCollection(c => c.Links); + cb.ToJson("ExternalLinks"); + }); b.HasOne().WithMany().HasForeignKey(x => x.IntakeId).IsRequired(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs index f8fc97f7f7..46f7d89d76 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/ApplicationFormConfigWidget.cs @@ -56,7 +56,7 @@ public async Task InvokeAsync(string? configType, Applicat RenewalLinkUri = renewalLink?.Uri ?? string.Empty, RenewalLinkTitle = renewalLink?.Title ?? string.Empty, RenewalLinkPublished = renewalLink?.Published ?? false, - ApplicantMessage = renewalLink?.Description ?? string.Empty, + ApplicantMessage = applicationForm?.ApplicantMessage ?? string.Empty, RelatedLinks = relatedLinks }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js index a2527cd6b0..e27eae4740 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationFormConfigWidget/Default.js @@ -223,7 +223,6 @@ renewalLink: renewalUriValue ? { uri: renewalUriValue, title: renewalLinkTitle.value, - description: applicantMessage.value, published: renewalLinkPublished.checked, externalLinkType: EXTERNAL_LINK_TYPE_RENEWAL, order: 0 @@ -239,7 +238,8 @@ order: index }; }) - .filter(function (link) { return link.uri; }) + .filter(function (link) { return link.uri; }), + applicantMessage: applicantMessage.value }; } diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs index 6bf57cd2ed..281e5dba69 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Applicants/SubmissionInfoDataProviderTests.cs @@ -532,8 +532,10 @@ public async Task GetDataAsync_ShouldReturnRenewalLink_WhenEligibleAndPublished( a.ApplicationFormId = formId; a.EligibleForRenewal = true; })], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com")])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = [CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com")] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -561,8 +563,10 @@ public async Task GetDataAsync_ShouldNotReturnRenewalLink_WhenNotEligibleForRene a.ApplicationFormId = formId; a.EligibleForRenewal = false; })], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [CreateExternalLink(ExternalLinkType.Renewal, published: true)])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = [CreateExternalLink(ExternalLinkType.Renewal, published: true)] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -589,8 +593,10 @@ public async Task GetDataAsync_ShouldNotReturnRenewalLink_WhenUnpublished() a.ApplicationFormId = formId; a.EligibleForRenewal = true; })], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [CreateExternalLink(ExternalLinkType.Renewal, published: false)])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = [CreateExternalLink(ExternalLinkType.Renewal, published: false)] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -613,11 +619,14 @@ public async Task GetDataAsync_ShouldExcludeUnpublishedRelatedLinks() SetupQueryables( [CreateSubmission(applicationId, "TESTUSER")], [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [ - CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://published.example.com"), - CreateExternalLink(ExternalLinkType.Related, published: false, order: 2, uri: "https://unpublished.example.com") - ])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://published.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: false, order: 2, uri: "https://unpublished.example.com") + ] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -641,12 +650,15 @@ public async Task GetDataAsync_ShouldReturnRelatedLinksInConfiguredOrder() SetupQueryables( [CreateSubmission(applicationId, "TESTUSER")], [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [ - CreateExternalLink(ExternalLinkType.Related, published: true, order: 2, uri: "https://second.example.com"), - CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://first.example.com"), - CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://middle.example.com") - ])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: 2, uri: "https://second.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://first.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 1, uri: "https://middle.example.com") + ] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -673,11 +685,14 @@ public async Task GetDataAsync_ShouldOrderUnorderedRelatedLinksLast() SetupQueryables( [CreateSubmission(applicationId, "TESTUSER")], [CreateApplication(applicationId, statusId, a => a.ApplicationFormId = formId)], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [ - CreateExternalLink(ExternalLinkType.Related, published: true, order: -1, uri: "https://unordered.example.com"), - CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://ordered.example.com") - ])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = + [ + CreateExternalLink(ExternalLinkType.Related, published: true, order: -1, uri: "https://unordered.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, order: 0, uri: "https://ordered.example.com") + ] + })], [CreateStatus(statusId, "Submitted")]); // Act @@ -707,11 +722,14 @@ public async Task GetDataAsync_ShouldNotMixRenewalAndRelatedLinks() a.ApplicationFormId = formId; a.EligibleForRenewal = true; })], - [CreateForm(formId, "Form", f => f.ExternalLinks = - [ - CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com"), - CreateExternalLink(ExternalLinkType.Related, published: true, uri: "https://related.example.com") - ])], + [CreateForm(formId, "Form", f => f.ExternalLinksConfig = new ExternalLinksConfig + { + Links = + [ + CreateExternalLink(ExternalLinkType.Renewal, published: true, uri: "https://renewal.example.com"), + CreateExternalLink(ExternalLinkType.Related, published: true, uri: "https://related.example.com") + ] + })], [CreateStatus(statusId, "Submitted")]); // Act diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/ApplicationForms/ExternalLinksConfigValidationTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/ApplicationForms/ExternalLinksConfigValidationTests.cs index b686143855..906e533265 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/ApplicationForms/ExternalLinksConfigValidationTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/ApplicationForms/ExternalLinksConfigValidationTests.cs @@ -55,19 +55,21 @@ await _applicationFormAppService.PatchExternalLinksConfigAsync( Published = false, ExternalLinkType = ExternalLinkType.Related } - ] + ], + ApplicantMessage = "Renewal message for applicants." }); var form = await _applicationFormRepository.GetAsync(GrantManagerTestData.ApplicationForm1_Id); - form.ExternalLinks.Count.ShouldBe(3); - var renewalLink = form.ExternalLinks.Single(l => l.ExternalLinkType == ExternalLinkType.Renewal); + form.ExternalLinksConfig.Links.Count.ShouldBe(3); + form.ExternalLinksConfig.ApplicantMessage.ShouldBe("Renewal message for applicants."); + var renewalLink = form.ExternalLinksConfig.Links.Single(l => l.ExternalLinkType == ExternalLinkType.Renewal); renewalLink.Uri.ShouldBe("https://chefs-test.apps.silver.devops.gov.bc.ca/app/form"); renewalLink.Title.ShouldBe("Renew Now"); renewalLink.Description.ShouldBe("Please renew before the deadline."); renewalLink.Published.ShouldBeTrue(); - var relatedLinks = form.ExternalLinks + var relatedLinks = form.ExternalLinksConfig.Links .Where(l => l.ExternalLinkType == ExternalLinkType.Related) .OrderBy(l => l.Order) .ToList(); @@ -103,7 +105,7 @@ await _applicationFormAppService.PatchExternalLinksConfigAsync( }); var form = await _applicationFormRepository.GetAsync(GrantManagerTestData.ApplicationForm1_Id); - var relatedLinks = form.ExternalLinks.Where(l => l.ExternalLinkType == ExternalLinkType.Related).ToList(); + var relatedLinks = form.ExternalLinksConfig.Links.Where(l => l.ExternalLinkType == ExternalLinkType.Related).ToList(); relatedLinks.Count.ShouldBe(2); relatedLinks.ShouldNotContain(l => l.Uri.EndsWith("first", StringComparison.Ordinal)); diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs index 7d4abc0452..228c0dd2b0 100644 --- a/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs +++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Domain.Tests/ApplicationForms/ApplicationFormTests.cs @@ -90,15 +90,18 @@ public void SetExternalLinks_ShouldAssignOrderAndTypes_WhenValid() [ new ExternalLink { Uri = "https://example.com/one", Order = 2 }, new ExternalLink { Uri = "https://example.com/two", Order = 1} - ]); + ], + "Please renew soon."); - form.ExternalLinks.Count.ShouldBe(3); - form.ExternalLinks[0].ExternalLinkType.ShouldBe(ExternalLinkType.Renewal); - form.ExternalLinks[0].Order.ShouldBe(-1); - form.ExternalLinks[1].ExternalLinkType.ShouldBe(ExternalLinkType.Related); - form.ExternalLinks[1].Order.ShouldBe(2); - form.ExternalLinks[2].ExternalLinkType.ShouldBe(ExternalLinkType.Related); - form.ExternalLinks[2].Order.ShouldBe(1); + var links = form.ExternalLinksConfig.Links; + links.Count.ShouldBe(3); + links[0].ExternalLinkType.ShouldBe(ExternalLinkType.Renewal); + links[0].Order.ShouldBe(-1); + links[1].ExternalLinkType.ShouldBe(ExternalLinkType.Related); + links[1].Order.ShouldBe(2); + links[2].ExternalLinkType.ShouldBe(ExternalLinkType.Related); + links[2].Order.ShouldBe(1); + form.ExternalLinksConfig.ApplicantMessage.ShouldBe("Please renew soon."); } } } From 770d436c875598cb176ef11632f02eac8e05652d Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 18 Aug 2026 13:53:06 -0700 Subject: [PATCH 119/121] AB#33935 add migration for application forms extra properties --- ..._ExternalLinksApplicantMessage.Designer.cs | 5381 +++++++++++++++++ ...7_AB33234_ExternalLinksApplicantMessage.cs | 55 + .../GrantTenantDbContextModelSnapshot.cs | 38 +- 3 files changed, 5460 insertions(+), 14 deletions(-) create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs new file mode 100644 index 0000000000..4c041dfe82 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs @@ -0,0 +1,5381 @@ +// +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("20260818204457_AB33234_ExternalLinksApplicantMessage")] + partial class AB33234_ExternalLinksApplicantMessage + { + /// + 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.ApplicationForms.GenerationReview", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContextId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Operation") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReviewData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Sequence") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("Operation", "ContextId", "Sequence") + .IsUnique(); + + b.ToTable("GenerationReviews", "AI"); + }); + + 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("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ApplicantAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AIAnalysis") + .HasColumnType("text"); + + b.Property("AIScoresheetAnswers") + .HasColumnType("jsonb"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantElectoralDistrict") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DefaultSiteId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("EligibleForRenewal") + .HasColumnType("boolean"); + + b.Property("ExternalStatusVisibility") + .HasColumnType("boolean"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.Property("UnityApplicationId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReferenceNo"); + + b.HasIndex("TenantId", "SubmissionDate") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AISummary") + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSubmissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AutomaticallyGenerateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultPaymentGroup") + .HasColumnType("integer"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ElectoralDistrictAddressType") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormHierarchy") + .HasColumnType("integer"); + + b.Property("IntakeId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsDirectApproval") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ManuallyInitiateAIAnalysis") + .HasColumnType("boolean"); + + b.Property("ParentFormId") + .HasColumnType("uuid"); + + b.Property("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("Prefix") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("SuffixType") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.ComplexProperty(typeof(Dictionary), "ExternalLinksConfig", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig", b1 => + { + b1.IsRequired(); + + b1.Property("ApplicantMessage") + .IsRequired() + .HasMaxLength(512); + + b1.ComplexCollection(typeof(List>), "Links", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig.Links#ExternalLink", b2 => + { + b2.IsRequired(); + + b2.Property("Description") + .IsRequired(); + + b2.Property("ExternalLinkType"); + + b2.Property("Order"); + + b2.Property("Published"); + + b2.Property("Title") + .IsRequired(); + + b2.Property("Uri") + .IsRequired(); + }); + + b1 + .ToJson("ExternalLinks") + .HasColumnType("jsonb"); + }); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.HasIndex("ParentFormId"); + + b.HasIndex("TenantId", "IsDeleted") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("NotifiedStatus") + .HasColumnType("text"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.HasIndex("TenantId", "ApplicationId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("AuditDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AuditNote") + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("text"); + + b.Property("AuditTrackingNumber") + .HasColumnType("text"); + + b.Property("AuditorName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("AuditHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FundingNotes") + .HasColumnType("text"); + + b.Property("FundingYear") + .HasColumnType("text"); + + b.Property("GrantCategory") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OneTimeConsideration") + .HasColumnType("numeric"); + + b.Property("PaidDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ReconsiderationAmount") + .HasColumnType("numeric"); + + b.Property("RenewedFunding") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalGrantAmount") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("FundingHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IssueDescription") + .HasColumnType("text"); + + b.Property("IssueHeading") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ResolutionNote") + .HasColumnType("text"); + + b.Property("Resolved") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Year") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("IssueTrackings", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalYear") + .HasColumnType("text"); + + b.Property("IncompleteReport") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("Outstanding") + .HasColumnType("boolean"); + + b.Property("ReportDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SignedOff") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.ToTable("ReportsHistories", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ApprovalRecommended") + .HasColumnType("boolean"); + + b.Property("AssessorId") + .HasColumnType("uuid"); + + b.Property("CleanGrowth") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicImpact") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinancialAnalysis") + .HasColumnType("integer"); + + b.Property("InclusiveGrowth") + .HasColumnType("integer"); + + b.Property("IsAiAssessment") + .HasColumnType("boolean"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssessorId"); + + b.ToTable("Assessments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicantComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CommenterId"); + + b.ToTable("ApplicationComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommenterId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PinDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.Contact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HomePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MobilePhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WorkPhoneExtension") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WorkPhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Contacts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.HasIndex("ContactId", "RelatedEntityType", "RelatedEntityId"); + + b.ToTable("ContactLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.HasIndex("TenantId"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationStatus") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ApplicationStatusId") + .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("DateField") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EmailTemplateId") + .HasColumnType("uuid"); + + b.Property("EventType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Module") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerDetail") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("ScheduledNotifications", "Notifications"); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("DateField") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("ScheduledNotificationId"); + + b.HasIndex("ApplicationId", "ScheduledNotificationId", "DateField") + .IsUnique(); + + b.ToTable("ScheduledNotificationTracking", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesHttpStatusCode") + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EmailType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestIds") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("Recipient") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("ScheduledNotificationId") + .HasColumnType("uuid"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EmailLogId") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OriginTemplateId") + .HasColumnType("uuid"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EmailLogId"); + + b.HasIndex("S3ObjectKey"); + + b.HasIndex("TemplateId"); + + b.ToTable("EmailLogAttachments", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RecipientCategory") + .HasColumnType("text"); + + b.Property("RecipientIdentifier") + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .HasMaxLength(35) + .HasColumnType("character varying(35)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CancelledBy") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("CancelledBy"); + + b.Property("CancelledById") + .HasColumnType("uuid") + .HasColumnName("CancelledById"); + + b.Property("CancelledOn") + .HasColumnType("timestamp without time zone") + .HasColumnName("CancelledOn"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FsbApNotified") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("FsbNotificationEmailLogId") + .HasColumnType("uuid"); + + b.Property("FsbNotificationSentDate") + .HasColumnType("timestamp without time zone"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvoiceStatus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsRecon") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PayeeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentDate") + .HasColumnType("text"); + + b.Property("PaymentNumber") + .HasColumnType("text"); + + b.Property("PaymentStatus") + .HasColumnType("text"); + + b.Property("ReferenceNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequesterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SubmissionConfirmationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupplierName") + .HasColumnType("text"); + + b.Property("SupplierNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AccountCodingId"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("CreationTime"); + + b.HasIndex("FsbNotificationEmailLogId"); + + b.HasIndex("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.HasIndex("Status"); + + b.HasIndex("TenantId", "CreationTime") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.Reporting.Domain.Configuration.ReportColumnsMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Mapping") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RoleStatus") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ViewStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ReportColumnsMaps", "Reporting"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Instances") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", null) + .WithMany("Answers") + .HasForeignKey("ScoresheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.ScoresheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Scoresheets.Scoresheet", "Scoresheet") + .WithMany("Sections") + .HasForeignKey("ScoresheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scoresheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.HasOne("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", null) + .WithMany("Values") + .HasForeignKey("WorksheetInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Links") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.WorksheetSection", "Section") + .WithMany("Fields") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.HasOne("Unity.Flex.Domain.Worksheets.Worksheet", "Worksheet") + .WithMany("Sections") + .HasForeignKey("WorksheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Worksheet"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicantAddresses") + .HasForeignKey("ApplicationId"); + + b.Navigation("Applicant"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithOne("ApplicantAgent") + .HasForeignKey("Unity.GrantManager.Applications.ApplicantAgent", "ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", "Applicant") + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", "ApplicationForm") + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationStatus", "ApplicationStatus") + .WithMany("Applications") + .HasForeignKey("ApplicationStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Applicant"); + + b.Navigation("ApplicationForm"); + + b.Navigation("ApplicationStatus"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationAssignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", "Assignee") + .WithMany() + .HasForeignKey("AssigneeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Assignee"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.HasOne("Unity.GrantManager.Intakes.Intake", null) + .WithMany() + .HasForeignKey("IntakeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ParentFormId") + .OnDelete(DeleteBehavior.NoAction); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.HasOne("Unity.GrantManager.Applications.ApplicationForm", null) + .WithMany() + .HasForeignKey("ApplicationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany("ApplicationLinks") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("ApplicationTags") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AuditHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.FundingHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.IssueTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ReportsHistory", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId"); + }); + + modelBuilder.Entity("Unity.GrantManager.Assessments.Assessment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", "Application") + .WithMany("Assessments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("AssessorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicantComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Applicant", null) + .WithMany() + .HasForeignKey("ApplicantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.ApplicationComment", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Comments.AssessmentComment", b => + { + b.HasOne("Unity.GrantManager.Assessments.Assessment", null) + .WithMany() + .HasForeignKey("AssessmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("CommenterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Contacts.ContactLink", b => + { + b.HasOne("Unity.GrantManager.Contacts.Contact", null) + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.GrantManager.Notifications.ScheduledNotificationTracking", b => + { + b.HasOne("Unity.GrantManager.Applications.Application", null) + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.Notifications.ScheduledNotification", null) + .WithMany() + .HasForeignKey("ScheduledNotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLogAttachment", b => + { + b.HasOne("Unity.Notifications.Emails.EmailLog", null) + .WithMany() + .HasForeignKey("EmailLogId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", null) + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("ApplicationLinks"); + + b.Navigation("ApplicationTags"); + + b.Navigation("Assessments"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Navigation("Applications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Navigation("ExpenseApprovals"); + + b.Navigation("PaymentTags"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Navigation("Sites"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs new file mode 100644 index 0000000000..e945ff1990 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class AB33234_ExternalLinksApplicantMessage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Existing rows store ExternalLinks as a bare JSON array. Wrap them in the new + // { ApplicantMessage, Links } object shape so the complex-type JSON mapping can + // deserialize them. + migrationBuilder.Sql( + """ + UPDATE "ApplicationForms" + SET "ExternalLinks" = jsonb_build_object('ApplicantMessage', '', 'Links', "ExternalLinks") + WHERE jsonb_typeof("ExternalLinks") = 'array'; + """); + + migrationBuilder.AlterColumn( + name: "ExternalLinks", + table: "ApplicationForms", + type: "jsonb", + nullable: false, + defaultValue: "{\"ApplicantMessage\":\"\",\"Links\":[]}", + oldClrType: typeof(string), + oldType: "jsonb", + oldDefaultValue: "[]"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ExternalLinks", + table: "ApplicationForms", + type: "jsonb", + nullable: false, + defaultValue: "[]", + oldClrType: typeof(string), + oldType: "jsonb", + oldDefaultValue: "{\"ApplicantMessage\":\"\",\"Links\":[]}"); + + migrationBuilder.Sql( + """ + UPDATE "ApplicationForms" + SET "ExternalLinks" = COALESCE("ExternalLinks" -> 'Links', '[]'::jsonb) + WHERE jsonb_typeof("ExternalLinks") = 'object'; + """); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 844462f43d..150520c414 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; @@ -807,6 +807,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("ContextId") + .HasColumnType("uuid"); + b.Property("CreationTime") .HasColumnType("timestamp without time zone") .HasColumnName("CreationTime"); @@ -820,9 +823,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("ExtraProperties"); - b.Property("ContextId") - .HasColumnType("uuid"); - b.Property("LastModificationTime") .HasColumnType("timestamp without time zone") .HasColumnName("LastModificationTime"); @@ -857,6 +857,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("GenerationReviews", "AI"); }); + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => { b.Property("Id") @@ -1880,24 +1881,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); - b.ComplexCollection(typeof(List>), "ExternalLinks", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinks#ExternalLink", b1 => + b.ComplexProperty(typeof(Dictionary), "ExternalLinksConfig", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig", b1 => { b1.IsRequired(); - b1.Property("Description") - .IsRequired(); + b1.Property("ApplicantMessage") + .IsRequired() + .HasMaxLength(512); + + b1.ComplexCollection(typeof(List>), "Links", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig.Links#ExternalLink", b2 => + { + b2.IsRequired(); + + b2.Property("Description") + .IsRequired(); - b1.Property("ExternalLinkType"); + b2.Property("ExternalLinkType"); - b1.Property("Order"); + b2.Property("Order"); - b1.Property("Published"); + b2.Property("Published"); - b1.Property("Title") - .IsRequired(); + b2.Property("Title") + .IsRequired(); - b1.Property("Uri") - .IsRequired(); + b2.Property("Uri") + .IsRequired(); + }); b1 .ToJson("ExternalLinks") From b8edc82c1ae868bbee98afa886b235521edbf7da Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 18 Aug 2026 14:17:03 -0700 Subject: [PATCH 120/121] AB#33935 fix unit tests --- .../ApplicantProfile/ExternalLink.cs | 2 - .../ApplicantProfile/ExternalLinksConfig.cs | 2 - .../GrantTenantDbContext.cs | 28 ++++++++++--- ...ExternalLinksApplicantMessage.Designer.cs} | 41 +++---------------- ..._AB33234_ExternalLinksApplicantMessage.cs} | 3 +- .../GrantTenantDbContextModelSnapshot.cs | 39 +++--------------- 6 files changed, 35 insertions(+), 80 deletions(-) rename applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/{20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs => 20260818211028_AB33234_ExternalLinksApplicantMessage.Designer.cs} (99%) rename applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/{20260818204457_AB33234_ExternalLinksApplicantMessage.cs => 20260818211028_AB33234_ExternalLinksApplicantMessage.cs} (93%) 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 index e3b086b63e..16dfe181b4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLink.cs @@ -1,12 +1,10 @@ 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)] diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs index 8a28c705fc..7a5022a822 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/ApplicantProfile/ExternalLinksConfig.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; namespace Unity.GrantManager.ApplicantProfile; @@ -8,7 +7,6 @@ namespace Unity.GrantManager.ApplicantProfile; /// Represents the Applicant Portal external links configuration for an application form, /// including the message shown to applicants alongside the renewal link. /// -[ComplexType] public class ExternalLinksConfig { [MaxLength(512)] 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 c27f908fd8..a25e36b48c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs @@ -1,8 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System.Linq; +using System.Text.Json; using Unity.Flex.EntityFrameworkCore; +using Unity.GrantManager.ApplicantProfile; using Unity.GrantManager.ApplicationForms; using Unity.GrantManager.Applications; using Unity.GrantManager.Assessments; @@ -68,6 +71,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + // ExternalLinksConfig is mapped as a JSON-serialized scalar via HasConversion below, + // not as an owned/entity type, so exclude it (and its nested ExternalLink type) from + // convention-based entity discovery. + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Entity(b => { b.ToTable(GrantManagerConsts.TenantTablePrefix + "Persons", @@ -123,11 +132,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.ConfigureByConvention(); //auto configure for the base class props b.Property(x => x.ApplicationFormName).IsRequired().HasMaxLength(255); - b.ComplexProperty(x => x.ExternalLinksConfig, cb => - { - cb.ComplexCollection(c => c.Links); - cb.ToJson("ExternalLinks"); - }); + // Mapped as a JSON-serialized scalar (rather than EF's native JSON complex-type + // support) because that feature is relational-only and breaks under the + // EFCore.InMemory provider used by Unity.GrantManager.Web.Tests. + b.Property(x => x.ExternalLinksConfig) + .HasColumnName("ExternalLinks") + .HasColumnType("jsonb") + .IsRequired() + .HasConversion( + config => JsonSerializer.Serialize(config, (JsonSerializerOptions?)null), + json => JsonSerializer.Deserialize(json, (JsonSerializerOptions?)null)!, + new ValueComparer( + (left, right) => JsonSerializer.Serialize(left, (JsonSerializerOptions?)null) == JsonSerializer.Serialize(right, (JsonSerializerOptions?)null), + config => JsonSerializer.Serialize(config, (JsonSerializerOptions?)null).GetHashCode(), + config => JsonSerializer.Deserialize(JsonSerializer.Serialize(config, (JsonSerializerOptions?)null), (JsonSerializerOptions?)null)!)); b.HasOne().WithMany().HasForeignKey(x => x.IntakeId).IsRequired(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.Designer.cs similarity index 99% rename from applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.Designer.cs index 4c041dfe82..f321798dc0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.Designer.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.Designer.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -14,7 +13,7 @@ namespace Unity.GrantManager.Migrations.TenantMigrations { [DbContext(typeof(GrantTenantDbContext))] - [Migration("20260818204457_AB33234_ExternalLinksApplicantMessage")] + [Migration("20260818211028_AB33234_ExternalLinksApplicantMessage")] partial class AB33234_ExternalLinksApplicantMessage { /// @@ -1824,6 +1823,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ElectoralDistrictAddressType") .HasColumnType("integer"); + b.Property("ExternalLinksConfig") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("ExternalLinks"); + b.Property("ExtraProperties") .IsRequired() .HasColumnType("text") @@ -1884,39 +1888,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); - b.ComplexProperty(typeof(Dictionary), "ExternalLinksConfig", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig", b1 => - { - b1.IsRequired(); - - b1.Property("ApplicantMessage") - .IsRequired() - .HasMaxLength(512); - - b1.ComplexCollection(typeof(List>), "Links", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig.Links#ExternalLink", b2 => - { - b2.IsRequired(); - - b2.Property("Description") - .IsRequired(); - - b2.Property("ExternalLinkType"); - - b2.Property("Order"); - - b2.Property("Published"); - - b2.Property("Title") - .IsRequired(); - - b2.Property("Uri") - .IsRequired(); - }); - - b1 - .ToJson("ExternalLinks") - .HasColumnType("jsonb"); - }); - b.HasKey("Id"); b.HasIndex("IntakeId"); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs similarity index 93% rename from applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs index e945ff1990..f59301519c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818204457_AB33234_ExternalLinksApplicantMessage.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs @@ -11,8 +11,7 @@ public partial class AB33234_ExternalLinksApplicantMessage : Migration protected override void Up(MigrationBuilder migrationBuilder) { // Existing rows store ExternalLinks as a bare JSON array. Wrap them in the new - // { ApplicantMessage, Links } object shape so the complex-type JSON mapping can - // deserialize them. + // { ApplicantMessage, Links } object shape expected by the current mapping. migrationBuilder.Sql( """ UPDATE "ApplicationForms" diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs index 150520c414..1e8af23bf4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/GrantTenantDbContextModelSnapshot.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -1821,6 +1820,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ElectoralDistrictAddressType") .HasColumnType("integer"); + b.Property("ExternalLinksConfig") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("ExternalLinks"); + b.Property("ExtraProperties") .IsRequired() .HasColumnType("text") @@ -1881,39 +1885,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .HasColumnType("integer"); - b.ComplexProperty(typeof(Dictionary), "ExternalLinksConfig", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig", b1 => - { - b1.IsRequired(); - - b1.Property("ApplicantMessage") - .IsRequired() - .HasMaxLength(512); - - b1.ComplexCollection(typeof(List>), "Links", "Unity.GrantManager.Applications.ApplicationForm.ExternalLinksConfig#ExternalLinksConfig.Links#ExternalLink", b2 => - { - b2.IsRequired(); - - b2.Property("Description") - .IsRequired(); - - b2.Property("ExternalLinkType"); - - b2.Property("Order"); - - b2.Property("Published"); - - b2.Property("Title") - .IsRequired(); - - b2.Property("Uri") - .IsRequired(); - }); - - b1 - .ToJson("ExternalLinks") - .HasColumnType("jsonb"); - }); - b.HasKey("Id"); b.HasIndex("IntakeId"); From e28373d6ae0e30be7abceec55fe45bfd1576f123 Mon Sep 17 00:00:00 2001 From: Andre Goncalves Date: Tue, 18 Aug 2026 14:37:16 -0700 Subject: [PATCH 121/121] AB#33935 update migration --- ...8_AB33234_ExternalLinksApplicantMessage.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs index f59301519c..0090a9c4b3 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20260818211028_AB33234_ExternalLinksApplicantMessage.cs @@ -10,13 +10,23 @@ public partial class AB33234_ExternalLinksApplicantMessage : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - // Existing rows store ExternalLinks as a bare JSON array. Wrap them in the new - // { ApplicantMessage, Links } object shape expected by the current mapping. + // Existing rows store ExternalLinks as a bare JSON array, with the applicant message + // (if any) saved in the renewal link's (ExternalLinkType 2) Description. Wrap them in + // the new { ApplicantMessage, Links } object shape, lifting that message out so it + // isn't silently lost. migrationBuilder.Sql( """ - UPDATE "ApplicationForms" - SET "ExternalLinks" = jsonb_build_object('ApplicantMessage', '', 'Links', "ExternalLinks") - WHERE jsonb_typeof("ExternalLinks") = 'array'; + UPDATE "ApplicationForms" AS form + SET "ExternalLinks" = jsonb_build_object( + 'ApplicantMessage', + COALESCE(( + SELECT link ->> 'Description' + FROM jsonb_array_elements(form."ExternalLinks") AS link + WHERE link ->> 'ExternalLinkType' = '2' + LIMIT 1 + ), ''), + 'Links', form."ExternalLinks") + WHERE jsonb_typeof(form."ExternalLinks") = 'array'; """); migrationBuilder.AlterColumn( @@ -43,11 +53,22 @@ protected override void Down(MigrationBuilder migrationBuilder) oldType: "jsonb", oldDefaultValue: "{\"ApplicantMessage\":\"\",\"Links\":[]}"); + // Push the applicant message back into the renewal link's Description before + // dropping the wrapper, so a rollback doesn't lose a message edited after the + // forward migration ran. migrationBuilder.Sql( """ - UPDATE "ApplicationForms" - SET "ExternalLinks" = COALESCE("ExternalLinks" -> 'Links', '[]'::jsonb) - WHERE jsonb_typeof("ExternalLinks") = 'object'; + UPDATE "ApplicationForms" AS form + SET "ExternalLinks" = COALESCE(( + SELECT jsonb_agg( + CASE + WHEN link ->> 'ExternalLinkType' = '2' + THEN jsonb_set(link, '{Description}', to_jsonb(form."ExternalLinks" ->> 'ApplicantMessage')) + ELSE link + END) + FROM jsonb_array_elements(form."ExternalLinks" -> 'Links') AS link + ), '[]'::jsonb) + WHERE jsonb_typeof(form."ExternalLinks") = 'object'; """); } }