diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/Collectors/BCAddressCollector.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/Collectors/BCAddressCollector.cs index 86bb2dcbf..5d78c484a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/Collectors/BCAddressCollector.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Worksheets/Collectors/BCAddressCollector.cs @@ -4,7 +4,7 @@ using System.Text.Json; using System.Threading.Tasks; using Unity.Flex.Worksheets.Values; -using Unity.GrantManager.Integration.Geocoder; +using Unity.GrantManager.Integrations.Geocoder; namespace Unity.Flex.Worksheets.Collectors { 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 5c084550a..42afa17cc 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 @@ -1,6 +1,4 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -23,49 +21,31 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Features; using Volo.Abp.SettingManagement; +using Microsoft.AspNetCore.Http; using Volo.Abp.Users; +using Unity.GrantManager.Notifications; namespace Unity.Notifications.EmailNotifications; - [Dependency(ReplaceServices = false)] [ExposeServices(typeof(EmailNotificationService), typeof(IEmailNotificationService))] -public class EmailNotificationService : ApplicationService, IEmailNotificationService -{ - private readonly IChesClientService _chesClientService; - private readonly IConfiguration _configuration; - private readonly EmailQueueService _emailQueueService; - private readonly IEmailLogsRepository _emailLogsRepository; - private readonly IExternalUserLookupServiceProvider _externalUserLookupServiceProvider; - private readonly ISettingManager _settingManager; - private readonly IFeatureChecker _featureChecker; - private readonly IHttpContextAccessor _httpContextAccessor; - - public EmailNotificationService( +#pragma warning disable S107 // Methods should not have too many parameters +public class EmailNotificationService( + INotificationsAppService notificationAppService, IEmailLogsRepository emailLogsRepository, - IConfiguration configuration, IChesClientService chesClientService, EmailQueueService emailQueueService, IExternalUserLookupServiceProvider externalUserLookupServiceProvider, ISettingManager settingManager, IFeatureChecker featureChecker, - IHttpContextAccessor httpContextAccessor - ) - { - _emailLogsRepository = emailLogsRepository; - _configuration = configuration; - _chesClientService = chesClientService; - _emailQueueService = emailQueueService; - _externalUserLookupServiceProvider = externalUserLookupServiceProvider; - _settingManager = settingManager; - _featureChecker = featureChecker; - _httpContextAccessor = httpContextAccessor; - } + IHttpContextAccessor httpContextAccessor) : ApplicationService, IEmailNotificationService +#pragma warning restore S107 // Methods should not have too many parameters +{ public async Task DeleteEmail(Guid id) { - await _emailLogsRepository.DeleteAsync(id); - } + await emailLogsRepository.DeleteAsync(id); + } public async Task GetEmailsChesWithNoResponseCountAsync() { @@ -77,7 +57,7 @@ public async Task GetEmailsChesWithNoResponseCountAsync() (x.Status == EmailStatus.Initialized && x.CreationTime.AddMinutes(10) < dbNow); // Fetch all email logs and apply the filter using LINQ - var allEmailLogs = await _emailLogsRepository.GetListAsync(); + var allEmailLogs = await emailLogsRepository.GetListAsync(); var emailLogs = allEmailLogs.Where(filter.Compile()).ToList(); // Ensure we're returning 0 if no logs are found @@ -90,16 +70,16 @@ public async Task GetEmailsChesWithNoResponseCountAsync() { return null; } - - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC); - EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId); + + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName); + EmailLog emailLog = await emailLogsRepository.GetAsync(emailId); emailLog = UpdateMappedEmailLog(emailLog, emailObject); emailLog.ApplicationId = applicationId; emailLog.Id = emailId; emailLog.Status = status ?? EmailStatus.Initialized; // When being called here the current tenant is in context - verified by looking at the tenant id - EmailLog loggedEmail = await _emailLogsRepository.UpdateAsync(emailLog, autoSave: true); + EmailLog loggedEmail = await emailLogsRepository.UpdateAsync(emailLog, autoSave: true); return loggedEmail; } @@ -122,7 +102,7 @@ public async Task GetEmailsChesWithNoResponseCountAsync() emailLog.Status = status ?? EmailStatus.Initialized; // When being called here the current tenant is in context - verified by looking at the tenant id - EmailLog loggedEmail = await _emailLogsRepository.InsertAsync(emailLog, autoSave: true); + EmailLog loggedEmail = await emailLogsRepository.InsertAsync(emailLog, autoSave: true); return loggedEmail; } @@ -131,9 +111,7 @@ protected virtual async Task NotifyTeamsChannel(string chesEmailError) string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); string activityTitle = "CHES Email error: " + chesEmailError; string activitySubtitle = "Environment: " + envInfo; - string teamsChannel = _configuration["Notifications:TeamsNotificationsWebhook"] ?? ""; - List facts = new() { }; - await TeamsNotificationService.PostToTeamsAsync(teamsChannel, activityTitle, activitySubtitle, facts); + await notificationAppService.PostToTeamsAsync(activityTitle, activitySubtitle); } public async Task SendCommentNotification(EmailCommentDto input) @@ -141,11 +119,11 @@ public async Task SendCommentNotification(EmailCommentDto i HttpResponseMessage res = new(); try { - if (await _featureChecker.IsEnabledAsync("Unity.Notifications")) + if (await featureChecker.IsEnabledAsync("Unity.Notifications")) { var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress); var scheme = "https"; - var request = _httpContextAccessor.HttpContext?.Request; + var request = httpContextAccessor.HttpContext?.Request; if (request == null) { @@ -211,7 +189,6 @@ public async Task SendCommentNotification(EmailCommentDto i return res; } - /// /// Send Email Notfication /// @@ -238,8 +215,8 @@ public async Task SendEmailNotification(string emailTo, str } // Send the email using the CHES client service - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName, emailCC, emailBCC); - var response = await _chesClientService.SendAsync(emailObject); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName); + var response = await chesClientService.SendAsync(emailObject); // Assuming SendAsync returns a HttpResponseMessage or equivalent: return response; @@ -259,7 +236,7 @@ public async Task SendEmailNotification(string emailTo, str EmailLog emailLog = new EmailLog(); try { - emailLog = await _emailLogsRepository.GetAsync(id); + emailLog = await emailLogsRepository.GetAsync(id); } catch (EntityNotFoundException ex) { @@ -272,7 +249,7 @@ public async Task SendEmailNotification(string emailTo, str [Authorize] public virtual async Task> GetHistoryByApplicationId(Guid applicationId) { - var entityList = await _emailLogsRepository.GetByApplicationIdAsync(applicationId); + var entityList = await emailLogsRepository.GetByApplicationIdAsync(applicationId); var dtoList = ObjectMapper.Map, List>(entityList); var sentByUserIds = dtoList @@ -284,7 +261,7 @@ public virtual async Task> GetHistoryByApplicationId(Guid foreach (var userId in sentByUserIds) { - var userInfo = await _externalUserLookupServiceProvider.FindByIdAsync(userId); + var userInfo = await externalUserLookupServiceProvider.FindByIdAsync(userId); if (userInfo != null) { userDictionary[userId] = ObjectMapper.Map(userInfo); @@ -313,10 +290,18 @@ public async Task SendEmailToQueue(EmailLog emailLog) emailNotificationEvent.Id = emailLog.Id; emailNotificationEvent.TenantId = emailLog.TenantId; emailNotificationEvent.RetryAttempts = emailLog.RetryAttempts; - await _emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent); + await emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent); } - protected virtual async Task GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) + protected virtual async Task GetEmailObjectAsync( + string emailTo, + string body, + string subject, + string? emailFrom, + string? emailBodyType, + string? emailTemplateName, + string? emailCC = null, + string? emailBCC = null) { var toList = emailTo.ParseEmailList() ?? []; var ccList = emailCC.ParseEmailList(); @@ -365,7 +350,7 @@ private async Task UpdateTenantSettings(string settingKey, string valueString) { if (!valueString.IsNullOrWhiteSpace()) { - await _settingManager.SetForCurrentTenantAsync(settingKey, valueString); + await settingManager.SetForCurrentTenantAsync(settingKey, valueString); } } } \ No newline at end of file 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 b53c3c860..46157df30 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 @@ -15,7 +15,6 @@ internal class EmailNotificationHandler( IEmailNotificationService emailNotificationService, IFeatureChecker featureChecker) : ILocalEventHandler, ITransientDependency { - private const string GRANT_APPLICATION_UPDATE_SUBJECT = "Grant Application Update"; private const string FAILED_PAYMENTS_SUBJECT = "CAS Payment Failure Notification"; public async Task HandleEventAsync(EmailNotificationEvent eventData) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientOptions.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientOptions.cs index 3c8a3f3a3..1ee0f3f6a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientOptions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientOptions.cs @@ -2,8 +2,6 @@ { public class ChesClientOptions { - public string ChesUrl { get; set; } = string.Empty; - public string ChesTokenUrl { get; set; } = string.Empty; public string ChesClientId { get; set; } = string.Empty; public string ChesClientSecret { get; set; } = string.Empty; } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientService.cs index 6bfd0bc1a..1939807c1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/Ches/ChesClientService.cs @@ -1,6 +1,5 @@ using Volo.Abp; using System.Threading.Tasks; -using System.Text.Json; using Unity.Modules.Shared.Integrations; using Unity.Modules.Shared.Http; using Volo.Abp.Application.Services; @@ -8,47 +7,52 @@ using Volo.Abp.DependencyInjection; using System.Net.Http; using Volo.Abp.Caching; +using Unity.GrantManager.Integrations; namespace Unity.Notifications.Integrations.Ches { [IntegrationService] [RemoteService(false, Name = "Ches")] [ExposeServices(typeof(ChesClientService), typeof(IChesClientService))] - public class ChesClientService : ApplicationService, IChesClientService + public class ChesClientService( + IDistributedCache chesTokenCache, + IResilientHttpRequest resilientHttpRequest, + IEndpointManagementAppService endpointManagementAppService, + IHttpClientFactory httpClientFactory, + IOptions chesClientOptions + ) : ApplicationService, IChesClientService { - private readonly IHttpClientFactory _httpClientFactory; - private readonly IResilientHttpRequest _resilientRestClient; - private readonly IOptions _chesClientOptions; - private readonly IDistributedCache _chesTokenCache; - - public ChesClientService( - IDistributedCache chesTokenCache, - IResilientHttpRequest resilientHttpRequest, - IHttpClientFactory httpClientFactory, - IOptions chesClientOptions) + public async Task SendAsync(object emailRequest) { - _resilientRestClient = resilientHttpRequest; - _chesClientOptions = chesClientOptions; - _httpClientFactory = httpClientFactory; - _chesTokenCache = chesTokenCache; + string authToken = await GetAuthTokenAsync(); + string notificationsApiUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.NOTIFICATION_API_BASE); + var resource = $"{notificationsApiUrl}/email"; + + // Pass the object directly; ResilientHttpRequest will serialize it to JSON + var response = await resilientHttpRequest.HttpAsync( + HttpMethod.Post, + resource, + emailRequest, + authToken + ); + + return response; } - public async Task SendAsync(object emailRequest) + private async Task GetAuthTokenAsync() { - ClientOptions clientOptions = new ClientOptions + string notificationsAuthUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.NOTIFICATION_AUTH); + + ClientOptions clientOptions = new() { - Url = _chesClientOptions.Value.ChesTokenUrl, - ClientId = _chesClientOptions.Value.ChesClientId, - ClientSecret = _chesClientOptions.Value.ChesClientSecret, + Url = notificationsAuthUrl, + ClientId = chesClientOptions.Value.ChesClientId, + ClientSecret = chesClientOptions.Value.ChesClientSecret, ApiKey = "ChesApiKey" }; - TokenService tokenService = new(_httpClientFactory, _chesTokenCache, Logger); - var authToken = await tokenService.GetAuthTokenAsync(clientOptions); - var resource = $"{_chesClientOptions.Value.ChesUrl}/email"; - string jsonString = JsonSerializer.Serialize(emailRequest); - var response = await _resilientRestClient.HttpAsyncWithBody(HttpMethod.Post, resource, jsonString, authToken); - return response; + TokenService tokenService = new(httpClientFactory, chesTokenCache, Logger); + return await tokenService.GetAuthTokenAsync(clientOptions); } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/TeamsNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/TeamsNotificationService.cs index 5bc0e6caa..f4d319906 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/TeamsNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/TeamsNotificationService.cs @@ -13,7 +13,14 @@ namespace Unity.Notifications.TeamsNotifications public class TeamsNotificationService { public TeamsNotificationService() : base() { } - private readonly List _facts = new List(); + + public const string DIRECT_MESSAGE_KEY_PREFIX = "DIRECT_MESSAGE_"; + public const string TEAMS_ALERT = $"{DIRECT_MESSAGE_KEY_PREFIX}0"; + public const string TEAMS_NOTIFICATION = $"{DIRECT_MESSAGE_KEY_PREFIX}1"; + + public static string TeamsChannel { get; set; } = string.Empty; + + private readonly List _facts = []; public async Task PostFactsToTeamsAsync(string teamsChannel, string activityTitle, string activitySubtitle) { @@ -51,7 +58,7 @@ private static class ChefsEventTypesConsts public const string FORM_DRAFT_PUBLISHED = "eventFormDraftPublished"; } - private static string InitializeMessageCard(string activityTitle, string activitySubtitle, List facts) + public static string InitializeMessageCard(string activityTitle, string activitySubtitle, List facts) { dynamic messageCard = MessageCard.GetMessageCard(); JObject jsonObj = JsonConvert.DeserializeObject(messageCard)!; @@ -111,49 +118,38 @@ public static async Task PostChefsEventToTeamsAsync(string teamsChannel, string string activitySubtitle = "Form Name: " + formName?.ToString(); - List facts = new() - { - new Fact - { - Name = "Form Version: ", - Value = version?.ToString() ?? string.Empty - }, - new Fact - { - Name = "Published: ", - Value = published?.ToString() ?? string.Empty - }, - new Fact - { - Name = "Updated By: ", - Value = updatedBy?.ToString() ?? string.Empty - }, - new Fact - { - Name = "Updated At: ", - Value = updatedAt?.ToString() + " UTC" - }, - new Fact - { - Name = "Created By: ", - Value = createdBy?.ToString() ?? string.Empty - }, - new Fact - { - Name = "Created At: ", - Value = createdAt?.ToString() + " UTC" - }, - }; + // Fix for IDE0028: Simplify collection initialization + List facts = + [ + new Fact { Name = "Form Version: ", Value = version?.ToString() ?? string.Empty }, + new Fact { Name = "Published: ", Value = published?.ToString() ?? string.Empty }, + new Fact { Name = "Updated By: ", Value = updatedBy?.ToString() ?? string.Empty }, + new Fact { Name = "Updated At: ", Value = updatedAt?.ToString() + " UTC" }, + new Fact { Name = "Created By: ", Value = createdBy?.ToString() ?? string.Empty }, + new Fact { Name = "Created At: ", Value = createdAt?.ToString() + " UTC" } + ]; await PostToTeamsAsync(teamsChannel, activityTitle, activitySubtitle, facts); } - private static async Task PostToTeamsChannelAsync(string teamsChannel, string messageCard) { - using var httpClient = new HttpClient(); - using var request = new HttpRequestMessage(new HttpMethod("POST"), teamsChannel); + private static readonly HttpClient httpClient = new(); + + /// + /// Posts a message card to the specified Microsoft Teams channel using an HTTP POST request. + /// + /// The webhook URL of the Teams channel. + /// The message card payload in JSON format. + public static async Task PostToTeamsChannelAsync(string teamsChannel, string messageCard) + { + using var request = new HttpRequestMessage(HttpMethod.Post, teamsChannel); request.Content = new StringContent(messageCard); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); - await httpClient.SendAsync(request); + var response = await httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + // Optionally log or throw an exception here + throw new HttpRequestException($"Failed to post to Teams channel. Status code: {response.StatusCode}"); + } } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs index 21c2162ce..7c9d8ef03 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/NotificationsDataSeedContributor.cs @@ -1,118 +1,111 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Unity.Notifications.EmailGroups; -using Unity.Notifications.Templates; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; - - -namespace Unity.Notifications; - -public class NotificationsDataSeedContributor : IDataSeedContributor, ITransientDependency -{ - private readonly ITemplateVariablesRepository _templateVariablesRepository; - private readonly IEmailGroupsRepository _emailGroupsRepository; - - public NotificationsDataSeedContributor(ITemplateVariablesRepository templateVariablesRepository, IEmailGroupsRepository emailGroupsRepository) - { - _templateVariablesRepository = templateVariablesRepository; - _emailGroupsRepository = emailGroupsRepository; - } - - public async Task SeedAsync(DataSeedContext context) - { - if (context.TenantId == null) // only seed into a tenant database - { - return; - } - - var emailTemplateVariableDtos = new List - { - new EmailTempateVariableDto { Name = "Applicant name", Token = "applicant_name", MapTo = "applicant.applicantName" }, - new EmailTempateVariableDto { Name = "Submission #", Token = "submission_number", MapTo = "referenceNo" }, - new EmailTempateVariableDto { Name = "Submission Date", Token = "submission_date", MapTo = "submissionDate" }, - new EmailTempateVariableDto { Name = "Category", Token = "category", MapTo = "applicationForm.category" }, - new EmailTempateVariableDto { Name = "Status", Token = "status", MapTo = "status" }, - new EmailTempateVariableDto { Name = "Approved Amount", Token = "approved_amount", MapTo = "approvedAmount" }, - new EmailTempateVariableDto { Name = "Approval date", Token = "approval_date", MapTo = "finalDecisionDate" }, - new EmailTempateVariableDto { Name = "Community", Token = "community", MapTo = "community" }, - new EmailTempateVariableDto { Name = "Contact Full Name", Token = "contact_full_name", MapTo = "contactFullName" }, - new EmailTempateVariableDto { Name = "Contact Title", Token = "contact_title", MapTo = "contactTitle" }, - new EmailTempateVariableDto { Name = "Decline Rationale", Token = "decline_rationale", MapTo = "declineRational" }, - new EmailTempateVariableDto { Name = "Registered Organization Name", Token = "organization_name", MapTo = "organizationName" }, - new EmailTempateVariableDto { Name = "Project Start Date", Token = "project_start_date", MapTo = "projectStartDate" }, - new EmailTempateVariableDto { Name = "Project End Date", Token = "project_end_date", MapTo = "projectEndDate" }, - new EmailTempateVariableDto { Name = "Project Name", Token = "project_name", MapTo = "projectName" }, - new EmailTempateVariableDto { Name = "Project Summary", Token = "project_summary", MapTo = "projectSummary" }, - new EmailTempateVariableDto { Name = "Signing Authority Full Name", Token = "signing_authority_full_name", MapTo = "signingAuthorityFullName" }, - new EmailTempateVariableDto { Name = "Signing Authority Title", Token = "signing_authority_title", MapTo = "signingAuthorityTitle" }, - new EmailTempateVariableDto { Name = "Applicant ID", Token = "applicant_id", MapTo = "applicant.unityApplicantId" }, - new EmailTempateVariableDto { Name = "Requested Amount", Token = "requested_amount", MapTo = "requestedAmount" } - }; - - try - { - foreach (var template in emailTemplateVariableDtos) - { - var existingVariable = await _templateVariablesRepository.FindAsync(tv => tv.Token == template.Token); - if (existingVariable == null) - { - await _templateVariablesRepository.InsertAsync( - new TemplateVariable { Name = template.Name, Token = template.Token, MapTo = template.MapTo }, - autoSave: true - ); - } - else if (existingVariable.Token == "category" && existingVariable.MapTo == "category") - { - existingVariable.MapTo = "applicationForm.category"; - await _templateVariablesRepository.UpdateAsync(existingVariable, autoSave: true); - } - } - } - catch (Exception ex) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.Notifications.EmailGroups; +using Unity.Notifications.Templates; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + + +namespace Unity.Notifications; + +public class NotificationsDataSeedContributor(ITemplateVariablesRepository templateVariablesRepository, + IEmailGroupsRepository emailGroupsRepository) : IDataSeedContributor, ITransientDependency +{ + + public async Task SeedAsync(DataSeedContext context) + { + if (context.TenantId == null) // only seed into a tenant database { - throw new InvalidOperationException($"Error seeding Notifications Data for Templates: {ex.Message}"); - } - - var emailGroups = new List - { - new EmailGroupDto {Name = "FSB-AP", Description = "This group manages the recipients for PO-related payments, which will be sent to FSB-AP to update contracts and initiate payment creation.",Type = "static"}, - new EmailGroupDto {Name = "Payments", Description = "This group manages the recipients for payment notifications, such as failures or errors",Type = "dynamic"} - }; - try - { - var allGroups = await _emailGroupsRepository.GetListAsync(); - foreach (var emailGroup in emailGroups) - { - var existingGroup = allGroups.FirstOrDefault(g => g.Name == emailGroup.Name); - if (existingGroup == null) - { - await _emailGroupsRepository.InsertAsync( - new EmailGroup { Name = emailGroup.Name, Description = emailGroup.Description, Type = emailGroup.Type }, - autoSave: true - ); - } - } - - } - catch (Exception ex) - { - throw new InvalidOperationException($"Error seeding Notifications Data for Email Groups: {ex.Message}"); - } - } - - internal class EmailGroupDto - { - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string Type { get; set; } = string.Empty; - } - internal class EmailTempateVariableDto - { - public string Name { get; set; } = string.Empty; - public string Token { get; set; } = string.Empty; - public string MapTo { get; set; } = string.Empty; - } + return; + } + + var emailTemplateVariableDtos = new List + { + new EmailTempateVariableDto { Name = "Applicant name", Token = "applicant_name", MapTo = "applicant.applicantName" }, + new EmailTempateVariableDto { Name = "Submission #", Token = "submission_number", MapTo = "referenceNo" }, + new EmailTempateVariableDto { Name = "Submission Date", Token = "submission_date", MapTo = "submissionDate" }, + new EmailTempateVariableDto { Name = "Category", Token = "category", MapTo = "applicationForm.category" }, + new EmailTempateVariableDto { Name = "Status", Token = "status", MapTo = "status" }, + new EmailTempateVariableDto { Name = "Approved Amount", Token = "approved_amount", MapTo = "approvedAmount" }, + new EmailTempateVariableDto { Name = "Approval date", Token = "approval_date", MapTo = "finalDecisionDate" }, + new EmailTempateVariableDto { Name = "Community", Token = "community", MapTo = "community" }, + new EmailTempateVariableDto { Name = "Contact Full Name", Token = "contact_full_name", MapTo = "contactFullName" }, + new EmailTempateVariableDto { Name = "Contact Title", Token = "contact_title", MapTo = "contactTitle" }, + new EmailTempateVariableDto { Name = "Decline Rationale", Token = "decline_rationale", MapTo = "declineRational" }, + new EmailTempateVariableDto { Name = "Registered Organization Name", Token = "organization_name", MapTo = "organizationName" }, + new EmailTempateVariableDto { Name = "Project Start Date", Token = "project_start_date", MapTo = "projectStartDate" }, + new EmailTempateVariableDto { Name = "Project End Date", Token = "project_end_date", MapTo = "projectEndDate" }, + new EmailTempateVariableDto { Name = "Project Name", Token = "project_name", MapTo = "projectName" }, + new EmailTempateVariableDto { Name = "Project Summary", Token = "project_summary", MapTo = "projectSummary" }, + new EmailTempateVariableDto { Name = "Signing Authority Full Name", Token = "signing_authority_full_name", MapTo = "signingAuthorityFullName" }, + new EmailTempateVariableDto { Name = "Signing Authority Title", Token = "signing_authority_title", MapTo = "signingAuthorityTitle" }, + new EmailTempateVariableDto { Name = "Applicant ID", Token = "applicant_id", MapTo = "applicant.unityApplicantId" }, + new EmailTempateVariableDto { Name = "Requested Amount", Token = "requested_amount", MapTo = "requestedAmount" } + }; + + try + { + foreach (var template in emailTemplateVariableDtos) + { + var existingVariable = await templateVariablesRepository.FindAsync(tv => tv.Token == template.Token); + if (existingVariable == null) + { + await templateVariablesRepository.InsertAsync( + new TemplateVariable { Name = template.Name, Token = template.Token, MapTo = template.MapTo }, + autoSave: true + ); + } + else if (existingVariable.Token == "category" && existingVariable.MapTo == "category") + { + existingVariable.MapTo = "applicationForm.category"; + await templateVariablesRepository.UpdateAsync(existingVariable, autoSave: true); + } + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error seeding Notifications Data for Templates: {ex.Message}"); + } + + var emailGroups = new List + { + new EmailGroupDto {Name = "FSB-AP", Description = "This group manages the recipients for PO-related payments, which will be sent to FSB-AP to update contracts and initiate payment creation.",Type = "static"}, + new EmailGroupDto {Name = "Payments", Description = "This group manages the recipients for payment notifications, such as failures or errors",Type = "dynamic"} + }; + try + { + var allGroups = await emailGroupsRepository.GetListAsync(); + foreach (var emailGroup in emailGroups) + { + var existingGroup = allGroups.FirstOrDefault(g => g.Name == emailGroup.Name); + if (existingGroup == null) + { + await emailGroupsRepository.InsertAsync( + new EmailGroup { Name = emailGroup.Name, Description = emailGroup.Description, Type = emailGroup.Type }, + autoSave: true + ); + } + } + + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error seeding Notifications Data for Email Groups: {ex.Message}"); + } + } + + internal class EmailGroupDto + { + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + } + internal class EmailTempateVariableDto + { + public string Name { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; + public string MapTo { get; set; } = string.Empty; + } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/DynamicUrl.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/DynamicUrl.cs new file mode 100644 index 000000000..35b5e15fa --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/DynamicUrl.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.GrantManager.Notifications.Settings; + +public class DynamicUrl : FullAuditedEntity, IMultiTenant +{ + public string KeyName { get; set; } = string.Empty; + + public string Url { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public Guid? TenantId { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/IDynamicUrlRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/IDynamicUrlRepository.cs new file mode 100644 index 000000000..b4ac701f3 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Settings/IDynamicUrlRepository.cs @@ -0,0 +1,9 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Unity.GrantManager.Notifications.Settings; + +public interface IDynamicUrlRepository : IRepository +{ + +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs new file mode 100644 index 000000000..4d457fb2d --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Unity.GrantManager.Notifications.Settings; + +namespace Unity.Notifications.EntityFrameworkCore; + +[ConnectionStringName("Default")] +public class GrantManagerDbContext : AbpDbContext +{ + public DbSet DynamicUrls { get; set; } + + // Add DbSet for each Aggregate Root here. + public GrantManagerDbContext(DbContextOptions options) + : base(options) + { + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs new file mode 100644 index 000000000..9875551fe --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs @@ -0,0 +1,18 @@ +using System; +using Unity.GrantManager.Notifications.Settings; +using Unity.Notifications.EntityFrameworkCore; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Unity.GrantManager.Repositories +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IDynamicUrlRepository))] + public class DynamicUrlRepository : EfCoreRepository, IDynamicUrlRepository + { + public DynamicUrlRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasClientOptions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasClientOptions.cs index ea0fb984a..b1e0715b9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasClientOptions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasClientOptions.cs @@ -2,7 +2,6 @@ { public class CasClientOptions { - public string CasBaseUrl { get; set; } = string.Empty; public string CasClientId { get; set; } = string.Empty; public string CasClientSecret { get; set; } = string.Empty; } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasTokenService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasTokenService.cs index 7cd54851c..b2b5cfe03 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasTokenService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/CasTokenService.cs @@ -1,6 +1,7 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Unity.GrantManager.Integrations; using Unity.Modules.Shared.Integrations; using Volo.Abp; using Volo.Abp.Application.Services; @@ -12,6 +13,7 @@ namespace Unity.Payments.Integrations.Cas [IntegrationService] [ExposeServices(typeof(CasTokenService), typeof(ICasTokenService))] public class CasTokenService( + IEndpointManagementAppService endpointManagementAppService, IOptions casClientOptions, IHttpClientFactory httpClientFactory, IDistributedCache chesTokenCache @@ -22,9 +24,10 @@ IDistributedCache chesTokenCache public async Task GetAuthTokenAsync() { + string caseBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE); ClientOptions clientOptions = new ClientOptions { - Url = $"{casClientOptions.Value.CasBaseUrl}/{OAUTH_PATH}", + Url = $"{caseBaseUrl}/{OAUTH_PATH}", ClientId = casClientOptions.Value.CasClientId, ClientSecret = casClientOptions.Value.CasClientSecret, ApiKey = CAS_API_KEY, diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/InvoiceService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/InvoiceService.cs index 1eb47ec87..9c1b77b47 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/InvoiceService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/Cas/InvoiceService.cs @@ -18,6 +18,7 @@ using Unity.Modules.Shared.Http; using Unity.Payments.PaymentConfigurations; using Unity.Payments.Domain.AccountCodings; +using Unity.GrantManager.Integrations; namespace Unity.Payments.Integrations.Cas { @@ -26,6 +27,7 @@ namespace Unity.Payments.Integrations.Cas [ExposeServices(typeof(InvoiceService), typeof(IInvoiceService))] #pragma warning disable S107 // Methods should not have too many parameters public class InvoiceService( + IEndpointManagementAppService endpointManagementAppService, ICasTokenService iTokenService, IAccountCodingRepository accountCodingRepository, PaymentConfigurationAppService paymentConfigurationAppService, @@ -79,7 +81,7 @@ public class InvoiceService( InvoiceLineAmount = paymentRequest.Amount, DefaultDistributionAccount = accountDistributionCode // This will be at the tenant level }; - casInvoice.InvoiceLineDetails = new List { invoiceLineDetail }; + casInvoice.InvoiceLineDetails = [invoiceLineDetail]; } return casInvoice; @@ -101,16 +103,11 @@ public class InvoiceService( InvoiceResponse invoiceResponse = new(); try { - PaymentRequest? paymentRequest = await paymentRequestRepository.GetPaymentRequestByInvoiceNumber(invoiceNumber); - if (paymentRequest is null) - { - throw new UserFriendlyException("CreateInvoiceByPaymentRequestAsync: Payment Request not found"); - } + var paymentRequest = await paymentRequestRepository.GetPaymentRequestByInvoiceNumber(invoiceNumber) + ?? throw new UserFriendlyException("CreateInvoiceByPaymentRequestAsync: Payment Request not found"); if (!paymentRequest.AccountCodingId.HasValue) - { throw new UserFriendlyException("CreateInvoiceByPaymentRequestAsync: Account Coding - Payment Request - not found"); - } AccountCoding accountCoding = await accountCodingRepository.GetAsync(paymentRequest.AccountCodingId.Value); string accountDistributionCode = await paymentConfigurationAppService.GetAccountDistributionCode(accountCoding);// this will be on the payment request @@ -170,14 +167,15 @@ public async Task CreateInvoiceAsync(Invoice casAPInvoice) { string jsonString = JsonSerializer.Serialize(casAPInvoice); var authToken = await iTokenService.GetAuthTokenAsync(); - var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_APINVOICE}/"; - var response = await resilientHttpRequest.HttpAsyncWithBody(HttpMethod.Post, resource, jsonString, authToken); + string casBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE); + var resource = $"{casBaseUrl}/{CFS_APINVOICE}/"; + var response = await resilientHttpRequest.HttpAsync(HttpMethod.Post, resource, jsonString, authToken); if (response != null) { if (response.Content != null && response.StatusCode != HttpStatusCode.NotFound) { - var contentString = ResilientHttpRequest.ContentToString(response.Content); + var contentString = await ResilientHttpRequest.ContentToStringAsync(response.Content); var result = JsonSerializer.Deserialize(contentString) ?? throw new UserFriendlyException("CAS InvoiceService CreateInvoiceAsync Exception: " + response); result.CASHttpStatusCode = response.StatusCode; @@ -201,14 +199,15 @@ public async Task CreateInvoiceAsync(Invoice casAPInvoice) public async Task GetCasInvoiceAsync(string invoiceNumber, string supplierNumber, string supplierSiteCode) { var authToken = await iTokenService.GetAuthTokenAsync(); - var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{supplierSiteCode}"; + var casBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE); + var resource = $"{casBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{supplierSiteCode}"; var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, authToken); if (response != null && response.Content != null && response.IsSuccessStatusCode) { - string contentString = ResilientHttpRequest.ContentToString(response.Content); + string contentString = await ResilientHttpRequest.ContentToStringAsync(response.Content); var result = JsonSerializer.Deserialize(contentString); return result ?? new CasPaymentSearchResult(); } @@ -221,7 +220,8 @@ public async Task GetCasInvoiceAsync(string invoiceNumbe public async Task GetCasPaymentAsync(string invoiceNumber, string supplierNumber, string siteNumber) { var authToken = await iTokenService.GetAuthTokenAsync(); - var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{siteNumber}"; + var casBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE); + var resource = $"{casBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{siteNumber}"; var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, authToken); CasPaymentSearchResult casPaymentSearchResult = new(); 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 cbaff113e..1227d82bc 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 @@ -13,27 +13,45 @@ using Unity.Modules.Shared.Correlation; using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Unity.GrantManager.Integrations; namespace Unity.Payments.Integrations.Cas { [IntegrationService] [ExposeServices(typeof(SupplierService), typeof(ISupplierService))] - public class SupplierService(ILocalEventBus localEventBus, - IResilientHttpRequest resilientHttpRequest, - IOptions casClientOptions, - ICasTokenService iTokenService) : ApplicationService, ISupplierService + 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 ILocalEventBus localEventBus; + private readonly IResilientHttpRequest resilientHttpRequest; + private readonly ICasTokenService iTokenService; + public SupplierService (ILocalEventBus localEventBus, + IEndpointManagementAppService endpointManagementAppService, + IResilientHttpRequest resilientHttpRequest, + ICasTokenService iTokenService) + { + this.localEventBus = localEventBus; + this.resilientHttpRequest = resilientHttpRequest; + this.iTokenService = iTokenService; + + // Initialize the base API URL once during construction + casBaseApiTask = InitializeBaseApiAsync(endpointManagementAppService); + } + private static async Task InitializeBaseApiAsync(IEndpointManagementAppService endpointManagementAppService) + { + var url = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE); + return url ?? throw new UserFriendlyException("Payment API base URL is not configured."); + } + public virtual async Task UpdateApplicantSupplierInfo(string? supplierNumber, Guid applicantId) { Logger.LogInformation("SupplierService->UpdateApplicantSupplierInfo: {SupplierNumber}, {ApplicantId}", supplierNumber, applicantId); - + // Integrate with payments module to update / insert supplier if (await FeatureChecker.IsEnabledAsync(PaymentConsts.UnityPaymentsFeature) && !string.IsNullOrEmpty(supplierNumber)) @@ -69,7 +87,7 @@ public async Task UpdateApplicantSupplierInfoByBn9(string? bn9, Guid ap catch (Exception ex) { Logger.LogError(ex, "An exception occurred updating the supplier for BN9: {ExceptionMessage}", ex.Message); - casSupplierResponse = "An exception occurred updating the supplier for BN9: " + ex.Message; + casSupplierResponse = "An exception occurred updating the supplier for BN9: " + ex.Message; } return casSupplierResponse; @@ -77,36 +95,52 @@ public async Task UpdateApplicantSupplierInfoByBn9(string? bn9, Guid ap private async Task UpdateSupplierInfo(dynamic casSupplierResponse, Guid applicantId) { - try { - UpsertSupplierEto supplierEto = GetEventDtoFromCasResponse(casSupplierResponse); + try + { + var casSupplierJson = casSupplierResponse is string str ? str : casSupplierResponse.ToString(); + using var doc = JsonDocument.Parse(casSupplierJson); + var rootElement = doc.RootElement; + if (rootElement.TryGetProperty("code", out JsonElement codeProp) && codeProp.GetString() == "Unauthorized") + throw new UserFriendlyException("Unauthorized access to CAS supplier information."); + UpsertSupplierEto supplierEto = GetEventDtoFromCasResponse(rootElement); supplierEto.CorrelationId = applicantId; supplierEto.CorrelationProvider = CorrelationConsts.Applicant; await localEventBus.PublishAsync(supplierEto); - } catch (Exception ex) + } + catch (Exception ex) { Logger.LogError(ex, "An exception occurred updating the supplier: {ExceptionMessage}", ex.Message); - throw new UserFriendlyException("An exception occurred updating the supplier."); + throw new UserFriendlyException("An exception occurred updating the supplier: " + ex.Message); } } - protected virtual UpsertSupplierEto GetEventDtoFromCasResponse(dynamic casSupplierResponse) + protected virtual UpsertSupplierEto GetEventDtoFromCasResponse(JsonElement casSupplierResponse) { - string lastUpdated = casSupplierResponse.GetProperty("lastupdated").ToString(); - string suppliernumber = casSupplierResponse.GetProperty("suppliernumber").ToString(); - string suppliername = casSupplierResponse.GetProperty("suppliername").ToString(); - string subcategory = casSupplierResponse.GetProperty("subcategory").ToString(); - string providerid = casSupplierResponse.GetProperty("providerid").ToString(); - string businessnumber = casSupplierResponse.GetProperty("businessnumber").ToString(); - string status = casSupplierResponse.GetProperty("status").ToString(); - string supplierprotected = casSupplierResponse.GetProperty("supplierprotected").ToString(); - string standardindustryclassification = casSupplierResponse.GetProperty("standardindustryclassification").ToString(); + string GetProp(string name) => + casSupplierResponse.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null + ? prop.ToString() + : string.Empty; + + string lastUpdated = GetProp("lastupdated"); + string suppliernumber = GetProp("suppliernumber"); + string suppliername = GetProp("suppliername"); + string subcategory = GetProp("subcategory"); + string providerid = GetProp("providerid"); + string businessnumber = GetProp("businessnumber"); + string status = GetProp("status"); + string supplierprotected = GetProp("supplierprotected"); + string standardindustryclassification = GetProp("standardindustryclassification"); _ = DateTime.TryParse(lastUpdated, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime lastUpdatedDate); - List siteEtos = new List(); - JArray siteArray = Newtonsoft.Json.JsonConvert.DeserializeObject(casSupplierResponse.GetProperty("supplieraddress").ToString()); - foreach (dynamic site in siteArray) + + var siteEtos = new List(); + if (casSupplierResponse.TryGetProperty("supplieraddress", out var sitesJson) && + sitesJson.ValueKind == JsonValueKind.Array) { - siteEtos.Add(GetSiteEto(site)); + foreach (var site in sitesJson.EnumerateArray()) + { + siteEtos.Add(GetSiteEto(site)); + } } return new UpsertSupplierEto @@ -124,6 +158,7 @@ protected virtual UpsertSupplierEto GetEventDtoFromCasResponse(dynamic casSuppli }; } + protected static SiteEto GetSiteEto(dynamic site) { string supplierSiteCode = site["suppliersitecode"].ToString(); @@ -169,7 +204,8 @@ public async Task GetCasSupplierInformationAsync(string? supplierNumber { if (!string.IsNullOrEmpty(supplierNumber)) { - var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_SUPPLIER}/{supplierNumber}"; + var casBaseApi = await casBaseApiTask; + var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{supplierNumber}"; return await GetCasSupplierInformationByResourceAsync(resource); } else @@ -182,7 +218,8 @@ public async Task GetCasSupplierInformationByBn9Async(string? bn9) { if (!string.IsNullOrEmpty(bn9)) { - var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_SUPPLIER}/{bn9}/businessnumber"; + var casBaseApi = await casBaseApiTask; + var resource = $"{casBaseApi}/{CFS_SUPPLIER}/{bn9}/businessnumber"; return await GetCasSupplierInformationByResourceAsync(resource); } else @@ -199,34 +236,33 @@ private async Task GetCasSupplierInformationByResourceAsync(string? res var authToken = await iTokenService.GetAuthTokenAsync(); try { - using (var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, authToken)) { - if (response != null) + using var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, authToken); + if (response != null) + { + if (response.Content != null && response.StatusCode != HttpStatusCode.NotFound) + { + var contentString = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(contentString) + ?? throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync: " + response); + return result; + } + else if (response.StatusCode == HttpStatusCode.NotFound) { - if (response.Content != null && response.StatusCode != HttpStatusCode.NotFound) - { - var contentString = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(contentString) - ?? throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync: " + response); - return result; - } - else if (response.StatusCode == HttpStatusCode.NotFound) - { - throw new UserFriendlyException("Supplier not Found."); - } - else if (response.StatusCode != HttpStatusCode.OK) - { - throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync Status Code: " + response.StatusCode); - } - else - { - throw new UserFriendlyException("The CAS Supplier Number was not found."); - } + throw new UserFriendlyException("Supplier not Found."); + } + else if (response.StatusCode != HttpStatusCode.OK) + { + throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync Status Code: " + response.StatusCode); } else { - throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync: Null response"); + throw new UserFriendlyException("The CAS Supplier Number was not found."); } } + else + { + throw new UserFriendlyException("CAS SupplierService GetCasSupplierInformationAsync: Null response"); + } } catch (Exception ex) { 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 b54839aa6..39115ece0 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 @@ -143,7 +143,7 @@ private static string GenerateReferenceNumberAsync(string referenceNumber, strin private static string GenerateSequenceNumberAsync(int sequenceNumber, int index) { - sequenceNumber = sequenceNumber + index; + sequenceNumber += index; return sequenceNumber.ToString("D4"); } @@ -282,13 +282,7 @@ private async Task GetLevel2ApprovalActionAsync(UpdatePay { var application = await (await applicationRepository.GetQueryableAsync()) .Include(a => a.ApplicationForm) - .FirstOrDefaultAsync(a => a.Id == applicationId); - - if (application == null) - { - throw new BusinessException($"Application with Id {applicationId} not found."); - } - + .FirstOrDefaultAsync(a => a.Id == applicationId) ?? throw new BusinessException($"Application with Id {applicationId} not found."); var appForm = application.ApplicationForm ?? (application.ApplicationFormId != Guid.Empty ? await applicationFormRepository.GetAsync(application.ApplicationFormId) @@ -306,13 +300,13 @@ private async Task GetLevel2ApprovalActionAsync(UpdatePay private async Task CanPerformLevel1ActionAsync(PaymentRequestStatus status) { - List level1Approvals = new() { PaymentRequestStatus.L1Pending, PaymentRequestStatus.L1Declined }; + List level1Approvals = [PaymentRequestStatus.L1Pending, PaymentRequestStatus.L1Declined]; return await permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L1ApproveOrDecline) && level1Approvals.Contains(status); } private async Task CanPerformLevel2ActionAsync(PaymentRequest payment, bool IsApprove) { - List level2Approvals = new() { PaymentRequestStatus.L2Pending, PaymentRequestStatus.L2Declined }; + List level2Approvals = [PaymentRequestStatus.L2Pending, PaymentRequestStatus.L2Declined]; // Rule AB#26693: Reject Payment Request update if violates L1 and L2 separation of duties var IsSameApprover = CurrentUser.Id == payment.ExpenseApprovals.FirstOrDefault(x => x.Type == ExpenseApprovalType.Level1)?.DecisionUserId; @@ -327,7 +321,7 @@ private async Task CanPerformLevel2ActionAsync(PaymentRequest payment, boo private async Task CanPerformLevel3ActionAsync(PaymentRequestStatus status) { - List level3Approvals = new() { PaymentRequestStatus.L3Pending, PaymentRequestStatus.L3Declined }; + List level3Approvals = [PaymentRequestStatus.L3Pending, PaymentRequestStatus.L3Declined]; return await permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L3ApproveOrDecline) && level3Approvals.Contains(status); } @@ -395,19 +389,17 @@ protected internal async Task> MapToDtoAndLoadDetailsAsy var paymentDtos = ObjectMapper.Map, List>(paymentsList); // Flatten all DecisionUserIds from ExpenseApprovals across all PaymentRequestDtos - List paymentRequesterIds = paymentDtos + List paymentRequesterIds = [.. paymentDtos .Select(payment => payment.CreatorId) .OfType() - .Distinct() - .ToList(); + .Distinct()]; - List expenseApprovalCreatorIds = paymentDtos + List expenseApprovalCreatorIds = [.. paymentDtos .SelectMany(payment => payment.ExpenseApprovals) .Where(expenseApproval => expenseApproval.Status != ExpenseApprovalStatus.Requested) .Select(expenseApproval => expenseApproval.DecisionUserId) .OfType() - .Distinct() - .ToList(); + .Distinct()]; // Call external lookup for each distinct User Id and store in a dictionary. var userDictionary = new Dictionary(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml.cs index 4df589584..73666169a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml.cs @@ -21,7 +21,7 @@ public class PaymentGrouping { public int GroupId { get; set; } public PaymentRequestStatus ToStatus { get; set; } - public List Items { get; set; } = new(); + public List Items { get; set; } = []; } public class UpdatePaymentRequestStatus( @@ -32,7 +32,7 @@ public class UpdatePaymentRequestStatus( IPaymentConfigurationAppService paymentConfigurationAppService, IPermissionCheckerService permissionCheckerService) : AbpPageModel { - [BindProperty] public List PaymentGroupings { get; set; } = new(); + [BindProperty] public List PaymentGroupings { get; set; } = []; [BindProperty] public decimal? UserPaymentThreshold { get; set; } [BindProperty] public decimal PaymentThreshold { get; set; } [BindProperty] public bool DisableSubmit { get; set; } @@ -44,7 +44,7 @@ public class UpdatePaymentRequestStatus( [BindProperty] public string? Note { get; set; } = string.Empty; - public List SelectedPaymentIds { get; set; } = new(); + public List SelectedPaymentIds { get; set; } = []; public string FromStatusText { get; set; } = string.Empty; public async Task OnGetAsync(string paymentIds, bool isApprove) @@ -53,15 +53,7 @@ public async Task OnGetAsync(string paymentIds, bool isApprove) var payments = await paymentRequestAppService.GetListByPaymentIdsAsync(SelectedPaymentIds); var paymentApprovals = await BuildPaymentApprovalsAsync(payments); - PaymentGroupings = paymentApprovals - .GroupBy(item => item.ToStatus) - .Select((group, index) => new PaymentGrouping - { - GroupId = index, - ToStatus = group.Key, - Items = group.ToList() - }) - .ToList(); + PaymentGroupings = [.. paymentApprovals.GroupBy(item => item.ToStatus).Select((group, index) => new PaymentGrouping { GroupId = index, ToStatus = group.Key, Items = [.. group] })]; DisableSubmit = paymentApprovals.Count == 0 || !ModelState.IsValid; } @@ -70,7 +62,7 @@ private async Task InitializeStateAsync(string paymentIds, bool isApprove) { await GetFromStateForUserAsync(); IsApproval = isApprove; - SelectedPaymentIds = JsonSerializer.Deserialize>(paymentIds) ?? new(); + SelectedPaymentIds = JsonSerializer.Deserialize>(paymentIds) ?? []; UserPaymentThreshold = await paymentRequestAppService.GetUserPaymentThresholdAsync(); HasPaymentConfiguration = await paymentConfigurationAppService.GetAsync() != null; } diff --git a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs index c299201fe..c3b47eb67 100644 --- a/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs +++ b/applications/Unity.GrantManager/modules/Unity.Reporting/src/Unity.Reporting.Web/Menus/ReportingMenuContributor.cs @@ -13,14 +13,7 @@ public async Task ConfigureMenuAsync(MenuConfigurationContext context) private static Task ConfigureReportingMenuAsync(MenuConfigurationContext context) { - //Add main menu items. - context.Menu.AddItem( - new ApplicationMenuItem( - ReportingMenus.Prefix, - displayName: "Reporting Admin", - "~/ReportingAdmin", - requiredPermissionName: IdentityConsts.ITAdminPermissionName - )); + context.Menu.AddItem( new ApplicationMenuItem( ReportingMenus.Prefix, diff --git a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/IResilientHttpRequest.cs b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/IResilientHttpRequest.cs index 537c0127c..c572b1cfe 100644 --- a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/IResilientHttpRequest.cs +++ b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/IResilientHttpRequest.cs @@ -1,4 +1,5 @@ using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Volo.Abp; @@ -6,7 +7,21 @@ namespace Unity.Modules.Shared.Http { public interface IResilientHttpRequest : IRemoteService { - Task HttpAsyncWithBody(HttpMethod httpVerb, string resource, string? body = null, string? authToken = null); - Task HttpAsync(HttpMethod httpVerb, string resource, string? authToken = null); + /// + /// Send an HTTP request with optional JSON body, authentication, and resilience policies. + /// If body is an object, it will be automatically serialized to JSON. + /// + Task HttpAsync( + HttpMethod httpVerb, + string resource, + object? body = null, + string? authToken = null, + (string username, string password)? basicAuth = null, + CancellationToken cancellationToken = default); + + /// + /// Set a base URL to be used for relative request paths. + /// + void SetBaseUrl(string baseUrl); } -} +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/ResilientHttpRequest.cs b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/ResilientHttpRequest.cs index 7af446d75..61e0856e3 100644 --- a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/ResilientHttpRequest.cs +++ b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Http/ResilientHttpRequest.cs @@ -5,109 +5,143 @@ using System.Net; using System.Net.Http; using System.Text; +using System.Threading; using System.Threading.Tasks; using Volo.Abp; +using Newtonsoft.Json; namespace Unity.Modules.Shared.Http { [IntegrationService] - public class ResilientHttpRequest : IResilientHttpRequest + public class ResilientHttpRequest(HttpClient httpClient) : IResilientHttpRequest { - private readonly IHttpClientFactory _httpClientFactory; private static int _maxRetryAttempts = 3; - private const int OneMinuteInSeconds = 60; private static TimeSpan _pauseBetweenFailures = TimeSpan.FromSeconds(2); - private static TimeSpan _httpRequestTimeout = TimeSpan.FromSeconds(OneMinuteInSeconds); + private static TimeSpan _httpRequestTimeout = TimeSpan.FromSeconds(60); - public ResilientHttpRequest(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } + private const string AuthorizationHeader = "Authorization"; + + private static ResiliencePipeline _pipeline = BuildPipeline(); + + private string? _baseUrl; + private readonly HttpClient _httpClient = httpClient; + + private static readonly HttpStatusCode[] RetryableStatusCodes = + [ + HttpStatusCode.TooManyRequests, + HttpStatusCode.InternalServerError, + HttpStatusCode.BadGateway, + HttpStatusCode.ServiceUnavailable, + HttpStatusCode.GatewayTimeout + ]; public static void SetPipelineOptions( - int maxRetryAttempts, + int maxRetryAttempts, TimeSpan pauseBetweenFailures, - TimeSpan httpRequestTimeout - ) + TimeSpan httpRequestTimeout) { _maxRetryAttempts = maxRetryAttempts; _pauseBetweenFailures = pauseBetweenFailures; _httpRequestTimeout = httpRequestTimeout; - } - private static bool ReprocessBasedOnStatusCode(HttpStatusCode statusCode) - { - HttpStatusCode[] reprocessStatusCodes = { - HttpStatusCode.TooManyRequests, - HttpStatusCode.InternalServerError, - HttpStatusCode.BadGateway, - HttpStatusCode.ServiceUnavailable, - HttpStatusCode.GatewayTimeout, - }; - - return reprocessStatusCodes.Contains(statusCode); + _pipeline = BuildPipeline(); // rebuild with new settings } - private static ResiliencePipeline _pipeline = - new ResiliencePipelineBuilder() - .AddRetry(new RetryStrategyOptions - { - ShouldHandle = new PredicateBuilder() - .Handle() - .HandleResult(result => ReprocessBasedOnStatusCode(result.StatusCode)), - Delay = _pauseBetweenFailures, - MaxRetryAttempts = _maxRetryAttempts, - UseJitter = true, - BackoffType = DelayBackoffType.Exponential, - OnRetry = args => - { - return default; - } - }) - .AddTimeout(_httpRequestTimeout) - .Build(); - - public async Task HttpAsync(HttpMethod httpVerb, string resource, string? authToken = null) + private static ResiliencePipeline BuildPipeline() { - return await ExecuteRequestAsync(httpVerb, resource, null, authToken); + return new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(result => ShouldRetry(result.StatusCode)), + Delay = _pauseBetweenFailures, + MaxRetryAttempts = _maxRetryAttempts, + UseJitter = true, + BackoffType = DelayBackoffType.Exponential + }) + .AddTimeout(_httpRequestTimeout) + .Build(); } - public async Task HttpAsyncWithBody(HttpMethod httpVerb, string resource, string? body = null, string? authToken = null) + public void SetBaseUrl(string baseUrl) { - return await ExecuteRequestAsync(httpVerb, resource, body, authToken); - } + if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out _)) + { + throw new ArgumentException("Base URL is not a valid absolute URI.", nameof(baseUrl)); + } - public static string ContentToString(HttpContent httpContent) - { - var readAsStringAsync = httpContent.ReadAsStringAsync(); - return readAsStringAsync.Result; + _baseUrl = baseUrl.TrimEnd('/'); } - private async Task ExecuteRequestAsync( - HttpMethod httpVerb, + private static bool ShouldRetry(HttpStatusCode statusCode) => + RetryableStatusCodes.Contains(statusCode); + + /// + /// Send an HTTP request with resilience policies applied. + /// + public async Task HttpAsync( + HttpMethod httpVerb, string resource, - string? body, - string? authToken) + object? body = null, + string? authToken = null, + (string username, string password)? basicAuth = null, + CancellationToken cancellationToken = default) { - // HttpClient uses the system default TLS settings; no need to set ServicePointManager.SecurityProtocol. - HttpRequestMessage requestMessage = new HttpRequestMessage(httpVerb, resource) { Version = new Version(3, 0) }; - using HttpClient httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Accept.Clear(); - httpClient.DefaultRequestHeaders.Clear(); - httpClient.DefaultRequestHeaders.ConnectionClose = true; - - if (!authToken.IsNullOrEmpty()) + // Determine final URL + if (!Uri.TryCreate(resource, UriKind.Absolute, out Uri? fullUrl)) { - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {authToken}"); + if (string.IsNullOrWhiteSpace(_baseUrl)) + { + throw new InvalidOperationException("Base URL must be set for relative paths."); + } + fullUrl = new Uri(new Uri(_baseUrl, UriKind.Absolute), resource); } - if (httpVerb != HttpMethod.Get && body != null) + // Execute through resilience pipeline + return await _pipeline.ExecuteAsync(async ct => { - requestMessage.Content = new StringContent(body, Encoding.UTF8, "application/json"); - } + using var requestMessage = new HttpRequestMessage(httpVerb, fullUrl); + + // Headers are per-request, not global + requestMessage.Headers.Accept.Clear(); + requestMessage.Headers.ConnectionClose = true; + + if (!string.IsNullOrWhiteSpace(authToken)) + { + requestMessage.Headers.Remove(AuthorizationHeader); + requestMessage.Headers.Add(AuthorizationHeader, $"Bearer {authToken}"); + } + else if (basicAuth.HasValue) + { + var credentials = Convert.ToBase64String( + Encoding.ASCII.GetBytes($"{basicAuth.Value.username}:{basicAuth.Value.password}") + ); + requestMessage.Headers.Remove(AuthorizationHeader); + requestMessage.Headers.Add(AuthorizationHeader, $"Basic {credentials}"); + } - HttpResponseMessage restResponse = await _pipeline.ExecuteAsync(async ct => await httpClient.SendAsync(requestMessage, ct)); - return await Task.FromResult(restResponse); + // Handle body if present + if (body != null) + { + string bodyString = body is string s + ? s + : JsonConvert.SerializeObject(body); // allow passing objects directly + + requestMessage.Content = new StringContent( + bodyString, + Encoding.UTF8, + "application/json" + ); + } + + return await _httpClient.SendAsync(requestMessage, ct); + }, cancellationToken); + } + + public static async Task ContentToStringAsync(HttpContent httpContent) + { + return await httpContent.ReadAsStringAsync(); } } } diff --git a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Permissions/IdentityConsts.cs b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Permissions/IdentityConsts.cs index 46ce155c9..1a4b1583e 100644 --- a/applications/Unity.GrantManager/modules/Unity.SharedKernel/Permissions/IdentityConsts.cs +++ b/applications/Unity.GrantManager/modules/Unity.SharedKernel/Permissions/IdentityConsts.cs @@ -5,5 +5,10 @@ public static class IdentityConsts public const string ITAdminPolicyName = "ITAdministrator"; public const string ITAdminRoleName = "ITAdministrator"; public const string ITAdminPermissionName = "ITAdministrator"; + + public const string ITOperationsPolicyName = "ITOperations"; + public const string ITOperationsRoleName = "ITOperations"; + public const string ITOperationsPermissionName = "ITOperations"; + } } diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Application.Contracts/TenantManagementPermissions.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Application.Contracts/TenantManagementPermissions.cs index b9764f49c..4b10adc48 100644 --- a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Application.Contracts/TenantManagementPermissions.cs +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Application.Contracts/TenantManagementPermissions.cs @@ -12,10 +12,10 @@ public static class Tenants public const string Default = GroupName + ".Tenants"; public const string Create = Default + ".Create"; public const string Update = Default + ".Update"; - public const string Delete = Default + ".Delete"; + public const string Delete = Default + ".Delete"; public const string ManageConnectionStrings = Default + ".ManageConnectionStrings"; - public const string ManageFeatures = AbpGroupName + ".Tenants" + ".ManageFeatures"; + public const string ManageEndpoints = AbpGroupName + ".Tenants" + ".ManageEndpoints"; } public static string[] GetAll() diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Navigation/TenantManagementMenuNames.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Navigation/TenantManagementMenuNames.cs index 8f6795200..007c2d6ab 100644 --- a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Navigation/TenantManagementMenuNames.cs +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Navigation/TenantManagementMenuNames.cs @@ -5,5 +5,6 @@ public static class TenantManagementMenuNames public const string GroupName = "TenantManagement"; public const string Tenants = GroupName + ".Tenants"; + public const string Endpoints = GroupName + ".Endpoints"; public const string Reconciliation = GroupName + ".Reconciliation"; } diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml new file mode 100644 index 000000000..3483fed16 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml @@ -0,0 +1,24 @@ +@page +@using Unity.GrantManager.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + +@model Unity.GrantManager.Web.Pages.EndpointManagement.CreateModalModel + +@inject IStringLocalizer L +@{ + Layout = null; +} + + + + + + + + + + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml.cs new file mode 100644 index 000000000..349fd9ff8 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/CreateModal.cshtml.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Unity.GrantManager.Integrations; +using Unity.Modules.Shared.Permissions; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.GrantManager.Web.Pages.EndpointManagement; + +[Authorize(IdentityConsts.ITOperationsPolicyName)] +public class CreateModalModel(IEndpointManagementAppService endpointManagementAppService) : AbpPageModel +{ + [BindProperty] + public CreateUpdateDynamicUrlDto Endpoint { get; set; } + + + public void OnGet() + { + Endpoint = new(); + } + + public async Task OnPostAsync() + { + await endpointManagementAppService.CreateAsync(Endpoint!); + return NoContent(); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/EndpointManagementPageModel.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/EndpointManagementPageModel.cs new file mode 100644 index 000000000..1b6e030d2 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/EndpointManagementPageModel.cs @@ -0,0 +1,11 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.TenantManagement.Web.Pages.EndpointManagement; + +public abstract class EndpointManagementPageModel : AbpPageModel +{ + protected EndpointManagementPageModel() + { + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml new file mode 100644 index 000000000..66071335e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml @@ -0,0 +1,51 @@ +@page +@using Microsoft.AspNetCore.Authorization; +@using Microsoft.AspNetCore.Mvc.Localization; +@using Unity.TenantManagement.Web.Navigation; +@using Volo.Abp.AspNetCore.Mvc.UI.Layout; +@using Volo.Abp.TenantManagement.Localization; +@using Unity.TenantManagement.Web.Pages.TenantManagement.Tenants; +@model IndexModel +@inject IHtmlLocalizer L +@inject IAuthorizationService Authorization +@inject IPageLayout PageLayout +@{ + PageLayout.Content.BreadCrumb.Add(L["Menu:Endpoints"].Value); + PageLayout.Content.MenuItemName = TenantManagementMenuNames.Endpoints; +} +@section styles { + + + +} +@section scripts { + + + + +} + + +
+
+
+

Endpoint Management

+
+
+ +
+ +
+ +
+
+
+ + + + + +
+
+ + diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml.cs new file mode 100644 index 000000000..1ad3aabea --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.cshtml.cs @@ -0,0 +1,16 @@ + +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Unity.TenantManagement.Web.Pages.EndpointManagement; + +public class IndexModel : PageModel +{ + public IndexModel() + { + } + + public void OnGet() + { + // Method intentionally left empty. + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.css b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.css new file mode 100644 index 000000000..3dd9b19c0 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.css @@ -0,0 +1,16 @@ +::-ms-reveal { + display: none; +} + +#AbpContentToolbar { + display: flex !important; + background-color: transparent; +} + +#EndpointManagementWrapper { + background-color: transparent; +} + +#UserSearchTable tbody tr { + cursor: pointer; +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.js b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.js new file mode 100644 index 000000000..d872831bc --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/Index.js @@ -0,0 +1,106 @@ +(function () { + const l = abp.localization.getResource('GrantManager'); + let createModal = new abp.ModalManager(abp.appPath + 'EndpointManagement/Endpoints/CreateModal'); + let updateModal = new abp.ModalManager(abp.appPath + 'EndpointManagement/Endpoints/UpdateModal'); + + /** + * Intakes: List All + */ + $.fn.dataTable.Buttons.defaults.dom.button.className = 'btn flex-none'; + let actionButtons = [ + { + text: ' ' + l('Common:Command:Create') + '', + titleAttr: l('Common:Command:Create'), + id: 'CreateButton', + className: 'btn-light rounded-1', + action: (e, dt, node, config) => createBtn(e) + } + ]; + + const listColumns = [ + { + title: "Key Name", + name: "keyName", + data: "keyName", + index: 0 + }, + { + title: "Url", + name: "url", + data: "url", + index: 1 + }, + { + title: "Description", + name: "description", + data: "description", + index: 2 + }, + { + title: l('Actions'), + data: 'id', + orderable: false, + className: 'notexport text-center', + name: 'rowActions', + index: 3, + rowAction: { + items: + [ + { + text: l('Common:Command:Edit'), + action: (data) => updateModal.open({ id: data.record.id }) + } + ] + } + } + ]; + + const defaultVisibleColumns = [ + 'keyName', + 'url', + 'description', + 'rowActions' + ]; + + let responseCallback = function (result) { + return { + recordsTotal: result.totalCount, + recordsFiltered: result.items.length, + data: result.items + }; + }; + + let dt = $('#EndpointsTable'); + + let dataTable = initializeDataTable({ + dt, + defaultVisibleColumns, + listColumns, + maxRowsPerPage: 25, + defaultSortColumn: 0, + dataEndpoint: unity.grantManager.integrations.endpoints.endpointManagement.getList, + data: {}, + responseCallback, + actionButtons, + pagingEnabled: true, + reorderEnabled: false, + languageSetValues: {}, + dataTableName: 'EndpointsTable', + dynamicButtonContainerId: 'dynamicButtonContainerId', + useNullPlaceholder: true, + externalSearchId: 'search-endpoints' + }); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + updateModal.onResult(function () { + dataTable.ajax.reload(); + }); + + function createBtn(e) { + e.preventDefault(); + createModal.open(); + }; +})(); diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml new file mode 100644 index 000000000..5c97de00f --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml @@ -0,0 +1,25 @@ +@page +@using Unity.GrantManager.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + +@model Unity.GrantManager.Web.Pages.EndpointManagement.UpdateModalModel + +@inject IStringLocalizer L +@{ + Layout = null; +} + + + + + + + + + + + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml.cs new file mode 100644 index 000000000..0f8a17190 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/Endpoints/UpdateModal.cshtml.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Unity.GrantManager.Integrations; +namespace Unity.GrantManager.Web.Pages.EndpointManagement; + +public class UpdateModalModel(IEndpointManagementAppService endpointManagementAppService) : AbpPageModel +{ + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + [BindProperty] + public CreateUpdateDynamicUrlDto Endpoint { get; set; } + + public async Task OnGetAsync() + { + var endpointDto = await endpointManagementAppService.GetAsync(Id); + Endpoint = ObjectMapper.Map(endpointDto); + } + + public async Task OnPostAsync() + { + await endpointManagementAppService.UpdateAsync(Id, Endpoint!); + await endpointManagementAppService.ClearCacheAsync(); + return NoContent(); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/_ViewImports.cshtml b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/_ViewImports.cshtml new file mode 100644 index 000000000..c1da1f5f1 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/EndpointManagement/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/TenantManagement/Tenants/TenantManagementPageModel.cs b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/TenantManagement/Tenants/TenantManagementPageModel.cs index cf76f69de..bd26beedd 100644 --- a/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/TenantManagement/Tenants/TenantManagementPageModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.TenantManagement/src/Unity.TenantManagement.Web/Pages/TenantManagement/Tenants/TenantManagementPageModel.cs @@ -1,5 +1,4 @@ -using Unity.TenantManagement.Web; -using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; namespace Unity.TenantManagement.Web.Pages.TenantManagement.Tenants; diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js index 7d3a6fd89..a24c4d86d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js +++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js @@ -334,7 +334,10 @@ function initializeDataTable(options) { } ); - initializeFilterButtonPopover(iDt); + // On the ITAdministrator pages, bootstrap popover is not loaded and causes the js to fail + if (typeof $('#btn-toggle-filter').popover !== "undefined") { + initializeFilterButtonPopover(iDt); + } searchFilter(iDt); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksDto.cs index ce68555ce..c9f708b7d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksDto.cs @@ -1,4 +1,5 @@ using System; +using Unity.GrantManager.Applications; using Volo.Abp.Application.Dtos; namespace Unity.GrantManager.GrantApplications; @@ -8,5 +9,6 @@ public class ApplicationLinksDto : EntityDto { public Guid ApplicationId { get; set; } public Guid LinkedApplicationId { get; set; } + public ApplicationLinkType LinkType { get; set; } = ApplicationLinkType.Related; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksInfoDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksInfoDto.cs index a9b90c716..bdbe55b91 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksInfoDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/ApplicationLinksInfoDto.cs @@ -1,4 +1,5 @@ using System; +using Unity.GrantManager.Applications; using Volo.Abp.Application.Dtos; namespace Unity.GrantManager.GrantApplications; @@ -11,5 +12,7 @@ public class ApplicationLinksInfoDto : EntityDto public String ApplicationStatus { get; set; } = String.Empty; public String Category { get; set; } = String.Empty; public string ProjectName { get; set; } = string.Empty; + public string ApplicantName { get; set; } = string.Empty; + public ApplicationLinkType LinkType { get; set; } = ApplicationLinkType.Related; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationLiteDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationLiteDto.cs index 95b7243df..22434446f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationLiteDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationLiteDto.cs @@ -7,5 +7,6 @@ public class GrantApplicationLiteDto : AuditedEntityDto { public string ProjectName { get; set; } = string.Empty; public string ReferenceNo { get; set; } = string.Empty; + public string ApplicantName { get; set; } = string.Empty; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationLinksService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationLinksService.cs index a3a82e1e4..4eceb9e6f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationLinksService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/IApplicationLinksService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Unity.GrantManager.Applications; using Volo.Abp.Application.Services; namespace Unity.GrantManager.GrantApplications; @@ -11,5 +12,9 @@ public interface IApplicationLinksService : ICrudAppService< { Task> GetListByApplicationAsync(Guid applicationId); Task GetLinkedApplicationAsync(Guid currentApplicationId, Guid linkedApplicationId); + Task GetCurrentApplicationInfoAsync(Guid applicationId); + Task DeleteWithPairAsync(Guid applicationLinkId); + Task GetApplicationDetailsByReferenceAsync(string referenceNumber); + Task UpdateLinkTypeAsync(Guid applicationLinkId, ApplicationLinkType newLinkType); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/IFormsApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/IFormsApiService.cs index f5671ffa1..d3b6276b7 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/IFormsApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/IFormsApiService.cs @@ -1,12 +1,14 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Unity.GrantManager.Integration.Chefs +namespace Unity.GrantManager.Integrations.Chefs { public interface IFormsApiService : IApplicationService { - Task GetFormDataAsync(string chefsFormId, string chefsFormVersionId); - Task GetForm(Guid? formId, string chefsApplicationFormGuid, string encryptedApiKey); + Task GetFormDataAsync(string chefsFormId, string chefsFormVersionId); + Task GetForm(Guid? formId, string chefsApplicationFormGuid, string encryptedApiKey); + Task GetSubmissionDataAsync(Guid chefsFormId, Guid submissionId); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/ISubmissionsApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/ISubmissionsApiService.cs index a90018508..0ab321867 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/ISubmissionsApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Chefs/ISubmissionsApiService.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Unity.GrantManager.Integration.Chefs +namespace Unity.GrantManager.Integrations.Chefs { public interface ISubmissionsApiService : IApplicationService { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUser.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUser.cs index 18a09ca03..b0d5260dd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUser.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUser.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public class CssUser { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUserAttributes.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUserAttributes.cs index 7b5db60b6..2659cb1e4 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUserAttributes.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/CssUserAttributes.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public class CssUserAttributes { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/ICssUsersApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/ICssUsersApiService.cs index 5f6cd46a6..d08adfe68 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/ICssUsersApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/ICssUsersApiService.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public interface ICssUsersApiService : IApplicationService { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/InvalidResponse.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/InvalidResponse.cs index f93fe7147..35c4a011d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/InvalidResponse.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/InvalidResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public class InvalidResponse { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/TokenValidationResponse.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/TokenValidationResponse.cs index dd19c4c80..c9ce345ed 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/TokenValidationResponse.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/TokenValidationResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public class TokenValidationResponse { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/UserSearchResult.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/UserSearchResult.cs index 6332f2021..2f548dfe1 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/UserSearchResult.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Css/UserSearchResult.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Unity.GrantManager.Integration.Css +namespace Unity.GrantManager.Integrations.Css { public class UserSearchResult { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/CreateUpdateDynamicUrlDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/CreateUpdateDynamicUrlDto.cs new file mode 100644 index 000000000..acc48f029 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/CreateUpdateDynamicUrlDto.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace Unity.GrantManager.Integrations +{ + public class CreateUpdateDynamicUrlDto + { + [DisplayName("Key Name")] + public string KeyName { get; set; } = string.Empty; + public string Url { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/DynamicUrlDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/DynamicUrlDto.cs new file mode 100644 index 000000000..db995751d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/DynamicUrlDto.cs @@ -0,0 +1,15 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.GrantManager.Integrations +{ + public class DynamicUrlDto : AuditedEntityDto + { + public string KeyName { get; set; } = string.Empty; + + public string Url { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + public Guid TenantId { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/IEndpointManagementAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/IEndpointManagementAppService.cs new file mode 100644 index 000000000..7c233192c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Endpoints/IEndpointManagementAppService.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Unity.GrantManager.Integrations; + + +public interface IEndpointManagementAppService : ICrudAppService< + DynamicUrlDto, + Guid, + PagedAndSortedResultRequestDto, + CreateUpdateDynamicUrlDto> + +{ + Task GetChefsApiBaseUrlAsync(); + Task GetUrlByKeyNameAsync(string keyName); + Task GetUgmUrlByKeyNameAsync(string keyName); + Task ClearCacheAsync(Guid? tenantId = null); +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/AddressDetailsDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/AddressDetailsDto.cs index 290da6b26..6bc393c59 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/AddressDetailsDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/AddressDetailsDto.cs @@ -1,4 +1,4 @@ -namespace Unity.GrantManager.Integration.Geocoder +namespace Unity.GrantManager.Integrations.Geocoder { public class AddressDetailsDto { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/IGeocoderApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/IGeocoderApiService.cs index 119fc637a..06cb36082 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/IGeocoderApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/Geocoder/IGeocoderApiService.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Unity.GrantManager.Integration.Geocoder +namespace Unity.GrantManager.Integrations.Geocoder { public interface IGeocoderApiService : IApplicationService { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/OrgBook/IOrgBookService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/OrgBook/IOrgBookService.cs index 67dac5200..602f09cba 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/OrgBook/IOrgBookService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Integration/OrgBook/IOrgBookService.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Unity.GrantManager.Integration.Orgbook +namespace Unity.GrantManager.Integrations.Orgbook { public interface IOrgBookService : IApplicationService { diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/CreateEmailDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateEmailDto.cs similarity index 99% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/CreateEmailDto.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateEmailDto.cs index 8433ffe51..b2fc69250 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/CreateEmailDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/CreateEmailDto.cs @@ -14,7 +14,6 @@ public class CreateEmailDto [Required] [MaxLength(1023)] // Max for CHES public string EmailSubject { get; set; } = string.Empty; - [Required] public string EmailBody { get; set; } = string.Empty; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/EmailDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/EmailDto.cs similarity index 100% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/EmailDto.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/EmailDto.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/Facts.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/Facts.cs similarity index 96% rename from applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/Facts.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/Facts.cs index b2a6ab0ab..6569cfe5e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/TeamsNotifications/Facts.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/Facts.cs @@ -1,13 +1,13 @@ -using System.Text.Json.Serialization; - -namespace Unity.Notifications.TeamsNotifications -{ - public class Fact - { - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - [JsonPropertyName("value")] - public string Value { get; set; } = string.Empty; - } +using System.Text.Json.Serialization; + +namespace Unity.Notifications.TeamsNotifications +{ + public class Fact + { + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; + } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/IEmailAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/IEmailAppService.cs similarity index 100% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/IEmailAppService.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/IEmailAppService.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/IEmailsService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/IEmailsService.cs similarity index 100% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/IEmailsService.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/IEmailsService.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/INotificationsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/INotificationsAppService.cs new file mode 100644 index 000000000..24b50ac1c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/INotificationsAppService.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Unity.Notifications.TeamsNotifications; + +namespace Unity.GrantManager.Notifications +{ + public interface INotificationsAppService + { + Task NotifyChefsEventToTeamsAsync(string factName, string factValue, bool alert = false); + Task PostChefsEventToTeamsAsync(string subscriptionEvent, dynamic form, dynamic chefsFormVersion); + Task PostToTeamsAsync(string activityTitle, string activitySubtitle); + Task PostToTeamsAsync(string activityTitle, string activitySubtitle, List facts); + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/UpdateEmailDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/UpdateEmailDto.cs similarity index 100% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Emails/UpdateEmailDto.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Notifications/UpdateEmailDto.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs index 920670030..0f8e35109 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Applicants/ApplicantAppService.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System; @@ -10,13 +10,13 @@ using Unity.GrantManager.GrantApplications; using Unity.GrantManager.Intakes; using Unity.GrantManager.Intakes.Mapping; -using Unity.GrantManager.Integration.Orgbook; +using Unity.Payments.Events; +using Volo.Abp; +using Unity.GrantManager.Integrations.Orgbook; using Unity.Modules.Shared.Utils; using Unity.Payments.Domain.Suppliers; -using Unity.Payments.Events; using Unity.Payments.Integrations.Cas; using Unity.Payments.Suppliers; -using Volo.Abp; using Volo.Abp.DependencyInjection; namespace Unity.GrantManager.Applicants; 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 a4d4cb7cd..5eec6d35f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormAppService.cs @@ -7,7 +7,7 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Forms; using Unity.GrantManager.GrantApplications; -using Unity.GrantManager.Integration.Chefs; +using Unity.GrantManager.Integrations.Chefs; using Unity.GrantManager.Permissions; using Unity.Payments.Permissions; using Volo.Abp; @@ -19,24 +19,24 @@ namespace Unity.GrantManager.ApplicationForms; [Authorize] -public class ApplicationFormAppService : -CrudAppService< - ApplicationForm, - ApplicationFormDto, - Guid, - PagedAndSortedResultRequestDto, - CreateUpdateApplicationFormDto>, - IApplicationFormAppService +public class ApplicationFormAppService + : CrudAppService< + ApplicationForm, + ApplicationFormDto, + Guid, + PagedAndSortedResultRequestDto, + CreateUpdateApplicationFormDto>, + IApplicationFormAppService { private readonly IStringEncryptionService _stringEncryptionService; - private readonly IFormsApiService _formsApiService; private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; private readonly IApplicationFormVersionRepository _applicationFormVersionRepository; private readonly IGrantApplicationAppService _applicationService; - private readonly IRepository _applicationFormRepository; + private readonly IFormsApiService _formsApiService; private readonly IApplicationFormSubmissionRepository _applicationFormSubmissionRepository; - public ApplicationFormAppService(IRepository repository, + public ApplicationFormAppService( + IRepository repository, IStringEncryptionService stringEncryptionService, IApplicationFormVersionAppService applicationFormVersionAppService, IApplicationFormVersionRepository applicationFormVersionRepository, @@ -47,18 +47,17 @@ public ApplicationFormAppService(IRepository repository, { _stringEncryptionService = stringEncryptionService; _applicationFormVersionAppService = applicationFormVersionAppService; - _formsApiService = formsApiService; _applicationFormVersionRepository = applicationFormVersionRepository; - _applicationFormRepository = repository; _applicationService = applicationService; _applicationFormSubmissionRepository = applicationFormSubmissionRepository; + _formsApiService = formsApiService; } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] public override async Task CreateAsync(CreateUpdateApplicationFormDto input) { input.ApiKey = _stringEncryptionService.Encrypt(input.ApiKey); - ApplicationFormDto applicationFormDto = await base.CreateAsync(input); + var applicationFormDto = await base.CreateAsync(input); return await InitializeFormVersion(applicationFormDto.Id, input); } @@ -71,46 +70,50 @@ public override async Task UpdateAsync(Guid id, CreateUpdate bool hasFormGuidChanged = existingForm.ChefsApplicationFormGuid != input.ChefsApplicationFormGuid; bool hasFormApiKeyChanged = existingForm.ApiKey != input.ApiKey; - // Only initialize form version if changes are made to form connection details if (hasFormGuidChanged || hasFormApiKeyChanged) { return await InitializeFormVersion(id, input); } - else - { - return await base.UpdateAsync(id, input); - } + + return await base.UpdateAsync(id, input); } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] private async Task InitializeFormVersion(Guid id, CreateUpdateApplicationFormDto input) { var applicationFormDto = new ApplicationFormDto(); + try { - if (input.ChefsApplicationFormGuid != null && input.ApiKey != null) + if (!string.IsNullOrWhiteSpace(input.ChefsApplicationFormGuid) && !string.IsNullOrWhiteSpace(input.ApiKey)) { - dynamic form = await _formsApiService.GetForm(Guid.Parse(input.ChefsApplicationFormGuid), input.ChefsApplicationFormGuid.ToString(), input.ApiKey); - if (form != null) + var form = await _formsApiService.GetForm( + Guid.Parse(input.ChefsApplicationFormGuid), + input.ChefsApplicationFormGuid, + input.ApiKey); + + if (form is JObject formObject) { - JObject formObject = JObject.Parse(form.ToString()); - var formName = formObject.SelectToken("name"); - if (formName != null) + var formName = formObject.SelectToken("name")?.ToString(); + if (!string.IsNullOrWhiteSpace(formName)) { - input.ApplicationFormName = formName.ToString(); + input.ApplicationFormName = formName; applicationFormDto = await base.UpdateAsync(id, input); } - bool initializePublishedOnly = false; - await _applicationFormVersionAppService.InitializePublishedFormVersion(form, id, initializePublishedOnly); + + await _applicationFormVersionAppService.InitializePublishedFormVersion(formObject, id, initializePublishedOnly: false); } } + return applicationFormDto; } catch (Exception ex) { - throw new UserFriendlyException("Exception: " + ex.Message + "\n\r Please check the CHEFS Form ID and CHEFS Form API Key"); + throw new UserFriendlyException( + "Error initializing CHEFS form. Please check the CHEFS: Form ID and API Key.", + innerException: ex + ); } - } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] @@ -132,64 +135,69 @@ public async Task GetElectoralDistrictAddressTypeAsync(Guid id) [Authorize(GrantManagerPermissions.ApplicationForms.Default)] public async Task> GetPublishedVersionsAsync(Guid id) { - IQueryable queryableFormVersions = _applicationFormVersionRepository.GetQueryableAsync().Result; - var formVersions = queryableFormVersions.Where(c => c.ApplicationFormId.Equals(id) && c.Published.Equals(true)).ToList(); - return await Task.FromResult>(ObjectMapper.Map, List>(formVersions.OrderByDescending(s => s.Version).ToList())); + var queryableFormVersions = await _applicationFormVersionRepository.GetQueryableAsync(); + var formVersions = queryableFormVersions + .Where(c => c.ApplicationFormId == id && c.Published) + .OrderByDescending(s => s.Version) + .ToList(); + + return ObjectMapper.Map, List>(formVersions); } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] public async Task> GetVersionsAsync(Guid id) { - IQueryable queryableFormVersions = _applicationFormVersionRepository.GetQueryableAsync().Result; - var formVersions = queryableFormVersions.Where(c => c.ApplicationFormId.Equals(id)).ToList(); - return await Task.FromResult>(ObjectMapper.Map, List>(formVersions.OrderByDescending(s => s.Version).ToList())); + var queryableFormVersions = await _applicationFormVersionRepository.GetQueryableAsync(); + var formVersions = queryableFormVersions + .Where(c => c.ApplicationFormId == id) + .OrderByDescending(s => s.Version) + .ToList(); + + return ObjectMapper.Map, List>(formVersions); } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] public async Task SaveApplicationFormScoresheet(FormScoresheetDto dto) { - var appForm = await _applicationFormRepository.GetAsync(dto.ApplicationFormId); + var appForm = await Repository.GetAsync(dto.ApplicationFormId); appForm.ScoresheetId = dto.ScoresheetId; - await _applicationFormRepository.UpdateAsync(appForm); + await Repository.UpdateAsync(appForm); } [Authorize(GrantManagerPermissions.ApplicationForms.Default)] public async Task PatchOtherConfig(Guid id, OtherConfigDto config) { - var form = await _applicationFormRepository.GetAsync(id); - + var form = await Repository.GetAsync(id); form.IsDirectApproval = config.IsDirectApproval; form.ElectoralDistrictAddressType = config.ElectoralDistrictAddressType; - - await _applicationFormRepository.UpdateAsync(form); + await Repository.UpdateAsync(form); } [Authorize(PaymentsPermissions.Payments.EditFormPaymentConfiguration)] public async Task GetFormPreventPaymentStatusByApplicationId(Guid applicationId) { - // Get the payment threshold for the application - GrantApplicationDto grantApplicationDto = await _applicationService.GetAsync(applicationId); - Guid formId = grantApplicationDto.ApplicationForm.Id; - ApplicationForm appForm = await _applicationFormRepository.GetAsync(formId); + var grantApplicationDto = await _applicationService.GetAsync(applicationId); + var formId = grantApplicationDto.ApplicationForm.Id; + var appForm = await Repository.GetAsync(formId); return appForm.PreventPayment; } + [Authorize(PaymentsPermissions.Payments.EditFormPaymentConfiguration)] public async Task SavePaymentConfiguration(FormPaymentConfigurationDto dto) { - ApplicationForm appForm = await _applicationFormRepository.GetAsync(dto.ApplicationFormId); + var appForm = await Repository.GetAsync(dto.ApplicationFormId); appForm.AccountCodingId = dto.AccountCodingId; appForm.Payable = dto.Payable; appForm.PreventPayment = dto.PreventPayment; appForm.PaymentApprovalThreshold = dto.PaymentApprovalThreshold; - await _applicationFormRepository.UpdateAsync(appForm); + await Repository.UpdateAsync(appForm); } - + public async Task GetFormPaymentApprovalThresholdByApplicationIdAsync(Guid applicationId) { - // Get the payment threshold for the application - GrantApplicationDto application = await _applicationService.GetAsync(applicationId); - Guid formId = application.ApplicationForm.Id; - ApplicationForm appForm = await _applicationFormRepository.GetAsync(formId); + var application = await _applicationService.GetAsync(applicationId); + var formId = application.ApplicationForm.Id; + var appForm = await Repository.GetAsync(formId); return appForm.PaymentApprovalThreshold; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormSycnronizationService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormSycnronizationService.cs index ca61e4fa1..d7b60781b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormSycnronizationService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormSycnronizationService.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using RestSharp; using RestSharp.Authenticators; @@ -14,7 +13,7 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Forms; using Unity.GrantManager.Intakes; -using Unity.GrantManager.Integration.Chefs; +using Unity.GrantManager.Integrations.Chefs; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -22,6 +21,7 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Encryption; using Volo.Abp.TenantManagement; +using Unity.GrantManager.Notifications; namespace Unity.GrantManager.ApplicationForms { @@ -41,40 +41,39 @@ public class ApplicationFormSycnronizationService : private readonly ICurrentTenant _currentTenant; private readonly IApplicationFormSubmissionRepository _applicationFormSubmissionRepository; private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; - private readonly IConfiguration _configuration; - private readonly ISubmissionsApiService _submissionsApiService; + private readonly IFormsApiService _formsApiService; private readonly IIntakeFormSubmissionManager _intakeFormSubmissionManager; + private readonly INotificationsAppService _notificationsAppService; private List _facts = new(); private readonly RestClient _intakeClient; private readonly ITenantRepository _tenantRepository; public List? applicationFormDtoList { get; set; } public HashSet FormVersionsInitializedVersionHash { get; set; } = new HashSet(); - - + public ApplicationFormSycnronizationService( + INotificationsAppService notificationsAppService, ICurrentTenant currentTenant, IRepository repository, ITenantRepository tenantRepository, RestClient restClient, - IConfiguration configuration, IStringEncryptionService stringEncryptionService, IApplicationFormRepository applicationFormRepository, IApplicationFormSubmissionRepository applicationFormSubmissionRepository, IApplicationFormVersionAppService applicationFormVersionAppService, - ISubmissionsApiService submissionsApiService, + IFormsApiService formsApiService, IIntakeFormSubmissionManager intakeFormSubmissionManager) : base(repository) { _currentTenant = currentTenant; _tenantRepository = tenantRepository; _intakeClient = restClient; - _configuration = configuration; _stringEncryptionService = stringEncryptionService; _applicationFormRepository = applicationFormRepository; - _submissionsApiService = submissionsApiService; + _formsApiService = formsApiService; _applicationFormSubmissionRepository = applicationFormSubmissionRepository; _applicationFormVersionAppService = applicationFormVersionAppService; _intakeFormSubmissionManager = intakeFormSubmissionManager; + _notificationsAppService = notificationsAppService; } private async Task SynchronizeFormSubmissions(HashSet missingSubmissions, ApplicationFormDto applicationFormDto) @@ -101,7 +100,7 @@ private async Task ProcessSingleSubmission(string submissionGuid, ApplicationFor return; } - JObject? submissionData = await _submissionsApiService.GetSubmissionDataAsync(chefsFormId, chefsSubmissionId); + JObject? submissionData = await _formsApiService.GetSubmissionDataAsync(chefsFormId, chefsSubmissionId); if (submissionData == null) { Logger.LogInformation("ApplicationFormSycnronizationService->SynchronizeFormSubmissions submissionData is null"); @@ -179,7 +178,7 @@ private async Task ProcessSubmission(ApplicationFormDto applicationFormDto, JObj HashSet missingSubmissions = new HashSet(); // Get all forms with api keys - applicationFormDtoList = (List?)await GetConnectedApplicationFormsAsync(); + applicationFormDtoList = (List?) await GetConnectedApplicationFormsAsync(); if (applicationFormDtoList != null) { @@ -237,8 +236,8 @@ private async Task ProcessSubmission(ApplicationFormDto applicationFormDto, JObj string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); string activityTitle = "Review Missed Chefs Submissions " + tenantName; string activitySubtitle = "Environment: " + envInfo; - string teamsChannel = _configuration["Notifications:TeamsNotificationsWebhook"] ?? ""; - await TeamsNotificationService.PostToTeamsAsync(teamsChannel, activityTitle, activitySubtitle, _facts); + + await _notificationsAppService.PostToTeamsAsync(activityTitle, activitySubtitle, _facts); } return (missingSubmissions ?? new HashSet(), missingSubmissionsReportBuilder.ToString()); } 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 de58359ed..dc0b776b1 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ApplicationForms/ApplicationFormVersionAppService.cs @@ -8,7 +8,7 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Forms; using Unity.GrantManager.Intakes; -using Unity.GrantManager.Integration.Chefs; +using Unity.GrantManager.Integrations.Chefs; using Unity.GrantManager.Reporting.FieldGenerators; using Unity.Modules.Shared.Features; using Volo.Abp.Application.Dtos; @@ -21,43 +21,23 @@ namespace Unity.GrantManager.ApplicationForms { - public class ApplicationFormVersionAppService : + public class ApplicationFormVersionAppService( + IRepository repository, + IIntakeFormSubmissionMapper formSubmissionMapper, + IUnitOfWorkManager unitOfWorkManager, + IFormsApiService formsApiService, + IApplicationFormVersionRepository formVersionRepository, + IApplicationFormSubmissionRepository formSubmissionRepository, + IReportingFieldsGeneratorService reportingFieldsGeneratorService, + IFeatureChecker featureChecker) : CrudAppService< ApplicationFormVersion, ApplicationFormVersionDto, Guid, PagedAndSortedResultRequestDto, - CreateUpdateApplicationFormVersionDto>, + CreateUpdateApplicationFormVersionDto>(repository), IApplicationFormVersionAppService { - private readonly IApplicationFormVersionRepository _formVersionRepository; - private readonly IIntakeFormSubmissionMapper _formSubmissionMapper; - private readonly IUnitOfWorkManager _unitOfWorkManager; - private readonly IFormsApiService _formsApiService; - private readonly IApplicationFormSubmissionRepository _formSubmissionRepository; - private readonly IReportingFieldsGeneratorService _reportingFieldsGeneratorService; - private readonly IFeatureChecker _featureChecker; - - public ApplicationFormVersionAppService( - IRepository repository, - IIntakeFormSubmissionMapper formSubmissionMapper, - IUnitOfWorkManager unitOfWorkManager, - IFormsApiService formsApiService, - IApplicationFormVersionRepository formVersionRepository, - IApplicationFormSubmissionRepository formSubmissionRepository, - IReportingFieldsGeneratorService reportingFieldsGeneratorService, - IFeatureChecker featureChecker) - : base(repository) - { - _formVersionRepository = formVersionRepository; - _formSubmissionMapper = formSubmissionMapper; - _unitOfWorkManager = unitOfWorkManager; - _formsApiService = formsApiService; - _formSubmissionRepository = formSubmissionRepository; - _reportingFieldsGeneratorService = reportingFieldsGeneratorService; - _featureChecker = featureChecker; - } - public override async Task CreateAsync(CreateUpdateApplicationFormVersionDto input) => await base.CreateAsync(input); @@ -143,8 +123,14 @@ private async Task FormVersionDoesNotExist(string formVersionId) => ChefsFormVersionGuid = formVersionId }; - var formVersion = await _formsApiService.GetFormDataAsync(formId, formVersionId); - applicationFormVersion.AvailableChefsFields = _formSubmissionMapper.InitializeAvailableFormFields(formVersion); + var formVersion = await formsApiService.GetFormDataAsync(formId, formVersionId); + if (formVersion == null) // Ensure formVersion is not null + { + Logger.LogWarning("Form version data is null for formId: {FormId}, formVersionId: {FormVersionId}", formId, formVersionId); + return null; + } + + applicationFormVersion.AvailableChefsFields = formSubmissionMapper.InitializeAvailableFormFields(formVersion); if (formVersion is JObject formVersionObject) { @@ -164,19 +150,19 @@ private async Task FormVersionDoesNotExist(string formVersionId) => private async Task InsertApplicationFormVersion(ApplicationFormVersionDto applicationFormVersionDto) { var applicationFormVersion = ObjectMapper.Map(applicationFormVersionDto); - await _formVersionRepository.InsertAsync(applicationFormVersion); + await formVersionRepository.InsertAsync(applicationFormVersion); } public async Task GetFormVersionSubmissionMapping(string chefsFormVersionId) { - var applicationFormVersion = (await _formVersionRepository.GetQueryableAsync()) + var applicationFormVersion = (await formVersionRepository.GetQueryableAsync()) .FirstOrDefault(s => s.ChefsFormVersionGuid == chefsFormVersionId); return applicationFormVersion?.SubmissionHeaderMapping; } private async Task GetApplicationFormVersion(string chefsFormVersionId) => - (await _formVersionRepository.GetQueryableAsync()) + (await formVersionRepository.GetQueryableAsync()) .FirstOrDefault(s => s.ChefsFormVersionGuid == chefsFormVersionId); public async Task FormVersionExists(string chefsFormVersionId) => @@ -184,14 +170,14 @@ public async Task FormVersionExists(string chefsFormVersionId) => private async Task UnPublishFormVersions(Guid applicationFormId, string chefsFormVersionId) { - using var uow = _unitOfWorkManager.Begin(); - var applicationFormVersion = (await _formVersionRepository.GetQueryableAsync()) + using var uow = unitOfWorkManager.Begin(); + var applicationFormVersion = (await formVersionRepository.GetQueryableAsync()) .FirstOrDefault(s => s.ChefsFormVersionGuid != chefsFormVersionId && s.ApplicationFormId == applicationFormId); if (applicationFormVersion != null) { applicationFormVersion.Published = false; - await _formVersionRepository.UpdateAsync(applicationFormVersion); + await formVersionRepository.UpdateAsync(applicationFormVersion); await uow.SaveChangesAsync(); return true; } @@ -208,10 +194,10 @@ public async Task UpdateOrCreateApplicationFormVersio var applicationFormVersion = await GetOrCreateApplicationFormVersion(chefsFormId, chefsFormVersionId, applicationFormId); await UpdateApplicationFormVersionFields(applicationFormVersion, chefsFormVersion, applicationFormId, chefsFormVersionId); - if (await _featureChecker.IsEnabledAsync(FeatureConsts.Reporting) && + if (await featureChecker.IsEnabledAsync(FeatureConsts.Reporting) && string.IsNullOrEmpty(applicationFormVersion.ReportViewName)) { - await _reportingFieldsGeneratorService.GenerateAndSetAsync(applicationFormVersion); + await reportingFieldsGeneratorService.GenerateAndSetAsync(applicationFormVersion); } return ObjectMapper.Map(applicationFormVersion); @@ -220,7 +206,7 @@ public async Task UpdateOrCreateApplicationFormVersio private async Task GetOrCreateApplicationFormVersion(string chefsFormId, string chefsFormVersionId, Guid applicationFormId) { var applicationFormVersion = await GetApplicationFormVersion(chefsFormVersionId) ?? - (await _formVersionRepository.GetQueryableAsync()) + (await formVersionRepository.GetQueryableAsync()) .FirstOrDefault(s => s.ChefsApplicationFormGuid == chefsFormId && s.ChefsFormVersionGuid == null) ?? new ApplicationFormVersion { @@ -241,7 +227,7 @@ private async Task UpdateApplicationFormVersionFields(ApplicationFormVersion app var published = ((JObject)chefsFormVersion).SelectToken("published")?.ToString(); var schema = ((JObject)chefsFormVersion).SelectToken("schema")?.ToString(); - applicationFormVersion.AvailableChefsFields = _formSubmissionMapper.InitializeAvailableFormFields(chefsFormVersion); + applicationFormVersion.AvailableChefsFields = formSubmissionMapper.InitializeAvailableFormFields(chefsFormVersion); applicationFormVersion.FormSchema = schema != null ? ChefsFormIOReplacement.ReplaceAdvancedFormIoControls(schema) ?? string.Empty : string.Empty; if (version != null) @@ -256,20 +242,20 @@ private async Task UpdateApplicationFormVersionFields(ApplicationFormVersion app } if (applicationFormVersion.Id == Guid.Empty) - await _formVersionRepository.InsertAsync(applicationFormVersion, true); + await formVersionRepository.InsertAsync(applicationFormVersion, true); else - await _formVersionRepository.UpdateAsync(applicationFormVersion, true); + await formVersionRepository.UpdateAsync(applicationFormVersion, true); } public async Task GetByChefsFormVersionId(Guid chefsFormVersionId) { - var applicationFormVersion = await _formVersionRepository.GetByChefsFormVersionAsync(chefsFormVersionId); + var applicationFormVersion = await formVersionRepository.GetByChefsFormVersionAsync(chefsFormVersionId); return applicationFormVersion == null ? null : ObjectMapper.Map(applicationFormVersion); } public async Task GetFormVersionByApplicationIdAsync(Guid applicationId) { - var formSubmission = await _formSubmissionRepository.GetByApplicationAsync(applicationId); + var formSubmission = await formSubmissionRepository.GetByApplicationAsync(applicationId); if (formSubmission.FormVersionId == null) { @@ -300,7 +286,7 @@ private async Task HandleEmptyFormVersionIdAsync(ApplicationFormSubmission var formVersionId = Guid.Parse(formVersionIdString); formSubmission.FormVersionId = formVersionId; - await _formSubmissionRepository.UpdateAsync(formSubmission); + await formSubmissionRepository.UpdateAsync(formSubmission); return await GetVersion(formVersionId); } catch @@ -311,17 +297,17 @@ private async Task HandleEmptyFormVersionIdAsync(ApplicationFormSubmission public async Task DeleteWorkSheetMappingByFormName(string formName, Guid formVersionId) { - var applicationFormVersion = await _formVersionRepository.GetAsync(formVersionId); + var applicationFormVersion = await formVersionRepository.GetAsync(formVersionId); if (applicationFormVersion?.SubmissionHeaderMapping == null) return; var pattern = $"(,\\s*\\\"{formName}.*\\\")|(\\\"{formName}.*\\\",)"; applicationFormVersion.SubmissionHeaderMapping = Regex.Replace(applicationFormVersion.SubmissionHeaderMapping, pattern, "", RegexOptions.None, TimeSpan.FromSeconds(30)); - await _formVersionRepository.UpdateAsync(applicationFormVersion); + await formVersionRepository.UpdateAsync(applicationFormVersion); } private async Task GetVersion(Guid formVersionId) { - var formVersion = await _formVersionRepository.GetByChefsFormVersionAsync(formVersionId); + var formVersion = await formVersionRepository.GetByChefsFormVersionAsync(formVersionId); return formVersion?.Version ?? 0; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ConfigureIntakeClientOptions.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ConfigureIntakeClientOptions.cs new file mode 100644 index 000000000..ddaecd402 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/ConfigureIntakeClientOptions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Unity.GrantManager.Intake; +using Unity.GrantManager.Integrations; +using System.Threading; +using System.Threading.Tasks; + +namespace Unity.GrantManager; + +public class ConfigureIntakeClientOptions( + IConfiguration configuration, + IEndpointManagementAppService endpointManagementAppService) +{ + public async Task ConfigureAsync(IntakeClientOptions options, CancellationToken cancellationToken = default) + { + var intakeBaseUri = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.INTAKE_API_BASE); + options.BaseUri = intakeBaseUri; + options.BearerTokenPlaceholder = configuration["Intake:BearerTokenPlaceholder"] ?? ""; + options.UseBearerToken = configuration.GetValue("Intake:UseBearerToken"); + options.AllowUnregisteredVersions = configuration.GetValue("Intake:AllowUnregisteredVersions"); + } +} + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ChefsEventSubscriptionService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ChefsEventSubscriptionService.cs index 033aa56a9..67acb7853 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ChefsEventSubscriptionService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Events/ChefsEventSubscriptionService.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using System; using System.Linq; using System.Threading.Tasks; @@ -6,53 +5,46 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Exceptions; using Unity.GrantManager.Intakes; -using Unity.GrantManager.Integration.Chefs; -using Unity.Notifications.TeamsNotifications; +using Unity.GrantManager.Integrations.Chefs; +using Unity.GrantManager.Notifications; using Volo.Abp; using Volo.Abp.Domain.Entities; namespace Unity.GrantManager.Events { [RemoteService(false)] - public class ChefsEventSubscriptionService : GrantManagerAppService, IChefsEventSubscriptionService - { - private readonly IApplicationFormRepository _applicationFormRepository; - private readonly IApplicationFormManager _applicationFormManager; - private readonly IIntakeFormSubmissionMapper _intakeFormSubmissionMapper; - private readonly ISubmissionsApiService _submissionsIntService; - private readonly IFormsApiService _formsApiService; - private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; - private readonly IConfiguration _configuration; - - public ChefsEventSubscriptionService( - IConfiguration configuration, + public class ChefsEventSubscriptionService( + INotificationsAppService notificationsAppService, IIntakeFormSubmissionMapper intakeFormSubmissionMapper, IApplicationFormManager applicationFormManager, - ISubmissionsApiService submissionsIntService, + IFormsApiService submissionsIntService, IApplicationFormRepository applicationFormRepository, IFormsApiService formsApiService, - IApplicationFormVersionAppService applicationFormVersionAppService) - { - _configuration = configuration; - _intakeFormSubmissionMapper = intakeFormSubmissionMapper; - _submissionsIntService = submissionsIntService; - _applicationFormRepository = applicationFormRepository; - _formsApiService = formsApiService; - _applicationFormManager = applicationFormManager; - _applicationFormVersionAppService = applicationFormVersionAppService; - } + IApplicationFormVersionAppService applicationFormVersionAppService) : GrantManagerAppService, IChefsEventSubscriptionService + { public async Task CreateIntakeMappingAsync(EventSubscriptionDto eventSubscriptionDto) { - var applicationForm = (await _applicationFormRepository + _ = (await applicationFormRepository .GetQueryableAsync()) .Where(s => s.ChefsApplicationFormGuid == eventSubscriptionDto.FormId.ToString()) .OrderBy(s => s.CreationTime) .FirstOrDefault() ?? throw new EntityNotFoundException("Application Form Not Registered"); + + var submissionData = await submissionsIntService.GetSubmissionDataAsync(eventSubscriptionDto.FormId, eventSubscriptionDto.SubmissionId) + ?? throw new InvalidFormDataSubmissionException(); - var submissionData = await _submissionsIntService.GetSubmissionDataAsync(eventSubscriptionDto.FormId, eventSubscriptionDto.SubmissionId) ?? throw new InvalidFormDataSubmissionException(); - var formVersion = await _formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), submissionData.submission.formVersionId.ToString()) ?? throw new InvalidFormDataSubmissionException(); - var result = _intakeFormSubmissionMapper.InitializeAvailableFormFields(formVersion); + // Access the 'submission' property from the JObject using the appropriate key. + var submission = submissionData["submission"] + ?? throw new InvalidFormDataSubmissionException(); + + // Ensure the 'formVersionId' is accessed correctly from the 'submission' object. + var formVersionId = submission["formVersionId"]?.ToString() + ?? throw new InvalidFormDataSubmissionException(); + + var formVersion = await formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), formVersionId) + ?? throw new InvalidFormDataSubmissionException(); + var result = intakeFormSubmissionMapper.InitializeAvailableFormFields(formVersion); return !result.IsNullOrEmpty(); } @@ -61,7 +53,7 @@ public async Task PublishedFormAsync(EventSubscriptionDto eventSubscriptio string formId = eventSubscriptionDto.FormId.ToString(); string formVersionId = eventSubscriptionDto.FormVersion.ToString(); - var applicationForm = (await _applicationFormRepository + var applicationForm = (await applicationFormRepository .GetQueryableAsync()) .Where(s => s.ChefsApplicationFormGuid == formId) .OrderBy(s => s.CreationTime) @@ -72,18 +64,27 @@ public async Task PublishedFormAsync(EventSubscriptionDto eventSubscriptio && applicationForm.ChefsApplicationFormGuid != null) { // Go grab the new name/description/version and map the new available fields - var formVersion = await _formsApiService.GetFormDataAsync(formId, formVersionId); - dynamic form = await _formsApiService.GetForm(Guid.Parse(applicationForm.ChefsApplicationFormGuid), applicationForm.ChefsApplicationFormGuid.ToString(), applicationForm.ApiKey); - applicationForm = _applicationFormManager.SynchronizePublishedForm(applicationForm, formVersion, form); - await _applicationFormVersionAppService.UpdateOrCreateApplicationFormVersion(formId, formVersionId, applicationForm.Id, formVersion); - applicationForm = await _applicationFormRepository.UpdateAsync(applicationForm); - string teamsChannel = _configuration["Notifications:TeamsNotificationsWebhook"] ?? ""; - TeamsNotificationService.PostChefsEventToTeamsAsync(teamsChannel, eventSubscriptionDto.SubscriptionEvent, form, formVersion); + var formVersion = await formsApiService.GetFormDataAsync(formId, formVersionId); + if (formVersion == null) + { + throw new InvalidFormDataSubmissionException("Form version data is null."); + } + + dynamic form = await formsApiService.GetForm(Guid.Parse(applicationForm.ChefsApplicationFormGuid), applicationForm.ChefsApplicationFormGuid.ToString(), applicationForm.ApiKey); + if (form == null) + { + throw new InvalidFormDataSubmissionException("Form data is null."); + } + + applicationForm = applicationFormManager.SynchronizePublishedForm(applicationForm, formVersion, form); + await applicationFormVersionAppService.UpdateOrCreateApplicationFormVersion(formId, formVersionId, applicationForm.Id, formVersion); + applicationForm = await applicationFormRepository.UpdateAsync(applicationForm); + notificationsAppService.PostChefsEventToTeamsAsync(eventSubscriptionDto.SubscriptionEvent, form, formVersion); } - else if(applicationForm == null) + else if (applicationForm == null) { EventSubscription eventSubscription = ObjectMapper.Map(eventSubscriptionDto); - applicationForm = await _applicationFormManager.InitializeApplicationForm(eventSubscription); + applicationForm = await applicationFormManager.InitializeApplicationForm(eventSubscription); } return applicationForm != null; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationLinksAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationLinksAppService.cs index c3ad567cb..98e25b47e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationLinksAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/ApplicationLinksAppService.cs @@ -1,9 +1,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Unity.GrantManager; using Unity.GrantManager.Applications; using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; @@ -21,6 +23,7 @@ public class ApplicationLinksAppService : CrudAppService< public IApplicationLinksRepository ApplicationLinksRepository { get; set; } = null!; public IApplicationRepository ApplicationRepository { get; set; } = null!; public IApplicationFormRepository ApplicationFormRepository { get; set; } = null!; + public IApplicantRepository ApplicantRepository { get; set; } = null!; // Constructor for the required repository public ApplicationLinksAppService(IRepository repository) : base(repository) { } @@ -30,12 +33,15 @@ public async Task> GetListByApplicationAsync(Guid var applicationLinksQuery = await ApplicationLinksRepository.GetQueryableAsync(); var applicationsQuery = await ApplicationRepository.GetQueryableAsync(); var applicationFormsQuery = await ApplicationFormRepository.GetQueryableAsync(); + var applicantsQuery = await ApplicantRepository.GetQueryableAsync(); var combinedQuery = from applicationLinks in applicationLinksQuery join application in applicationsQuery on applicationLinks.LinkedApplicationId equals application.Id into appLinks from application in appLinks.DefaultIfEmpty() // Left join for safety join appForm in applicationFormsQuery on application.ApplicationFormId equals appForm.Id into appForms from appForm in appForms.DefaultIfEmpty() // Left join for safety + join applicant in applicantsQuery on application.ApplicantId equals applicant.Id into applicants + from applicant in applicants.DefaultIfEmpty() // Left join for safety where applicationLinks.ApplicationId == applicationId || applicationLinks.LinkedApplicationId == applicationId select new ApplicationLinksInfoDto { @@ -43,8 +49,10 @@ join appForm in applicationFormsQuery on application.ApplicationFormId equals ap ApplicationId = application.Id, ApplicationStatus = application.ApplicationStatus.InternalStatus, ReferenceNumber = application.ReferenceNo, - Category = appForm.Category ?? "Unknown", // Handle potential nulls - ProjectName = application.ProjectName + Category = appForm.Category ?? GrantManagerConsts.UnknownValue, // Handle potential nulls + ProjectName = application.ProjectName, + ApplicantName = applicant.ApplicantName ?? GrantManagerConsts.UnknownValue, // Handle potential nulls + LinkType = applicationLinks.LinkType }; return await combinedQuery.ToListAsync(); @@ -55,12 +63,15 @@ public async Task GetLinkedApplicationAsync(Guid curren var applicationLinksQuery = await ApplicationLinksRepository.GetQueryableAsync(); var applicationsQuery = await ApplicationRepository.GetQueryableAsync(); var applicationFormsQuery = await ApplicationFormRepository.GetQueryableAsync(); + var applicantsQuery = await ApplicantRepository.GetQueryableAsync(); var combinedQuery = from applicationLinks in applicationLinksQuery join application in applicationsQuery on applicationLinks.LinkedApplicationId equals application.Id into appLinks from application in appLinks.DefaultIfEmpty() // Left join for safety join appForm in applicationFormsQuery on application.ApplicationFormId equals appForm.Id into appForms from appForm in appForms.DefaultIfEmpty() // Left join for safety + join applicant in applicantsQuery on application.ApplicantId equals applicant.Id into applicants + from applicant in applicants.DefaultIfEmpty() // Left join for safety where applicationLinks.ApplicationId == linkedApplicationId && applicationLinks.LinkedApplicationId == currentApplicationId select new ApplicationLinksInfoDto { @@ -68,10 +79,280 @@ join appForm in applicationFormsQuery on application.ApplicationFormId equals ap ApplicationId = application.Id, ApplicationStatus = application.ApplicationStatus.InternalStatus, ReferenceNumber = application.ReferenceNo, - Category = appForm.Category ?? "Unknown", // Handle potential nulls - ProjectName = application.ProjectName + Category = appForm.Category ?? GrantManagerConsts.UnknownValue, // Handle potential nulls + ProjectName = application.ProjectName, + ApplicantName = applicant.ApplicantName ?? GrantManagerConsts.UnknownValue, // Handle potential nulls + LinkType = applicationLinks.LinkType }; return await combinedQuery.SingleAsync(); } + + public async Task GetCurrentApplicationInfoAsync(Guid applicationId) + { + Logger.LogInformation("GetCurrentApplicationInfoAsync called with applicationId: {ApplicationId}", applicationId); + + try + { + var applicationsQuery = await ApplicationRepository.GetQueryableAsync(); + var application = await applicationsQuery + .Include(a => a.ApplicationStatus) + .Where(a => a.Id == applicationId) + .FirstOrDefaultAsync(); + + if (application == null) + { + Logger.LogWarning("Application not found with ID: {ApplicationId}", applicationId); + return new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = applicationId, + ApplicationStatus = "Not Found", + ReferenceNumber = GrantManagerConsts.UnknownValue, + Category = GrantManagerConsts.UnknownValue, + ProjectName = GrantManagerConsts.UnknownValue, + ApplicantName = GrantManagerConsts.UnknownValue, + LinkType = ApplicationLinkType.Related + }; + } + + + // Now try to get related data safely + string category = GrantManagerConsts.UnknownValue; + string applicantName = GrantManagerConsts.UnknownValue; + + try + { + var applicationFormsQuery = await ApplicationFormRepository.GetQueryableAsync(); + var applicationForm = await applicationFormsQuery + .Where(af => af.Id == application.ApplicationFormId) + .FirstOrDefaultAsync(); + if (applicationForm != null) + { + category = applicationForm.Category ?? GrantManagerConsts.UnknownValue; + } + else + { + Logger.LogWarning("Application form not found with ID: {ApplicationFormId}", application.ApplicationFormId); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error looking up application form with ID: {ApplicationFormId}", application.ApplicationFormId); + } + + try + { + var applicantsQuery = await ApplicantRepository.GetQueryableAsync(); + var applicant = await applicantsQuery + .Where(ap => ap.Id == application.ApplicantId) + .FirstOrDefaultAsync(); + if (applicant != null) + { + applicantName = applicant.ApplicantName ?? GrantManagerConsts.UnknownValue; + } + else + { + Logger.LogWarning("Applicant not found with ID: {ApplicantId}", application.ApplicantId); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error looking up applicant with ID: {ApplicantId}", application.ApplicantId); + } + + // Get application status (loaded via Include) + string applicationStatus; + try + { + if (application.ApplicationStatus != null) + { + applicationStatus = application.ApplicationStatus.InternalStatus ?? GrantManagerConsts.UnknownValue; + } + else + { + Logger.LogWarning("ApplicationStatus is null for application {ApplicationId}", applicationId); + applicationStatus = "Status Not Available"; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error accessing ApplicationStatus for application {ApplicationId}", applicationId); + applicationStatus = "Status Unavailable"; + } + + var result = new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = application.Id, + ApplicationStatus = applicationStatus, + ReferenceNumber = application.ReferenceNo ?? GrantManagerConsts.UnknownValue, + Category = category, + ProjectName = application.ProjectName ?? GrantManagerConsts.UnknownValue, + ApplicantName = applicantName, + LinkType = ApplicationLinkType.Related + }; + + return result; + } + catch (Exception ex) + { + Logger.LogError(ex, "Critical error in GetCurrentApplicationInfoAsync for applicationId: {ApplicationId}", applicationId); + + // If all else fails, return a basic structure + return new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = applicationId, + ApplicationStatus = "Error Loading", + ReferenceNumber = GrantManagerConsts.UnknownValue, + Category = GrantManagerConsts.UnknownValue, + ProjectName = GrantManagerConsts.UnknownValue, + ApplicantName = GrantManagerConsts.UnknownValue, + LinkType = ApplicationLinkType.Related + }; + } + } + + public async Task DeleteWithPairAsync(Guid applicationLinkId) + { + // Get the link to find the paired record + var link = await Repository.GetAsync(applicationLinkId); + + // Find the paired link (reverse direction) + var applicationLinksQuery = await ApplicationLinksRepository.GetQueryableAsync(); + var pairedLink = await applicationLinksQuery + .Where(x => x.ApplicationId == link.LinkedApplicationId && x.LinkedApplicationId == link.ApplicationId) + .FirstOrDefaultAsync(); + + // Delete both links + await Repository.DeleteAsync(applicationLinkId); + + if (pairedLink != null) + { + await Repository.DeleteAsync(pairedLink.Id); + } + } + + public async Task GetApplicationDetailsByReferenceAsync(string referenceNumber) + { + Logger.LogInformation("GetApplicationDetailsByReferenceAsync called with referenceNumber: {ReferenceNumber}", referenceNumber); + + try + { + var applicationsQuery = await ApplicationRepository.GetQueryableAsync(); + var application = await applicationsQuery + .Include(a => a.ApplicationStatus) + .Where(a => a.ReferenceNo == referenceNumber) + .FirstOrDefaultAsync(); + + if (application == null) + { + Logger.LogWarning("Application not found with ReferenceNumber: {ReferenceNumber}", referenceNumber); + return new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = Guid.Empty, + ApplicationStatus = "Not Found", + ReferenceNumber = referenceNumber, + Category = GrantManagerConsts.UnknownValue, + ProjectName = GrantManagerConsts.UnknownValue, + ApplicantName = GrantManagerConsts.UnknownValue, + LinkType = ApplicationLinkType.Related + }; + } + + // Get related data safely + string category = GrantManagerConsts.UnknownValue; + string applicantName = GrantManagerConsts.UnknownValue; + + try + { + var applicationFormsQuery = await ApplicationFormRepository.GetQueryableAsync(); + var applicationForm = await applicationFormsQuery + .Where(af => af.Id == application.ApplicationFormId) + .FirstOrDefaultAsync(); + if (applicationForm != null) + { + category = applicationForm.Category ?? GrantManagerConsts.UnknownValue; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error looking up application form"); + } + + try + { + var applicantsQuery = await ApplicantRepository.GetQueryableAsync(); + var applicant = await applicantsQuery + .Where(ap => ap.Id == application.ApplicantId) + .FirstOrDefaultAsync(); + if (applicant != null) + { + applicantName = applicant.ApplicantName ?? GrantManagerConsts.UnknownValue; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error looking up applicant"); + } + + string applicationStatus = GrantManagerConsts.UnknownValue; + if (application.ApplicationStatus != null) + { + applicationStatus = application.ApplicationStatus.InternalStatus ?? GrantManagerConsts.UnknownValue; + } + + return new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = application.Id, + ApplicationStatus = applicationStatus, + ReferenceNumber = application.ReferenceNo ?? referenceNumber, + Category = category, + ProjectName = application.ProjectName ?? GrantManagerConsts.UnknownValue, + ApplicantName = applicantName, + LinkType = ApplicationLinkType.Related + }; + } + catch (Exception ex) + { + Logger.LogError(ex, "Critical error in GetApplicationDetailsByReferenceAsync for referenceNumber: {ReferenceNumber}", referenceNumber); + + return new ApplicationLinksInfoDto + { + Id = Guid.Empty, + ApplicationId = Guid.Empty, + ApplicationStatus = "Error Loading", + ReferenceNumber = referenceNumber, + Category = GrantManagerConsts.UnknownValue, + ProjectName = GrantManagerConsts.UnknownValue, + ApplicantName = GrantManagerConsts.UnknownValue, + LinkType = ApplicationLinkType.Related + }; + } + } + + public async Task UpdateLinkTypeAsync(Guid applicationLinkId, ApplicationLinkType newLinkType) + { + Logger.LogInformation("UpdateLinkTypeAsync called with linkId: {LinkId}, newLinkType: {LinkType}", applicationLinkId, newLinkType); + + // Get the existing link + var link = await Repository.GetAsync(applicationLinkId); + + if (link != null) + { + // Update the link type + link.LinkType = newLinkType; + await Repository.UpdateAsync(link); + + Logger.LogInformation("Successfully updated link type for linkId: {LinkId}", applicationLinkId); + } + else + { + Logger.LogWarning("Link not found with ID: {LinkId}", applicationLinkId); + } + + } } \ No newline at end of file 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 b73c4b0fd..cb9b4ce34 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantApplications/GrantApplicationAppService.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Unity.GrantManager; using Unity.Flex.WorksheetInstances; using Unity.Flex.Worksheets; using Unity.GrantManager.Applicants; @@ -1277,12 +1278,18 @@ await _localEventBus.PublishAsync( public async Task> GetAllApplicationsAsync() { - var query = from applications in await _applicationRepository.GetQueryableAsync() + var applicationsQuery = await _applicationRepository.GetQueryableAsync(); + var applicantsQuery = await _applicantRepository.GetQueryableAsync(); + + var query = from applications in applicationsQuery + join applicant in applicantsQuery on applications.ApplicantId equals applicant.Id into applicantGroup + from applicant in applicantGroup.DefaultIfEmpty() select new GrantApplicationLiteDto { Id = applications.Id, ProjectName = applications.ProjectName, - ReferenceNo = applications.ReferenceNo + ReferenceNo = applications.ReferenceNo, + ApplicantName = applicant != null ? (applicant.ApplicantName ?? GrantManagerConsts.UnknownValue) : GrantManagerConsts.UnknownValue }; return await query.ToListAsync(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs index 1ad3ec85a..15866c572 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationAutoMapperProfile.cs @@ -11,6 +11,7 @@ using Unity.GrantManager.GrantApplications; using Unity.GrantManager.Identity; using Unity.GrantManager.Intakes; +using Unity.GrantManager.Integrations; using Unity.GrantManager.Locality; using Unity.GrantManager.Zones; using Unity.Payments.Domain.AccountCodings; @@ -25,7 +26,12 @@ public GrantManagerApplicationAutoMapperProfile() /* You can configure your AutoMapper mapping configuration here. * Alternatively, you can split your mapping configurations * into multiple profile classes for a better organization. */ - + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); CreateMap(); CreateMap() diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationModule.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationModule.cs index 06ddfc9be..843b04557 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationModule.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/GrantManagerApplicationModule.cs @@ -19,7 +19,6 @@ using Volo.Abp.PermissionManagement; using Volo.Abp.SettingManagement; using Volo.Abp.BackgroundWorkers.Quartz; -using Unity.GrantManager.GrantApplications; using Volo.Abp.Application.Dtos; using Unity.Notifications; using Unity.Notifications.Integrations.Ches; @@ -38,6 +37,9 @@ using Unity.GrantManager.Infrastructure; using Medallion.Threading; using Unity.GrantManager.Locks; +using JsonSerializerOptions = System.Text.Json.JsonSerializerOptions; +using Unity.GrantManager.Integrations.Chefs; +using Unity.Modules.Shared.Http; namespace Unity.GrantManager; @@ -121,21 +123,12 @@ public override void ConfigureServices(ServiceConfigurationContext context) }); context.Services.AddSingleton(); - context.Services.AddSingleton(); - - Configure(options => - { - // This fails unit tests unless set to a non empty string - // RestClient will throw an error - baseUrl can not be empty - options.BaseUri = configuration["Intake:BaseUri"] ?? "https://submit.digital.gov.bc.ca/app/api/v1"; - options.BearerTokenPlaceholder = configuration["Intake:BearerTokenPlaceholder"] ?? ""; - options.UseBearerToken = configuration.GetValue("Intake:UseBearerToken"); - options.AllowUnregisteredVersions = configuration.GetValue("Intake:AllowUnregisteredVersions"); - }); - - context.Services.Configure(configuration.GetSection(key: "Payments")); - context.Services.Configure(configuration.GetSection(key: "CssApi")); - context.Services.Configure(configuration.GetSection(key: "Notifications")); + context.Services.AddTransient(); + context.Services.AddTransient(); + + context.Services.Configure(configuration.GetSection("Payments")); + context.Services.Configure(configuration.GetSection("CssApi")); + context.Services.Configure(configuration.GetSection("Notifications")); ConfigureBackgroundServices(configuration); @@ -152,60 +145,45 @@ public override void ConfigureServices(ServiceConfigurationContext context) context.Services.ConfigureRabbitMQ(); context.Services.AddScoped(); - _ = context.Services.AddSingleton(provider => + context.Services.AddSingleton(provider => { - var options = provider.GetService>()?.Value; - if (null != options) + var options = (provider.GetService>()?.Value) ?? throw new InvalidOperationException("IntakeClientOptions not configured."); + if (options.BaseUri == string.Empty) { - var restOptions = new RestClientOptions(options.BaseUri) - { - // NOTE: Basic authentication only works for fetching forms and lists of form submissions - // Authenticator = options.UseBearerToken ? - // new JwtAuthenticator(options.BearerTokenPlaceholder) : - // new HttpBasicAuthenticator(options.FormId, options.ApiKey), + options.BaseUri = "https://submit.digital.gov.bc.ca/app/api/v1"; + } + var restOptions = options != null + ? new RestClientOptions(options.BaseUri) + { FailOnDeserializationError = true, ThrowOnDeserializationError = true - }; + } + : new RestClientOptions(); - return new RestClient( - restOptions, - configureSerialization: s => - s.UseSystemTextJson(new System.Text.Json.JsonSerializerOptions - { - WriteIndented = true, - PropertyNameCaseInsensitive = true, - ReadCommentHandling = JsonCommentHandling.Skip, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }) - ); - } - else - { - return new RestClient( - configureSerialization: s => - s.UseSystemTextJson(new System.Text.Json.JsonSerializerOptions - { - WriteIndented = true, - PropertyNameCaseInsensitive = true, - ReadCommentHandling = JsonCommentHandling.Skip, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }) - ); - } + return new RestClient( + restOptions, + configureSerialization: s => s.UseSystemTextJson(new JsonSerializerOptions + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }) + ); }); - // Set the max defaults as max - we are using non serverside paging and this effect this + // Max paging limits ExtensibleLimitedResultRequestDto.DefaultMaxResultCount = int.MaxValue; ExtensibleLimitedResultRequestDto.MaxMaxResultCount = int.MaxValue; - LimitedResultRequestDto.DefaultMaxResultCount = int.MaxValue; LimitedResultRequestDto.MaxMaxResultCount = int.MaxValue; } private void ConfigureBackgroundServices(IConfiguration configuration) { - if (!Convert.ToBoolean(configuration["BackgroundJobs:IsJobExecutionEnabled"])) return; + if (!Convert.ToBoolean(configuration["BackgroundJobs:IsJobExecutionEnabled"])) + return; Configure(options => { @@ -217,13 +195,10 @@ private void ConfigureBackgroundServices(IConfiguration configuration) options.IsAutoRegisterEnabled = configuration.GetValue("BackgroundJobs:Quartz:IsAutoRegisterEnabled"); }); - /* - * There are Global Retry Options that can be configured, configure if required, or if handled per job - */ Configure(options => { options.IsJobExecutionEnabled = configuration.GetValue("BackgroundJobs:IsJobExecutionEnabled"); options.Quartz.IsAutoRegisterEnabled = configuration.GetValue("BackgroundJobs:Quartz:IsAutoRegisterEnabled"); }); } -} \ No newline at end of file +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Identity/UserImportAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Identity/UserImportAppService.cs index 5c9336fe5..dbf527204 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Identity/UserImportAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Identity/UserImportAppService.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; -using Unity.GrantManager.Integration.Css; +using Unity.GrantManager.Integrations.Css; using Volo.Abp; using Volo.Abp.Data; using Volo.Abp.Identity; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/ApplicationIntakeAdminService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/ApplicationIntakeAdminService.cs index 4d820df70..81a212f1b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/ApplicationIntakeAdminService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/ApplicationIntakeAdminService.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.Applications; -using Unity.GrantManager.Integration.Chefs; +using Unity.GrantManager.Integrations.Chefs; using Volo.Abp.Domain.Entities; namespace Unity.GrantManager.Intakes diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/DetermineElectoralDistrictHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/DetermineElectoralDistrictHandler.cs index 392216c7a..951b781d6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/DetermineElectoralDistrictHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/Handlers/DetermineElectoralDistrictHandler.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Unity.GrantManager.Intakes.Events; -using Unity.GrantManager.Integration.Geocoder; +using Unity.GrantManager.Integrations.Geocoder; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/IntakeSubmissionAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/IntakeSubmissionAppService.cs index a19e4db14..aaa3f5a84 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/IntakeSubmissionAppService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Intakes/IntakeSubmissionAppService.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using System; using System.Linq; using System.Threading.Tasks; @@ -8,50 +6,30 @@ using Unity.GrantManager.Applications; using Unity.GrantManager.Events; using Unity.GrantManager.Exceptions; -using Unity.GrantManager.Intake; -using Unity.GrantManager.Integration.Chefs; -using Unity.Notifications.TeamsNotifications; +using Unity.GrantManager.Integrations.Chefs; +using Unity.GrantManager.Notifications; + using Volo.Abp; namespace Unity.GrantManager.Intakes { [RemoteService(false)] - public class IntakeSubmissionAppService : GrantManagerAppService, IIntakeSubmissionAppService + public class IntakeSubmissionAppService(INotificationsAppService notificationsAppService, + IIntakeFormSubmissionManager intakeFormSubmissionManager, + IFormsApiService formsApiService, + IApplicationFormRepository applicationFormRepository, + IApplicationFormVersionAppService applicationFormVersionAppService) : GrantManagerAppService, IIntakeSubmissionAppService { - private readonly IApplicationFormRepository _applicationFormRepository; - private readonly IIntakeFormSubmissionManager _intakeFormSubmissionManager; - private readonly IFormsApiService _formsApiService; - private readonly ISubmissionsApiService _submissionsIntService; - private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; - private readonly IOptions _intakeClientOptions; - private readonly IConfiguration _configuration; - - public IntakeSubmissionAppService(IIntakeFormSubmissionManager intakeFormSubmissionManager, - IFormsApiService formsApiService, - ISubmissionsApiService submissionsIntService, - IApplicationFormRepository applicationFormRepository, - IApplicationFormVersionAppService applicationFormVersionAppService, - IOptions intakeClientOptions, - IConfiguration configuration) - { - _intakeFormSubmissionManager = intakeFormSubmissionManager; - _submissionsIntService = submissionsIntService; - _applicationFormRepository = applicationFormRepository; - _formsApiService = formsApiService; - _applicationFormVersionAppService = applicationFormVersionAppService; - _intakeClientOptions = intakeClientOptions; - _configuration = configuration; - } public async Task CreateIntakeSubmissionAsync(EventSubscriptionDto eventSubscriptionDto) { - var applicationForm = (await _applicationFormRepository + var applicationForm = (await applicationFormRepository .GetQueryableAsync()) .Where(s => s.ChefsApplicationFormGuid == eventSubscriptionDto.FormId.ToString()) .OrderBy(s => s.CreationTime) .FirstOrDefault() ?? throw new ApplicationFormSetupException("Application Form Not Registered"); - JObject submissionData = await _submissionsIntService.GetSubmissionDataAsync(eventSubscriptionDto.FormId, eventSubscriptionDto.SubmissionId) ?? throw new InvalidFormDataSubmissionException(); + JObject submissionData = await formsApiService.GetSubmissionDataAsync(eventSubscriptionDto.FormId, eventSubscriptionDto.SubmissionId) ?? throw new InvalidFormDataSubmissionException(); bool validSubmission = await ValidateSubmission(eventSubscriptionDto, submissionData); if (!validSubmission) { @@ -64,7 +42,7 @@ public async Task CreateIntakeSubmissionAsync( await StoreChefsFieldMappingAsync(eventSubscriptionDto, applicationForm, token); } - var result = await _intakeFormSubmissionManager.ProcessFormSubmissionAsync(applicationForm, submissionData); + var result = await intakeFormSubmissionManager.ProcessFormSubmissionAsync(applicationForm, submissionData); return new EventSubscriptionConfirmationDto() { ConfirmationId = result }; } @@ -75,7 +53,7 @@ private async Task ValidateSubmission(EventSubscriptionDto eventSubscripti if (tokenDraft != null && tokenDraft.ToString() == "True") { string factName = "A draft submission was submitted and should not have been"; string factValue = $"FormId: {eventSubscriptionDto.FormId} SubmissionID: {eventSubscriptionDto.SubmissionId}"; - await SendTeamsNotification(factName, factValue); + await notificationsAppService.NotifyChefsEventToTeamsAsync(factName, factValue, true); return false; } @@ -83,54 +61,42 @@ private async Task ValidateSubmission(EventSubscriptionDto eventSubscripti if (tokenDeleted != null && tokenDeleted.ToString() == "True") { string factName = "A deleted submission was submitted - user navigated back and got a success message from chefs"; string factValue = $"FormId: {eventSubscriptionDto.FormId} SubmissionID: {eventSubscriptionDto.SubmissionId}"; - await SendTeamsNotification(factName, factValue); + await notificationsAppService.NotifyChefsEventToTeamsAsync(factName, factValue, false); } // If there are no mappings initialize the available - bool formVersionExists = await _applicationFormVersionAppService.FormVersionExists(eventSubscriptionDto.FormVersion.ToString()); + bool formVersionExists = await applicationFormVersionAppService.FormVersionExists(eventSubscriptionDto.FormVersion.ToString()); if (!formVersionExists) { - dynamic? formVersion = await _formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), + dynamic? formVersion = await formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), eventSubscriptionDto.FormVersion.ToString()); if(formVersion == null) { string factName = "Application Form Version Not Registered - Unknown Version"; string factValue = $"FormId: {eventSubscriptionDto.FormId} FormVersion: {eventSubscriptionDto.FormVersion}"; - await SendTeamsNotification(factName, factValue); + await notificationsAppService.NotifyChefsEventToTeamsAsync(factName, factValue, true); return false; - } else if(!_intakeClientOptions.Value.AllowUnregisteredVersions) - { + } else { var version = ((JObject)formVersion!).SelectToken("version"); var published = ((JObject)formVersion!).SelectToken("published"); string factName = "Application Form Version Not Registered - Unknown Version"; string factValue = $"Application Form Version Not Registerd - Version: {version} Published: {published}"; - await SendTeamsNotification(factName, factValue); + await notificationsAppService.NotifyChefsEventToTeamsAsync(factName, factValue, true); return false; } } return true; } - private async Task SendTeamsNotification(string factName, string factValue) - { - string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - string activityTitle = "Chefs Submission Event Validation Error"; - string activitySubtitle = "Environment: " + envInfo; - string teamsChannel = _configuration["Notifications:TeamsNotificationsWebhook"] ?? ""; - TeamsNotificationService teamsNotificationService = new(); - teamsNotificationService.AddFact(factName, factValue); - await teamsNotificationService.PostFactsToTeamsAsync(teamsChannel, activityTitle, activitySubtitle); - } - private async Task StoreChefsFieldMappingAsync(EventSubscriptionDto eventSubscriptionDto, ApplicationForm applicationForm, JToken token) { Guid formVersionId = Guid.Parse(token.ToString()); - var formData = await _formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), formVersionId.ToString()) ?? throw new InvalidFormDataSubmissionException(); + var formData = await formsApiService.GetFormDataAsync(eventSubscriptionDto.FormId.ToString(), formVersionId.ToString()) ?? throw new InvalidFormDataSubmissionException(); string chefsFormId = eventSubscriptionDto.FormId.ToString(); string chefsFormVersionId = eventSubscriptionDto.FormVersion.ToString(); - await _applicationFormVersionAppService.UpdateOrCreateApplicationFormVersion(chefsFormId, chefsFormVersionId, applicationForm.Id, formData); + await applicationFormVersionAppService.UpdateOrCreateApplicationFormVersion(chefsFormId, chefsFormVersionId, applicationForm.Id, formData); } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/FormsApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/FormsApiService.cs index 8e87a2d9d..070de5eda 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/FormsApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/FormsApiService.cs @@ -1,82 +1,130 @@ using Newtonsoft.Json; -using RestSharp; -using RestSharp.Authenticators; +using Newtonsoft.Json.Linq; using System; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Unity.GrantManager.Applications; -using Unity.GrantManager.Integration.Chefs; using Unity.GrantManager.Integrations.Exceptions; -using Unity.GrantManager.Integrations.Http; using Volo.Abp; using Volo.Abp.Security.Encryption; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Unity.Modules.Shared.Http; +using Microsoft.EntityFrameworkCore; namespace Unity.GrantManager.Integrations.Chefs { - [IntegrationService] - public class FormsApiService : GrantManagerAppService, IFormsApiService + [IntegrationService] + [ExposeServices(typeof(IFormsApiService))] + public class FormsApiService( + IEndpointManagementAppService endpointManagementAppService, + IApplicationFormRepository applicationFormRepository, + IStringEncryptionService stringEncryptionService, + IResilientHttpRequest resilientRestClient, + ILogger logger) : GrantManagerAppService, IFormsApiService { - private readonly IApplicationFormRepository _applicationFormRepository; - private readonly IStringEncryptionService _stringEncryptionService; - private readonly IResilientHttpRequest _resilientRestClient; + private string? _chefsApiBaseUrl; - public FormsApiService(IApplicationFormRepository applicationFormRepository, - IStringEncryptionService stringEncryptionService, - IResilientHttpRequest resilientRestClient) + private async Task GetChefsApiBaseUrlAsync() { - _applicationFormRepository = applicationFormRepository; - _stringEncryptionService = stringEncryptionService; - _resilientRestClient = resilientRestClient; + _chefsApiBaseUrl ??= await endpointManagementAppService.GetChefsApiBaseUrlAsync(); + return _chefsApiBaseUrl; } - public async Task GetFormDataAsync(string chefsFormId, string chefsFormVersionId) + public async Task GetFormDataAsync(string chefsFormId, string chefsFormVersionId) { - var applicationForm = (await _applicationFormRepository - .GetQueryableAsync()) + var applicationForm = await (await applicationFormRepository.GetQueryableAsync()) .Where(s => s.ChefsApplicationFormGuid == chefsFormId) .OrderBy(s => s.CreationTime) - .FirstOrDefault(); + .FirstOrDefaultAsync(); - if (applicationForm == null) return null; - - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"/forms/{chefsFormId}/versions/{chefsFormVersionId}", - null, - null, - new HttpBasicAuthenticator(applicationForm.ChefsApplicationFormGuid!, _stringEncryptionService.Decrypt(applicationForm.ApiKey!) ?? string.Empty)); - - if (response != null - && response.Content != null - && response.IsSuccessStatusCode) + if (applicationForm == null) { - string content = response.Content; - return JsonConvert.DeserializeObject(content)!; + logger.LogWarning("No application form found for FormId: {FormId}", chefsFormId); + return null; } - else + + string chefsApi = await GetChefsApiBaseUrlAsync(); + string url = $"{chefsApi}/forms/{chefsFormId}/versions/{chefsFormVersionId}"; + + var response = await GetRequestAsync(url, applicationForm.ChefsApplicationFormGuid!, applicationForm.ApiKey!); + return await ParseJsonResponseAsync(response); + } + + public async Task GetForm(Guid? formId, string chefsApplicationFormGuid, string encryptedApiKey) + { + if (string.IsNullOrWhiteSpace(chefsApplicationFormGuid) || string.IsNullOrWhiteSpace(encryptedApiKey)) { - throw new IntegrationServiceException($"Error with integrating with request resource"); + throw new ArgumentException("Form GUID and API Key must be provided"); } + + string chefsApi = await GetChefsApiBaseUrlAsync(); + string url = $"{chefsApi}/forms/{formId}"; + + var response = await GetRequestAsync(url, chefsApplicationFormGuid, encryptedApiKey); + return await ParseJsonResponseAsync(response) ?? new JObject(); } - public async Task GetForm(Guid? formId, string chefsApplicationFormGuid, string encryptedApiKey) + public async Task GetSubmissionDataAsync(Guid chefsFormId, Guid submissionId) { - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"/forms/{formId}", - null, - null, - new HttpBasicAuthenticator(chefsApplicationFormGuid!, _stringEncryptionService.Decrypt(encryptedApiKey!) ?? string.Empty)); - - if (response != null - && response.Content != null - && response.IsSuccessStatusCode) + var applicationForm = await (await applicationFormRepository.GetQueryableAsync()) + .Where(s => s.ChefsApplicationFormGuid == chefsFormId.ToString()) + .OrderBy(s => s.CreationTime) + .FirstOrDefaultAsync(); + + if (applicationForm == null) { - return response.Content; + logger.LogWarning("No application form found for SubmissionId: {SubmissionId}", submissionId); + return null; } - else + + string chefsApi = await GetChefsApiBaseUrlAsync(); + string url = $"{chefsApi}/submissions/{submissionId}"; + + var response = await GetRequestAsync(url, applicationForm.ChefsApplicationFormGuid!, applicationForm.ApiKey!); + return await ParseJsonResponseAsync(response); + } + + private async Task GetRequestAsync(string url, string chefsApplicationFormGuid, string encryptedApiKey) + { + if (string.IsNullOrWhiteSpace(encryptedApiKey)) + throw new ArgumentException("API key is missing or empty"); + + var decryptedApiKey = stringEncryptionService.Decrypt(encryptedApiKey) ?? string.Empty; + logger.LogInformation( + "Sending GET request to {Url} using basic auth with FormGuid: {FormGuid}", + url.Replace(Environment.NewLine, "").Replace("\n", "").Replace("\r", ""), + chefsApplicationFormGuid + ); + + var response = await resilientRestClient.HttpAsync( + HttpMethod.Get, + url, + null, + null, + basicAuth: (chefsApplicationFormGuid, decryptedApiKey) + ); + + if (!response.IsSuccessStatusCode) { - throw new IntegrationServiceException($"Error with integrating with request resource"); + var content = await response.Content.ReadAsStringAsync(); + logger.LogError( + "Request to {Url} failed with status {StatusCode} ({Reason}). Response: {Content}", + url.Replace(Environment.NewLine, "").Replace("\n", "").Replace("\r", ""), + response.StatusCode, + response.ReasonPhrase, + content + ); } + + return response; + } + + private static async Task ParseJsonResponseAsync(HttpResponseMessage response) + { + var content = await response.Content.ReadAsStringAsync(); + return string.IsNullOrWhiteSpace(content) ? null : JsonConvert.DeserializeObject(content); } } } - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/SubmissionsApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/SubmissionsApiService.cs deleted file mode 100644 index fba7ad473..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Chefs/SubmissionsApiService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Newtonsoft.Json; -using RestSharp; -using RestSharp.Authenticators; -using System; -using System.Linq; -using System.Threading.Tasks; -using Unity.GrantManager.Applications; -using Unity.GrantManager.Integration.Chefs; -using Unity.GrantManager.Integrations.Exceptions; -using Unity.GrantManager.Integrations.Http; -using Volo.Abp; -using Volo.Abp.Security.Encryption; - -namespace Unity.GrantManager.Integrations.Chefs -{ - [IntegrationService] - public class SubmissionsApiService : GrantManagerAppService, ISubmissionsApiService - { - private readonly IResilientHttpRequest _resilientRestClient; - private readonly IApplicationFormRepository _applicationFormRepository; - private readonly IStringEncryptionService _stringEncryptionService; - - public SubmissionsApiService(IResilientHttpRequest resilientRestClient, - IApplicationFormRepository applicationFormRepository, - IStringEncryptionService stringEncryptionService) - { - _resilientRestClient = resilientRestClient; - _applicationFormRepository = applicationFormRepository; - _stringEncryptionService = stringEncryptionService; - } - - public async Task GetSubmissionDataAsync(Guid chefsFormId, Guid submissionId) - { - var applicationForm = (await _applicationFormRepository - .GetQueryableAsync()) - .Where(s => s.ChefsApplicationFormGuid == chefsFormId.ToString()) - .OrderBy(s => s.CreationTime) - .FirstOrDefault(); - - if (applicationForm == null) return null; - - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"/submissions/{submissionId}", - null, - null, - new HttpBasicAuthenticator(applicationForm.ChefsApplicationFormGuid!, _stringEncryptionService.Decrypt(applicationForm.ApiKey!) ?? string.Empty)); - - if (response != null - && response.Content != null - && response.IsSuccessStatusCode) - { - string content = response.Content; - return JsonConvert.DeserializeObject(content)!; - } - else - { - throw new IntegrationServiceException($"Error with integrating with request resource"); - } - } - } -} - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiOptions.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiOptions.cs index 939a6d72d..a007e08f8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiOptions.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiOptions.cs @@ -1,11 +1,9 @@ namespace Unity.GrantManager.Integrations.Sso { public class CssApiOptions - { - public string TokenUrl { get; set; } = string.Empty; + { public string ClientId { get; set; } = string.Empty; public string ClientSecret { get; set; } = string.Empty; - public string Url { get; set; } = string.Empty; public string Env { get; set; } = string.Empty; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiService.cs index 3a8aa178d..24b808e8f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Css/CssApiService.cs @@ -1,140 +1,220 @@ -using RestSharp; -using RestSharp.Authenticators; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using Unity.GrantManager.Integrations.Http; -using Volo.Abp.Application.Services; -using System.Text.Json; -using System; -using Volo.Abp.Caching; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; using System.Linq; +using System; +using Unity.GrantManager.Integrations.Css; using Volo.Abp; +using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; -using Unity.GrantManager.Integration.Css; +using Volo.Abp.Caching; +using Unity.Modules.Shared.Http; namespace Unity.GrantManager.Integrations.Sso { [IntegrationService] [ExposeServices(typeof(CssApiService), typeof(ICssUsersApiService))] - public class CssApiService(IResilientHttpRequest resilientHttpRequest, + public class CssApiService( + IEndpointManagementAppService endpointManagementAppService, + IResilientHttpRequest resilientHttpRequest, + IHttpClientFactory httpClientFactory, // Add this for token requests IDistributedCache accessTokenCache, - IOptions cssApiOptions, - RestClient restClient) : ApplicationService, ICssUsersApiService + IOptions cssApiOptions) : ApplicationService, ICssUsersApiService { + private readonly CssApiOptions _cssApiOptions = cssApiOptions.Value; private const string CSS_API_KEY = "CssApiKey"; public async Task FindUserAsync(string directory, string guid) { - var paramDictionary = new Dictionary(); - - if (guid != null) - { - paramDictionary.Add(nameof(guid), guid); - } - - return await SearchSsoAsync(directory, paramDictionary); + var parameters = new Dictionary { { nameof(guid), guid } }; + return await SearchSsoAsync(directory, parameters); } public async Task SearchUsersAsync(string directory, string? firstName = null, string? lastName = null) - { - var paramDictionary = new Dictionary(); + { + var parameters = new Dictionary(); - if (firstName != null && firstName.Length >= 2) - { - paramDictionary.Add(nameof(firstName), firstName); - } + if (!string.IsNullOrWhiteSpace(firstName) && firstName.Length >= 2) + parameters.Add(nameof(firstName), firstName); - if (lastName != null && lastName.Length >= 2) - { - paramDictionary.Add(nameof(lastName), lastName); - } + if (!string.IsNullOrWhiteSpace(lastName) && lastName.Length >= 2) + parameters.Add(nameof(lastName), lastName); - return await SearchSsoAsync(directory, paramDictionary); + return await SearchSsoAsync(directory, parameters); } - private async Task SearchSsoAsync(string directory, Dictionary paramDictionary) + private async Task SearchSsoAsync(string directory, Dictionary parameters) { - var tokenResponse = await GetAccessTokenAsync(); - - var resource = BuildUrlWithQueryStringUsingUriBuilder($"{cssApiOptions.Value.Url}/{cssApiOptions.Value.Env}/{directory}/users?", paramDictionary); - - var authHeaders = new Dictionary + try { - { "Authorization", $"Bearer {tokenResponse.AccessToken}" } - }; - - var response = await resilientHttpRequest.HttpAsync(Method.Get, resource, authHeaders); - - if (response != null - && response.Content != null - && response.IsSuccessStatusCode) - { - string content = response.Content; - var result = JsonSerializer.Deserialize(content) ?? throw new UserFriendlyException("SearchSsoAsync -> Could not Deserialize"); + var cssApiUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.CSS_API_BASE); + var tokenResponse = await GetAccessTokenAsync(); + var baseUrl = $"{cssApiUrl}/{_cssApiOptions.Env}/{directory}/users"; + var url = BuildUrlWithQuery(baseUrl, parameters); + + var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, url, null, tokenResponse.AccessToken); + + if (response != null && response.IsSuccessStatusCode && response.Content != null) + { + var json = await response.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(json)) + { + Logger.LogWarning("Empty response received from CSS API for directory {Directory}", directory); + return CreateErrorResult("Empty response from CSS API"); + } + + try + { + var result = JsonSerializer.Deserialize(json, _jsonOptions)!; + + if (result != null) + { + result.Success = true; + return result; + } + } + catch (JsonException ex) + { + Logger.LogError(ex, "Failed to deserialize user search result. JSON: {Json}", json); + return CreateErrorResult("Failed to parse response from CSS API"); + } + } - result.Success = true; - return result; + var statusCode = response?.StatusCode.ToString() ?? "Unknown"; + var errorContent = response?.Content != null ? await response.Content.ReadAsStringAsync() : "No response"; + Logger.LogWarning("CSS API request failed. Status: {StatusCode}, Content: {Content}, URL: {Url}", + statusCode, errorContent, url); + + return CreateErrorResult($"CSS API request failed with status {statusCode}"); } - else + catch (Exception ex) { - return new UserSearchResult() { Error = "", Success = false, Data = Array.Empty() }; + Logger.LogError(ex, "Unexpected error during CSS API search for directory {Directory}", directory); + return CreateErrorResult("Unexpected error occurred while searching users"); } } - private static string BuildUrlWithQueryStringUsingUriBuilder(string basePath, Dictionary queryParams) + private static UserSearchResult CreateErrorResult(string error) => new() { - var uriBuilder = new UriBuilder(basePath) - { - Query = string.Join("&", queryParams.Select(kvp => $"{kvp.Key}={kvp.Value}")) - }; - return uriBuilder.Uri.AbsoluteUri; + Success = false, + Error = error, + Data = [] + }; + + private static string BuildUrlWithQuery(string basePath, Dictionary queryParams) + { + if (queryParams.Count == 0) + return basePath; + + var query = string.Join("&", queryParams.Select(kv => $"{kv.Key}={Uri.EscapeDataString(kv.Value)}")); + return $"{basePath}?{query}"; } - private async Task GetAccessTokenAsync() + // Define this once (e.g., at the top of your class or as a static field) + private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { - var cachedTokenResponse = await accessTokenCache.GetAsync(CSS_API_KEY); + PropertyNameCaseInsensitive = true + }; - if (cachedTokenResponse != null) + private async Task GetAccessTokenAsync() + { + // Check cache first + var cachedToken = await accessTokenCache.GetAsync(CSS_API_KEY); + if (cachedToken != null && !IsTokenExpiringSoon(cachedToken)) { - return cachedTokenResponse; + return cachedToken; } - var grantType = "client_credentials"; - - var request = new RestRequest($"{cssApiOptions.Value.TokenUrl}") + try { - Authenticator = new HttpBasicAuthenticator(cssApiOptions.Value.ClientId, cssApiOptions.Value.ClientSecret) - }; - request.AddHeader("content-type", "application/x-www-form-urlencoded"); - request.AddParameter("application/x-www-form-urlencoded", $"grant_type={grantType}", ParameterType.RequestBody); + var cssTokenApiUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.CSS_TOKEN_API_BASE); - var response = await restClient.ExecuteAsync(request, Method.Post); + // Use HttpClientFactory instead of new HttpClient() + using var client = httpClientFactory.CreateClient(); - if (response.Content == null) - { - throw new UserFriendlyException($"Error fetching Css API token - content empty {response.StatusCode} {response.ErrorMessage}"); - } + var request = new HttpRequestMessage(HttpMethod.Post, cssTokenApiUrl); + var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_cssApiOptions.ClientId}:{_cssApiOptions.ClientSecret}")); - if (response.StatusCode != HttpStatusCode.OK) - { - Logger.LogError(response.ErrorException, "Error fetching Css API token {StatusCode} {ErrorMessage} {ErrorException}", response.StatusCode, response.ErrorMessage, response.ErrorException); + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); + request.Content = new StringContent("grant_type=client_credentials", Encoding.UTF8, "application/x-www-form-urlencoded"); + + var response = await client.SendAsync(request); - if (response.StatusCode == HttpStatusCode.Unauthorized) - { - throw new UnauthorizedAccessException(response.ErrorMessage); + if (!response.IsSuccessStatusCode) + { + var errorContent = response.Content != null ? await response.Content.ReadAsStringAsync() : "No content"; + Logger.LogError("Failed to fetch CSS API token. Status: {StatusCode}, Content: {ErrorContent}, URL: {Url}", + response.StatusCode, errorContent, cssTokenApiUrl); + throw new UserFriendlyException($"Failed to authenticate with CSS API: {response.StatusCode}"); + } + + if (response.Content == null) + { + Logger.LogError("CSS token API returned success but no content"); + throw new UserFriendlyException("Invalid response from CSS token API"); } - } - var tokenResponse = JsonSerializer.Deserialize(response.Content) ?? throw new UserFriendlyException($"Error deserializing token response {response.StatusCode} {response.ErrorMessage}"); - await accessTokenCache.SetAsync(CSS_API_KEY, tokenResponse, new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions() + var content = await response.Content.ReadAsStringAsync(); + + if (string.IsNullOrWhiteSpace(content)) + { + Logger.LogError("CSS token API returned empty content"); + throw new UserFriendlyException("Empty response from CSS token API"); + } + + TokenValidationResponse tokenResponse; + try + { + tokenResponse = JsonSerializer.Deserialize(content, _jsonOptions)!; + + if (tokenResponse == null) + { + throw new UserFriendlyException("Invalid token response format"); + } + } + catch (JsonException ex) + { + Logger.LogError(ex, "Failed to deserialize token response. Content: {Content}", content); + throw new UserFriendlyException("Failed to parse token response"); + } + + // Cache with buffer time to avoid expiration edge cases + var cacheExpiration = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn - 60); // 60 second buffer + await accessTokenCache.SetAsync(CSS_API_KEY, tokenResponse, new DistributedCacheEntryOptions + { + AbsoluteExpiration = cacheExpiration + }); + + Logger.LogDebug("Successfully cached new CSS API token, expires at {ExpirationTime}", cacheExpiration); + return tokenResponse; + } + catch (UserFriendlyException) + { + throw; // Re-throw user-friendly exceptions as-is + } + catch (Exception ex) { - AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn) - }); + Logger.LogError(ex, "Unexpected error while fetching CSS API access token"); + throw new UserFriendlyException("Failed to authenticate with CSS API"); + } + } - return tokenResponse; + /// + /// Check if token is expiring within the next 5 minutes + /// + private static bool IsTokenExpiringSoon(TokenValidationResponse token) + { + if (token.ExpiresIn <= 0) return true; + + // Consider token expiring if it has less than 5 minutes left + return token.ExpiresIn < 300; } } -} +} \ No newline at end of file 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 new file mode 100644 index 000000000..5718fe268 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Endpoints/EndpointManagementAppService.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Uow; + +namespace Unity.GrantManager.Integrations.Endpoints +{ + public class EndpointManagementAppService( + IRepository repository, + IDistributedCache cache) : + CrudAppService< + DynamicUrl, + DynamicUrlDto, + Guid, + PagedAndSortedResultRequestDto, + CreateUpdateDynamicUrlDto>(repository), + IEndpointManagementAppService + { + private readonly IDistributedCache _cache = cache; + private const string CACHE_KEY_SET_PREFIX = "DynamicUrl:KeySet"; + + private static string BuildCacheKey(string keyName, bool tenantSpecific, Guid? tenantId) + => $"DynamicUrl:{tenantSpecific}:{tenantId ?? Guid.Empty}:{keyName}"; + + private static string BuildCacheKeySetKey(Guid? tenantId) + => $"{CACHE_KEY_SET_PREFIX}:{tenantId ?? Guid.Empty}"; + + private async Task AddToKeySetAsync(string cacheKey, Guid? tenantId) + { + var keySetKey = BuildCacheKeySetKey(tenantId); + var existing = await _cache.GetStringAsync(keySetKey); + var keySet = string.IsNullOrEmpty(existing) + ? new HashSet() + : System.Text.Json.JsonSerializer.Deserialize>(existing) ?? new HashSet(); + + keySet.Add(cacheKey); + var serialized = System.Text.Json.JsonSerializer.Serialize(keySet); + + await _cache.SetStringAsync(keySetKey, serialized, new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24) // longer than individual entries + }); + } + + private async Task RemoveFromKeySetAsync(string cacheKey, Guid? tenantId) + { + var keySetKey = BuildCacheKeySetKey(tenantId); + var existing = await _cache.GetStringAsync(keySetKey); + if (string.IsNullOrEmpty(existing)) return; + + var keySet = System.Text.Json.JsonSerializer.Deserialize>(existing); + if (keySet?.Remove(cacheKey) == true) + { + var serialized = System.Text.Json.JsonSerializer.Serialize(keySet); + await _cache.SetStringAsync(keySetKey, serialized, new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24) + }); + } + } + + [UnitOfWork] + public async Task GetChefsApiBaseUrlAsync() + { + var url = await GetUrlByKeyNameInternalAsync(DynamicUrlKeyNames.INTAKE_API_BASE, tenantSpecific: false); + + if (string.IsNullOrWhiteSpace(url)) + throw new UserFriendlyException("CHEFS API base URL not configured."); + + return url!; + } + + [UnitOfWork] + public async Task GetUgmUrlByKeyNameAsync(string keyName) + { + var url = await GetUrlByKeyNameInternalAsync(keyName, tenantSpecific: false); + if (url == null) + throw new UserFriendlyException($"URL for key '{keyName}' not configured."); + return url; + } + + public async Task GetUrlByKeyNameAsync(string keyName) + { + var url = await GetUrlByKeyNameInternalAsync(keyName, tenantSpecific: true); + if (url == null) + throw new UserFriendlyException($"URL for key '{keyName}' not configured."); + return url; + } + + private async Task GetUrlByKeyNameInternalAsync(string keyName, bool tenantSpecific) + { + var tenantId = tenantSpecific ? CurrentTenant.Id : null; + var cacheKey = BuildCacheKey(keyName, tenantSpecific, tenantId); + var cached = await _cache.GetStringAsync(cacheKey); + if (!string.IsNullOrEmpty(cached)) + return cached; + + DynamicUrl? dynamicUrl; + if (tenantSpecific) + { + dynamicUrl = await Repository.FirstOrDefaultAsync(x => x.KeyName == keyName && x.TenantId == tenantId); + } + else + { + using (CurrentTenant.Change(null)) + { + dynamicUrl = await Repository.FirstOrDefaultAsync(x => x.KeyName == keyName && x.TenantId == null); + } + } + + var url = dynamicUrl?.Url; + + if (!string.IsNullOrWhiteSpace(url)) + { + await _cache.SetStringAsync( + cacheKey, + url, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) + }); + + // Track this key + await AddToKeySetAsync(cacheKey, tenantId); + } + + return url; + } + + // ------------------------------ + // Cache invalidation methods + // ------------------------------ + public async Task InvalidateCacheAsync(string keyName, bool tenantSpecific, Guid? tenantId) + { + var cacheKey = BuildCacheKey(keyName, tenantSpecific, tenantId); + await _cache.RemoveAsync(cacheKey); + await RemoveFromKeySetAsync(cacheKey, tenantId); + } + + /// + /// Clears all DynamicUrl cache entries for the specified tenant (or all tenants if null). + /// Uses tracked cache keys for efficient bulk clearing. + /// + public async Task ClearCacheAsync(Guid? tenantId = null) + { + var keySetKey = BuildCacheKeySetKey(tenantId); + var existing = await _cache.GetStringAsync(keySetKey); + + if (!string.IsNullOrEmpty(existing)) + { + var keySet = System.Text.Json.JsonSerializer.Deserialize>(existing); + if (keySet != null) + { + // Remove all tracked cache entries + var tasks = keySet.Select(key => _cache.RemoveAsync(key)); + await Task.WhenAll(tasks); + } + } + + // Clear the key set itself + await _cache.RemoveAsync(keySetKey); + } + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/GeocoderApiService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/GeocoderApiService.cs index 4c3d6c3a4..47b65c629 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/GeocoderApiService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/GeocoderApiService.cs @@ -1,30 +1,27 @@ -using Microsoft.AspNetCore.Authorization; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using RestSharp; -using System.Threading.Tasks; -using Unity.GrantManager.Integration.Geocoder; using Unity.GrantManager.Integrations.Exceptions; -using Unity.GrantManager.Integrations.Http; +using Unity.Modules.Shared.Http; using Volo.Abp.Application.Services; namespace Unity.GrantManager.Integrations.Geocoder { - //[IntegrationService] - //[ExposeServices(typeof(GeocoderApiService), typeof(IGeocoderApiService))] [AllowAnonymous] - public class GeocoderApiService(IResilientHttpRequest resilientRestClient, IConfiguration configuration) : ApplicationService, IGeocoderApiService + public class GeocoderApiService(IResilientHttpRequest resilientRestClient, IConfiguration configuration, IEndpointManagementAppService endpointManagementAppService) : ApplicationService { public async Task GetAddressDetailsAsync(string address) { var resource = $"{configuration["Geocoder:LocationDetails:BaseUri"]}/addresses.json?outputSRS=3005&addressString={address}"; - return ResultMapper.MapToLocation(await GetGeoCodeDataSegmentAsync(resource)); } public async Task GetElectoralDistrictAsync(LocationCoordinates locationCoordinates) { - var resource = $"{configuration["Geocoder:BaseUri"]}" + + var geoCoderBaseUri = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.GEOCODER_API_BASE); + var resource = $"{geoCoderBaseUri}" + $"{configuration["Geocoder:ElectoralDistrict:feature"]}" + $"&srsname=EPSG:4326" + $"&propertyName={configuration["Geocoder:ElectoralDistrict:property"]}" + @@ -63,20 +60,17 @@ public async Task GetRegionalDistrictAsync(LocationCoordina private async Task GetGeoCodeDataSegmentAsync(string resource) { - var response = await resilientRestClient.HttpAsync(Method.Get, resource); + var response = await resilientRestClient.HttpAsync(HttpMethod.Get, resource, null, null); - if (response != null - && response.Content != null - && response.IsSuccessStatusCode) + if (response != null && response.IsSuccessStatusCode && response.Content != null) { - string content = response.Content; + var content = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(content)!; } else { - throw new IntegrationServiceException($"Error with integrating with request resource"); + throw new IntegrationServiceException($"Error integrating with resource: {resource}. Status: {response?.StatusCode}"); } } } } - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/ResultMapper.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/ResultMapper.cs index 29b5b3614..dcb09459c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/ResultMapper.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Geocoder/ResultMapper.cs @@ -1,6 +1,4 @@ -using Unity.GrantManager.Integration.Geocoder; - -namespace Unity.GrantManager.Integrations.Geocoder +namespace Unity.GrantManager.Integrations.Geocoder { internal static class ResultMapper { @@ -50,4 +48,4 @@ internal static class ResultMapper }; } } -} +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/IResilientHttpRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/IResilientHttpRequest.cs deleted file mode 100644 index 899311a98..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/IResilientHttpRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Polly.CircuitBreaker; -using Polly.Retry; -using RestSharp; -using RestSharp.Authenticators; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Application.Services; - -namespace Unity.GrantManager.Integrations.Http -{ - public interface IResilientHttpRequest : IApplicationService - { - Task HttpAsync(Method httpVerb, string resource, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null); - Task HttpAsync(Method httpVerb, string resource, AsyncRetryPolicy retryPolicy, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null); - Task HttpAsync(Method httpVerb, string resource, AsyncRetryPolicy retryPolicy, AsyncCircuitBreakerPolicy circuitBreakerPolicy, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null); - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/ResilientHttpRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/ResilientHttpRequest.cs deleted file mode 100644 index 8e281ee60..000000000 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/Http/ResilientHttpRequest.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.Extensions.Logging; -using Polly; -using Polly.CircuitBreaker; -using Polly.Retry; -using RestSharp; -using RestSharp.Authenticators; -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using Volo.Abp; - -namespace Unity.GrantManager.Integrations.Http -{ - [IntegrationService] - public class ResilientHttpRequest : GrantManagerAppService, IResilientHttpRequest - { - private readonly RestClient _restClient; - private static int _maxRetryAttempts = 3; - private static TimeSpan _pauseBetweenFailures = TimeSpan.FromSeconds(10); - - public ResilientHttpRequest(RestClient restClient) - { - _restClient = restClient; - } - - public static void SetMaxRetryAttemptsAndPauseBetweenFailures(int maxRetryAttempts, TimeSpan pauseBetweenFailures) - { - _maxRetryAttempts = maxRetryAttempts; - _pauseBetweenFailures = pauseBetweenFailures; - } - - public async Task HttpAsync(Method httpVerb, string resource, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null) - { - return await ExecuteRequestAsync(httpVerb, resource, headers, requestObject, null, null, authenticator); - } - - public async Task HttpAsync(Method httpVerb, string resource, AsyncRetryPolicy retryPolicy, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null) - { - return await ExecuteRequestAsync(httpVerb, resource, headers, requestObject, retryPolicy, null, authenticator); - } - - public async Task HttpAsync(Method httpVerb, string resource, AsyncRetryPolicy retryPolicy, AsyncCircuitBreakerPolicy circuitBreakerPolicy, Dictionary? headers = null, object? requestObject = null, IAuthenticator? authenticator = null) - { - return await ExecuteRequestAsync(httpVerb, resource, headers, requestObject, retryPolicy, circuitBreakerPolicy, authenticator); - } - - private async Task ExecuteRequestAsync(Method httpVerb, string resource, Dictionary? headers, object? requestObject, AsyncRetryPolicy? retryPolicy = null, AsyncCircuitBreakerPolicy? circuitBreakerPolicy = null, IAuthenticator? authenticator = null) - { - RestResponse? restResponse; - - try - { - var restRequest = new RestRequest(resource, httpVerb); - if (authenticator != null) - { - restRequest.Authenticator = authenticator; - } - restRequest.AddHeader("cache-control", "no-cache"); - if (headers != null && headers.Count > 0) - foreach (var header in headers) - restRequest.AddHeader(header.Key, header.Value); - - if (httpVerb != Method.Get && requestObject != null) - { - restRequest.RequestFormat = DataFormat.Json; - restRequest.AddJsonBody(requestObject); - } - restResponse = await RestResponseWithPolicyAsync(restRequest, retryPolicy, circuitBreakerPolicy); - } - catch (Exception ex) - { - restResponse = new RestResponse - { - Content = ex.Message, - ErrorMessage = ex.Message, - ResponseStatus = ResponseStatus.TimedOut, - StatusCode = HttpStatusCode.ServiceUnavailable - }; - } - - return await Task.FromResult(restResponse); - } - - private async Task RestResponseWithPolicyAsync(RestRequest restRequest, AsyncRetryPolicy? retryPolicy = null, AsyncCircuitBreakerPolicy? circuitBreakerPolicy = null) - { - retryPolicy ??= Policy - .HandleResult(x => !x.IsSuccessful) - .WaitAndRetryAsync(_maxRetryAttempts, x => _pauseBetweenFailures, async (iRestResponse, timeSpan, retryCount, context) => - { - await Task.Run(() => Logger.LogError("The request failed. HttpStatusCode={statusCode}. Waiting {timeSpan} seconds before retry. Number attempt {retryCount}. Uri={responseUri}; RequestResponse={responseContent}", iRestResponse.Result.StatusCode, timeSpan, retryCount, iRestResponse.Result.ResponseUri, iRestResponse.Result.Content)); - }); - - circuitBreakerPolicy ??= Policy - .HandleResult(x => x.StatusCode == HttpStatusCode.ServiceUnavailable || x.StatusCode == HttpStatusCode.TooManyRequests) - .CircuitBreakerAsync(1, TimeSpan.FromSeconds(30), onBreak: async (iRestResponse, timespan, context) => - { - await Task.Run(() => Logger.LogError("Circuit went into a fault state. Reason: {resultContent}", iRestResponse.Result.Content)); - }, - onReset: async (context) => - { - await Task.Run(() => Logger.LogError($"Circuit left the fault state.")); - }); - - return await retryPolicy.WrapAsync(circuitBreakerPolicy).ExecuteAsync(async () => await _restClient.ExecuteAsync(restRequest)); - } - } -} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/OrgBook/OrgBookService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/OrgBook/OrgBookService.cs index 75d05d31c..cf8d50a2c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/OrgBook/OrgBookService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Integrations/OrgBook/OrgBookService.cs @@ -1,85 +1,83 @@ using Newtonsoft.Json; -using RestSharp; using System.Text.Json; using System.Threading.Tasks; -using Unity.GrantManager.Integration.Orgbook; using Unity.GrantManager.Integrations.Exceptions; -using Unity.GrantManager.Integrations.Http; using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; +using System.Net.Http; +using Unity.Modules.Shared.Http; +using System; namespace Unity.GrantManager.Integrations.Orgbook { - [ExposeServices(typeof(OrgBookService), typeof(IOrgBookService))] public class OrgBookService : ApplicationService, IOrgBookService { - private readonly IResilientHttpRequest _resilientRestClient; + private readonly IResilientHttpRequest resilientRestClient; + private readonly Task orgbookBaseApiTask; + + private const string OrgbookQueryMatch = "inactive=any&latest=any&revoked=any&ordering=-score"; - private readonly string orgbook_base_api = "https://orgbook.gov.bc.ca/api"; - private readonly string orgbook_query_match = "inactive=any&latest=any&revoked=any&ordering=-score"; + public OrgBookService( + IResilientHttpRequest resilientRestClient, + IEndpointManagementAppService endpointManagementAppService) + { + this.resilientRestClient = resilientRestClient; - public OrgBookService(IResilientHttpRequest resilientRestClient) { - _resilientRestClient = resilientRestClient; + // Initialize the base API URL once during construction + orgbookBaseApiTask = InitializeBaseApiAsync(endpointManagementAppService); + } + + private static async Task InitializeBaseApiAsync(IEndpointManagementAppService endpointManagementAppService) + { + var url = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.ORGBOOK_API_BASE); + return url ?? throw new IntegrationServiceException("OrgBook API base URL is not configured."); } public async Task GetOrgBookQueryAsync(string orgBookQuery) { - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"{orgbook_base_api}/v4/search/topic?q={orgBookQuery}&{orgbook_query_match}"); - - if (response != null && response.Content != null) - { - string content = response.Content; - return JsonConvert.DeserializeObject(content)!; - } - else - { - throw new IntegrationServiceException("GetOrgBookByNumberAsync -> No Response"); - } + var baseApi = await orgbookBaseApiTask; + var queryParams = $"q={Uri.EscapeDataString(orgBookQuery)}&{OrgbookQueryMatch}"; + var response = await resilientRestClient.HttpAsync( + HttpMethod.Get, + $"{baseApi}/v4/search/topic?{queryParams}", + null, null, null); + + if (response?.Content == null) + throw new IntegrationServiceException("OrgBook query request returned no response."); + + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content)!; } public async Task GetOrgBookAutocompleteQueryAsync(string? orgBookQuery) { - if (orgBookQuery == null) - { + if (string.IsNullOrWhiteSpace(orgBookQuery)) return JsonDocument.Parse("{}"); - } - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"{orgbook_base_api}/v3/search/autocomplete?q={orgBookQuery}&revoked=false&inactive="); + var baseApi = await orgbookBaseApiTask; + var url = $"{baseApi}/v3/search/autocomplete?q={Uri.EscapeDataString(orgBookQuery)}&revoked=false&inactive="; + var response = await resilientRestClient.HttpAsync(HttpMethod.Get, url, null, null, null); - if (response != null && response.Content != null) - { + if (response?.Content == null) + throw new IntegrationServiceException("OrgBook autocomplete request returned no response."); - return JsonDocument.Parse(response.Content); - } - else - { - throw new IntegrationServiceException("Failed to connect to Org Book"); - } + return JsonDocument.Parse(await response.Content.ReadAsStringAsync()); } public async Task GetOrgBookDetailsQueryAsync(string? orgBookId) { - if (orgBookId == null) - { + if (string.IsNullOrWhiteSpace(orgBookId)) return JsonDocument.Parse("{}"); - } - var response = await _resilientRestClient - .HttpAsync(Method.Get, $"{orgbook_base_api}/v2/topic/ident/registration.registries.ca/{orgBookId}/formatted"); + var baseApi = await orgbookBaseApiTask; + var url = $"{baseApi}/v2/topic/ident/registration.registries.ca/{Uri.EscapeDataString(orgBookId)}/formatted"; + var response = await resilientRestClient.HttpAsync(HttpMethod.Get, url, null, null, null); - if (response != null && response.Content != null) - { + if (response?.Content == null) + throw new IntegrationServiceException("OrgBook details request returned no response."); - return JsonDocument.Parse(response.Content); - } - else - { - throw new IntegrationServiceException("Failed to connect to Org Book"); - } + return JsonDocument.Parse(await response.Content.ReadAsStringAsync()); } } } - diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Emails/EmailAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Norifications/EmailAppService.cs similarity index 100% rename from applications/Unity.GrantManager/src/Unity.GrantManager.Application/Emails/EmailAppService.cs rename to applications/Unity.GrantManager/src/Unity.GrantManager.Application/Norifications/EmailAppService.cs diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Norifications/NotificationsAppService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Norifications/NotificationsAppService.cs new file mode 100644 index 000000000..b01b2881d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Norifications/NotificationsAppService.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Unity.GrantManager.Applications; +using Unity.GrantManager.Integrations; +using Unity.Notifications.TeamsNotifications; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Unity.GrantManager.Notifications +{ + // This class is responsible for first lookup up the Teams channel URL from the database and then posting notifications to the Teams Service. + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(NotificationsAppService), typeof(INotificationsAppService))] + public class NotificationsAppService : INotificationsAppService, ITransientDependency + { + private readonly IDynamicUrlRepository _dynamicUrlRepository; + private readonly TeamsNotificationService _teamsNotificationService; + + public NotificationsAppService(IDynamicUrlRepository dynamicUrlRepository) + { + _dynamicUrlRepository = dynamicUrlRepository; + _teamsNotificationService = new TeamsNotificationService(); + } + + public async Task InitializeTeamsChannelAsync(string keyName) + { + DynamicUrl? teamsChannel = await _dynamicUrlRepository.FirstOrDefaultAsync(q => q.KeyName == keyName); + if (teamsChannel?.Url == null) + { + return ""; + } + return teamsChannel.Url; + } + + [RemoteService(false)] + public async Task NotifyChefsEventToTeamsAsync(string factName, string factValue, bool alert = false) + { + string teamsChannel = await InitializeTeamsChannelAsync(alert ? TeamsNotificationService.TEAMS_ALERT : TeamsNotificationService.TEAMS_NOTIFICATION); + if (teamsChannel.IsNullOrEmpty()) + { + return; + } + + string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + string activityTitle = "Chefs Submission Event Validation Error"; + string activitySubtitle = "Environment: " + envInfo; + _teamsNotificationService.AddFact(factName, factValue); + await _teamsNotificationService.PostFactsToTeamsAsync(teamsChannel, activityTitle, activitySubtitle); + } + + public async Task PostToTeamsAsync(string activityTitle, string activitySubtitle, List facts) + { + string teamsChannel = await InitializeTeamsChannelAsync(TeamsNotificationService.TEAMS_NOTIFICATION); + if (teamsChannel.IsNullOrEmpty()) + { + return; + } + + string messageCard = TeamsNotificationService.InitializeMessageCard(activityTitle, activitySubtitle, facts); + await TeamsNotificationService.PostToTeamsChannelAsync(teamsChannel, messageCard); + } + + public async Task PostToTeamsAsync(string activityTitle, string activitySubtitle) + { + string teamsChannel = await InitializeTeamsChannelAsync(TeamsNotificationService.TEAMS_NOTIFICATION); + if (teamsChannel.IsNullOrEmpty()) + { + return; + } + List facts = new() { }; + string messageCard = TeamsNotificationService.InitializeMessageCard(activityTitle, activitySubtitle, facts); + await TeamsNotificationService.PostToTeamsChannelAsync(teamsChannel, messageCard); + } + + public async Task PostChefsEventToTeamsAsync(string subscriptionEvent, dynamic form, dynamic chefsFormVersion) + { + string teamsChannel = await InitializeTeamsChannelAsync(TeamsNotificationService.TEAMS_NOTIFICATION); + if (teamsChannel.IsNullOrEmpty()) + { + return; + } + await TeamsNotificationService.PostChefsEventToTeamsAsync(teamsChannel, subscriptionEvent, form, chefsFormVersion); + } + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/NotificationApiUrlHolder.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/NotificationApiUrlHolder.cs new file mode 100644 index 000000000..6dd0e861c --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/NotificationApiUrlHolder.cs @@ -0,0 +1,6 @@ + +namespace Unity.GrantManager; +public class NotificationApiUrlHolder +{ + public string? BaseUrl { get; set; } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Applications/ApplicationLinkType.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Applications/ApplicationLinkType.cs new file mode 100644 index 000000000..84a274070 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Applications/ApplicationLinkType.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace Unity.GrantManager.Applications; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ApplicationLinkType +{ + Related, + Parent, + Child +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrl.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrl.cs new file mode 100644 index 000000000..99ef7797b --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrl.cs @@ -0,0 +1,15 @@ +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.GrantManager.Integrations; + +public class DynamicUrl : FullAuditedEntity, IMultiTenant, IHasConcurrencyStamp +{ + public string ConcurrencyStamp { get; set; } = string.Empty; + public string KeyName { get; set; } = string.Empty; + public string Url { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public Guid? TenantId { get; set; } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrlKeyNames.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrlKeyNames.cs new file mode 100644 index 000000000..8e1c9cce8 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Integrations/DynamicUrlKeyNames.cs @@ -0,0 +1,16 @@ +namespace Unity.GrantManager.Integrations; + +public static class DynamicUrlKeyNames +{ + public const string CSS_API_BASE = "CSS_API_BASE"; + public const string CSS_TOKEN_API_BASE = "CSS_TOKEN_API_BASE"; + public const string INTAKE_API_BASE = "INTAKE_API_BASE"; + public const string PAYMENT_API_BASE = "PAYMENT_API_BASE"; + public const string ORGBOOK_API_BASE = "ORGBOOK_API_BASE"; + public const string NOTIFICATION_API_BASE = "NOTIFICATION_API_BASE"; + public const string NOTIFICATION_AUTH = "NOTIFICATION_AUTH"; + public const string DIRECT_MESSAGE_KEY_PREFIX = "DIRECT_MESSAGE_"; // Teams Direct Message URL Weebhook- Dynamically incremented + public const string WEBHOOK_KEY_PREFIX = "WEBHOOK_"; // General Webhook URL - Dynamically incremented + public const string GEOCODER_API_BASE = "GEOCODER_API_BASE"; + public const string GEOCODER_LOCATION_API_BASE = "GEOCODER_LOCATION_API_BASE"; +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json index a8763b123..06fb21bfc 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 @@ -15,6 +15,7 @@ "Menu:Intakes": "Intakes", "Menu:ApplicationForms": "Forms", "Menu:TenantManagement": "Tenants", + "Menu:EndpointManagement": "Endpoints", "Welcome": "Welcome", "LongWelcomeMessage": "Welcome to Unity Grant Manager", @@ -455,6 +456,11 @@ "ApplicationBatchApprovalRequest:InvalidPermissions": "Invalid permissions", "ApplicationBatchApprovalRequest:InvalidApprovedAmount": "Invalid Approved Amount, it must be greater than 0.00", "ApplicationBatchApprovalRequest:MaxCountExceeded": "You have exceeded the maximum number of items for bulk approval. Please reduce the number to {0} or fewer", - "ApplicationBatchApprovalRequest:InvalidRecommendedAmount": "Invalid Recommended Amount, it must be greater than 0.00" + "ApplicationBatchApprovalRequest:InvalidRecommendedAmount": "Invalid Recommended Amount, it must be greater than 0.00", + + "ApplicationLinks:Category": "Category", + "ApplicationLinks:ID": "ID", + "ApplicationLinks:Status": "Status", + "ApplicationLinks:LinkType": "Link Type" } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantManagerPermissions.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantManagerPermissions.cs index 612268927..991948f97 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantManagerPermissions.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantManagerPermissions.cs @@ -3,8 +3,8 @@ #pragma warning disable S3218 // Inner class members should not shadow outer class "static" or type members public static class GrantManagerPermissions { - public const string GroupName = "GrantManagerManagement"; - + public const string GroupName = "GrantManagerManagement"; + public const string Default = GroupName + ".Default"; public static class Organizations @@ -23,5 +23,11 @@ public static class ApplicationForms { public const string Default = GroupName + ".ApplicationForms"; } + + public static class Endpoints + { + public const string Default = GroupName + ".Endpoints"; + public const string ManageEndpoints = Default + ".ManageEndpoints"; + } } #pragma warning restore S3218 // Inner class members should not shadow outer class "static" or type members \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Unity.GrantManager.Domain.Shared.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Unity.GrantManager.Domain.Shared.csproj index a6dc6a7f5..38791bfc9 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Unity.GrantManager.Domain.Shared.csproj +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Unity.GrantManager.Domain.Shared.csproj @@ -26,7 +26,7 @@ - + diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationFormVersion.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationFormVersion.cs index e9cfb1467..f72fba7de 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationFormVersion.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationFormVersion.cs @@ -21,7 +21,7 @@ public class ApplicationFormVersion : AuditedAggregateRoot, IMultiTenant public string ReportKeys { get; set; } = string.Empty; public string ReportViewName { get; set; } = string.Empty; [Column(TypeName = "jsonb")] - public string? FormSchema { get; set; } = string.Empty; + public string? FormSchema { get; set; } = null; /// /// Checks if the submission header mapping contains a specific field. diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationLink.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationLink.cs index 0b90c67a0..e0bdb81e0 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationLink.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/ApplicationLink.cs @@ -9,4 +9,5 @@ public class ApplicationLink : AuditedAggregateRoot, IMultiTenant public Guid ApplicationId { get; set; } public Guid LinkedApplicationId { get; set; } public Guid? TenantId { get; set; } + public ApplicationLinkType LinkType { get; set; } = ApplicationLinkType.Related; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IDynamicUrlRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IDynamicUrlRepository.cs new file mode 100644 index 000000000..6d8aa1d64 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Applications/IDynamicUrlRepository.cs @@ -0,0 +1,10 @@ +using System; +using Unity.GrantManager.Integrations; +using Volo.Abp.Domain.Repositories; + +namespace Unity.GrantManager.Applications; + +public interface IDynamicUrlRepository : IRepository +{ + +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerConsts.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerConsts.cs index 4e1a94625..21788549c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerConsts.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerConsts.cs @@ -16,4 +16,6 @@ public static class GrantManagerConsts public const string DefaultTenantConnectionStringName = "Tenant"; public const string DefaultConnectionStringName = "Default"; + + public const string UnknownValue = "Unknown"; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerDataSeederContributor.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerDataSeederContributor.cs index 75c425f5d..9fe38d74e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerDataSeederContributor.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/GrantManagerDataSeederContributor.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Unity.GrantManager.Applications; using Unity.GrantManager.GrantApplications; @@ -7,138 +8,63 @@ namespace Unity.GrantManager; -public class GrantManagerDataSeederContributor : IDataSeedContributor, ITransientDependency +public class GrantManagerDataSeederContributor( + IApplicationStatusRepository applicationStatusRepository) : IDataSeeder, ITransientDependency { - private readonly IApplicationStatusRepository _applicationStatusRepository; - - public GrantManagerDataSeederContributor(IApplicationStatusRepository applicationStatusRepository) + public static class GrantApplicationStates { - _applicationStatusRepository = applicationStatusRepository; + public const string SUBMITTED = "Submitted"; + public const string ASSIGNED = "Assigned"; + public const string WITHDRAWN = "Withdrawn"; + public const string CLOSED = "Closed"; + public const string UNDER_REVIEW = "Under Review"; + public const string UNDER_INITIAL_REVIEW = "Under Initial Review"; + public const string INITITAL_REVIEW_COMPLETED = "Initial Review Completed"; + public const string UNDER_ASSESSMENT = "Under Assessment"; + public const string ASSESSMENT_COMPLETED = "Assessment Completed"; + public const string GRANT_APPROVED = "Grant Approved"; + public const string DECLINED = "Declined"; + public const string DEFER = "Deferred"; + public const string ON_HOLD = "On Hold"; } public async Task SeedAsync(DataSeedContext context) { - if (context.TenantId != null) // only try seed into a tenant database - { - ApplicationStatus? status1 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.SUBMITTED); - status1 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.SUBMITTED, - ExternalStatus = "Submitted", - InternalStatus = "Submitted" - } - ); - - ApplicationStatus? status2 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.ASSIGNED); - status2 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.ASSIGNED, - ExternalStatus = "Under Review", - InternalStatus = "Assigned" - } - ); - - ApplicationStatus? status3 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.WITHDRAWN); - status3 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.WITHDRAWN, - ExternalStatus = "Withdrawn", - InternalStatus = "Withdrawn" - } - ); - - ApplicationStatus? status4 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.CLOSED); - status4 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.CLOSED, - ExternalStatus = "Closed", - InternalStatus = "Closed" - } - ); - - ApplicationStatus? status5 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.UNDER_INITIAL_REVIEW); - status5 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.UNDER_INITIAL_REVIEW, - ExternalStatus = "Under Review", - InternalStatus = "Under Initial Review" - } - ); - - ApplicationStatus? status6 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.INITITAL_REVIEW_COMPLETED); - status6 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.INITITAL_REVIEW_COMPLETED, - ExternalStatus = "Under Review", - InternalStatus = "Initial Review Completed" - } - ); - ApplicationStatus? status7 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.UNDER_ASSESSMENT); - status7 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.UNDER_ASSESSMENT, - ExternalStatus = "Under Review", - InternalStatus = "Under Assessment" - } - ); - - ApplicationStatus? status8 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.ASSESSMENT_COMPLETED); - status8 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.ASSESSMENT_COMPLETED, - ExternalStatus = "Under Review", - InternalStatus = "Assessment Completed" - } - ); - - ApplicationStatus? status9 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.GRANT_APPROVED); - status9 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.GRANT_APPROVED, - ExternalStatus = "Approved", - InternalStatus = "Approved" - } - ); + if (context.TenantId == null) // only seed into a tenant database + { + return; + } - ApplicationStatus? status10 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.GRANT_NOT_APPROVED); - status10 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.GRANT_NOT_APPROVED, - ExternalStatus = "Declined", - InternalStatus = "Declined" - } - ); + await SeedApplicationStatusAsync(); + } - ApplicationStatus? status11 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.DEFER); - status11 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.DEFER, - ExternalStatus = "Deferred", - InternalStatus = "Deferred" - } - ); + + private async Task SeedApplicationStatusAsync() + { + var statuses = new List + { + new() { StatusCode = GrantApplicationState.SUBMITTED, ExternalStatus = GrantApplicationStates.SUBMITTED, InternalStatus = GrantApplicationStates.SUBMITTED }, + new() { StatusCode = GrantApplicationState.ASSIGNED, ExternalStatus = GrantApplicationStates.UNDER_REVIEW, InternalStatus = GrantApplicationStates.ASSIGNED }, + new() { StatusCode = GrantApplicationState.WITHDRAWN, ExternalStatus = GrantApplicationStates.WITHDRAWN, InternalStatus = GrantApplicationStates.WITHDRAWN }, + new() { StatusCode = GrantApplicationState.CLOSED, ExternalStatus = GrantApplicationStates.CLOSED, InternalStatus = GrantApplicationStates.CLOSED }, + new() { StatusCode = GrantApplicationState.UNDER_INITIAL_REVIEW, ExternalStatus = GrantApplicationStates.UNDER_REVIEW, InternalStatus = GrantApplicationStates.UNDER_INITIAL_REVIEW }, + new() { StatusCode = GrantApplicationState.INITITAL_REVIEW_COMPLETED, ExternalStatus = GrantApplicationStates.UNDER_REVIEW, InternalStatus = GrantApplicationStates.INITITAL_REVIEW_COMPLETED }, + new() { StatusCode = GrantApplicationState.UNDER_ASSESSMENT, ExternalStatus = GrantApplicationStates.UNDER_REVIEW, InternalStatus = GrantApplicationStates.UNDER_ASSESSMENT }, + new() { StatusCode = GrantApplicationState.ASSESSMENT_COMPLETED, ExternalStatus = GrantApplicationStates.UNDER_REVIEW, InternalStatus = GrantApplicationStates.ASSESSMENT_COMPLETED }, + new() { StatusCode = GrantApplicationState.GRANT_APPROVED, ExternalStatus = GrantApplicationStates.GRANT_APPROVED, InternalStatus = GrantApplicationStates.GRANT_APPROVED }, + new() { StatusCode = GrantApplicationState.GRANT_NOT_APPROVED, ExternalStatus = GrantApplicationStates.DECLINED, InternalStatus = GrantApplicationStates.DECLINED }, + new() { StatusCode = GrantApplicationState.DEFER, ExternalStatus = GrantApplicationStates.DEFER, InternalStatus = GrantApplicationStates.DEFER }, + new() { StatusCode = GrantApplicationState.ON_HOLD, ExternalStatus = GrantApplicationStates.ON_HOLD, InternalStatus = GrantApplicationStates.ON_HOLD }, + }; - ApplicationStatus? status12 = await _applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == GrantApplicationState.ON_HOLD); - status12 ??= await _applicationStatusRepository.InsertAsync( - new ApplicationStatus - { - StatusCode = GrantApplicationState.ON_HOLD, - ExternalStatus = "On Hold", - InternalStatus = "On Hold" - } - ); + foreach (var status in statuses) + { + var existing = await applicationStatusRepository.FirstOrDefaultAsync(s => s.StatusCode == status.StatusCode); + if (existing == null) + { + await applicationStatusRepository.InsertAsync(status); + } } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Integrations/DynamicUrlDataSeeder.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Integrations/DynamicUrlDataSeeder.cs new file mode 100644 index 000000000..a1c43db3e --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Integrations/DynamicUrlDataSeeder.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Unity.GrantManager.Applications; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Unity.GrantManager.Integrations +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(DynamicUrlDataSeeder), typeof(IDataSeedContributor))] + public class DynamicUrlDataSeeder(IDynamicUrlRepository DynamicUrlRepository, ICurrentTenant currentTenant) : IDataSeedContributor, ITransientDependency + { + + public async Task SeedAsync(DataSeedContext context) + { + await SeedDynamicUrlAsync(); + } + + public static class DynamicUrls + { + public const string PROTOCOL = "https:"; + public const string CHEFS_PROD_URL = $"{PROTOCOL}//submit.digital.gov.bc.ca/app/api/v1"; + public const string CAS_PROD_URL = $"{PROTOCOL}//cfs-systws.cas.gov.bc.ca:7026/ords/cas"; // Not entered for security reasons + public const string CHES_PROD_URL = $"{PROTOCOL}//ches.api.gov.bc.ca/api/v1"; + public const string CHES_PROD_AUTH = $"{PROTOCOL}//loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token"; + public const string ORGBOOK_PROD_URL = $"{PROTOCOL}//orgbook.gov.bc.ca/api"; + public const string CSS_API_BASE_URL = $"{PROTOCOL}//api.loginproxy.gov.bc.ca/api/v1"; + public const string CSS_TOKEN_API_BASE_URL = $"{PROTOCOL}//loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/token"; + public const string GEOCODER_BASE_URL = $"{PROTOCOL}//openmaps.gov.bc.ca/geo/pub/ows?service=WFS&version=1.0.0&request=GetFeature&typeName="; + public const string GEOCODER_LOCATION_BASE_URL = $"{PROTOCOL}//geocoder.api.gov.bc.ca"; + } + + private async Task SeedDynamicUrlAsync() + { + if (currentTenant == null || currentTenant.Id == null) + { + int messageIndex = 0; + int webhookIndex = 0; + var dynamicUrls = new List + { + new() { KeyName = DynamicUrlKeyNames.GEOCODER_API_BASE, Url = DynamicUrls.GEOCODER_BASE_URL, Description = "Geocoder API Base" }, + new() { KeyName = DynamicUrlKeyNames.GEOCODER_LOCATION_API_BASE, Url = DynamicUrls.GEOCODER_LOCATION_BASE_URL, Description = "Geocoder Location API Base" }, + new() { KeyName = DynamicUrlKeyNames.CSS_API_BASE, Url = DynamicUrls.CSS_API_BASE_URL, Description = "Common Single Sign-on Services API" }, + new() { KeyName = DynamicUrlKeyNames.CSS_TOKEN_API_BASE, Url = DynamicUrls.CSS_TOKEN_API_BASE_URL, Description = "Common Single Sign-on Token API" }, + new() { KeyName = DynamicUrlKeyNames.PAYMENT_API_BASE, Url = DynamicUrls.CAS_PROD_URL, Description = "BC Corporate Accounting Services API" }, + new() { KeyName = DynamicUrlKeyNames.ORGBOOK_API_BASE, Url = DynamicUrls.ORGBOOK_PROD_URL, Description = "OrgBook Services API" }, + new() { KeyName = DynamicUrlKeyNames.INTAKE_API_BASE, Url = DynamicUrls.CHEFS_PROD_URL, Description = "Common Hosted Forms Service API" }, + new() { KeyName = DynamicUrlKeyNames.NOTIFICATION_API_BASE, Url = DynamicUrls.CHES_PROD_URL, Description = "Common Hosted Email Service API" }, + new() { KeyName = DynamicUrlKeyNames.NOTIFICATION_AUTH, Url = DynamicUrls.CHES_PROD_AUTH, Description = "Common Hosted Email Service OAUTH" }, + new() { KeyName = $"{DynamicUrlKeyNames.DIRECT_MESSAGE_KEY_PREFIX}{messageIndex++}", Url = "", Description = $"Direct message webhook {messageIndex}" }, + new() { KeyName = $"{DynamicUrlKeyNames.DIRECT_MESSAGE_KEY_PREFIX}{messageIndex++}", Url = "", Description = $"Direct message webhook {messageIndex}" }, + new() { KeyName = $"{DynamicUrlKeyNames.DIRECT_MESSAGE_KEY_PREFIX}{messageIndex++}", Url = "", Description = $"Direct message webhook {messageIndex}" }, + new() { KeyName = $"{DynamicUrlKeyNames.WEBHOOK_KEY_PREFIX}{webhookIndex++}", Url = "", Description = $"Webhook {webhookIndex}" }, + new() { KeyName = $"{DynamicUrlKeyNames.WEBHOOK_KEY_PREFIX}{webhookIndex++}", Url = "", Description = $"Webhook {webhookIndex}" }, + new() { KeyName = $"{DynamicUrlKeyNames.WEBHOOK_KEY_PREFIX}{webhookIndex++}", Url = "", Description = $"Webhook {webhookIndex}" }, + }; + + foreach (var dynamicUrl in dynamicUrls) + { + var existing = await DynamicUrlRepository.FirstOrDefaultAsync(s => s.KeyName == dynamicUrl.KeyName); + if (existing == null) + { + await DynamicUrlRepository.InsertAsync(dynamicUrl); + } + } + } + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs index 47fbb5143..ed8b28f03 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs @@ -17,6 +17,7 @@ using Volo.Abp.TenantManagement.EntityFrameworkCore; using AppAny.Quartz.EntityFrameworkCore.Migrations; using AppAny.Quartz.EntityFrameworkCore.Migrations.PostgreSQL; +using Unity.GrantManager.Integrations; namespace Unity.GrantManager.EntityFrameworkCore; @@ -30,6 +31,7 @@ public class GrantManagerDbContext : { /* Add DbSet properties for your Aggregate Roots / Entities here. */ + public DbSet DynamicUrls { get; set; } public DbSet Sectors { get; set; } public DbSet SubSectors { get; set; } public DbSet EconomicRegion { get; set; } @@ -90,6 +92,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.AddQuartz(builder => builder.UsePostgreSql("qrtz_", null)); /* Configure your own tables/entities inside here */ + modelBuilder.Entity(b => + { + b.ToTable(GrantManagerConsts.DbTablePrefix + "DynamicUrls", + GrantManagerConsts.DbSchema); + b.HasKey(x => x.Id); + b.Property(x => x.KeyName).IsRequired().HasMaxLength(128); + b.Property(x => x.Url).IsRequired(); + b.Property(x => x.Description).HasMaxLength(256); + b.ConfigureByConvention(); + }); modelBuilder.Entity(b => { 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 41dc770a6..2f978759b 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantTenantDbContext.cs @@ -284,6 +284,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.ConfigureByConvention(); b.HasOne().WithMany().HasForeignKey(x => x.ApplicationId).IsRequired(); + + b.Property(x => x.LinkType) + .IsRequired() + .HasDefaultValue(ApplicationLinkType.Related) + .HasConversion(new EnumToStringConverter()); }); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.Designer.cs new file mode 100644 index 000000000..afdb96491 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.Designer.cs @@ -0,0 +1,2537 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.HostMigrations +{ + [DbContext(typeof(GrantManagerDbContext))] + [Migration("20250514165300_DynamicUrls")] + partial class DynamicUrls + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.ChefsMissedSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsSubmissionGuids") + .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"); + + b.HasKey("Id"); + + b.ToTable("ChefsMissedSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Community", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Communities", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.EconomicRegion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicRegionCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("EconomicRegionName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.HasKey("Id"); + + b.ToTable("EconomicRegions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.ElectoralDistrict", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ElectoralDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ElectoralDistrictName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.HasKey("Id"); + + b.ToTable("ElectoralDistricts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.RegionalDistrict", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("EconomicRegionCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("RegionalDistrictCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrictName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RegionalDistricts", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Sector", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SectorCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SectorName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sectors", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.SubSector", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SectorId") + .HasColumnType("uuid"); + + b.Property("SubSectorCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubSectorName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SectorId"); + + b.ToTable("SubSectors", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Tokens.TenantToken", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TenantTokens", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("character varying(96)") + .HasColumnName("ApplicationName"); + + b.Property("BrowserInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("BrowserInfo"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ClientId"); + + b.Property("ClientIpAddress") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ClientIpAddress"); + + b.Property("ClientName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ClientName"); + + b.Property("Comments") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Comments"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("CorrelationId"); + + b.Property("Exceptions") + .HasColumnType("text"); + + b.Property("ExecutionDuration") + .HasColumnType("integer") + .HasColumnName("ExecutionDuration"); + + b.Property("ExecutionTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("HttpMethod") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("HttpMethod"); + + b.Property("HttpStatusCode") + .HasColumnType("integer") + .HasColumnName("HttpStatusCode"); + + b.Property("ImpersonatorTenantId") + .HasColumnType("uuid") + .HasColumnName("ImpersonatorTenantId"); + + b.Property("ImpersonatorTenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("ImpersonatorTenantName"); + + b.Property("ImpersonatorUserId") + .HasColumnType("uuid") + .HasColumnName("ImpersonatorUserId"); + + b.Property("ImpersonatorUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("ImpersonatorUserName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("TenantName"); + + b.Property("Url") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Url"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("UserName"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AuditLogs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditLogId") + .HasColumnType("uuid") + .HasColumnName("AuditLogId"); + + b.Property("ExecutionDuration") + .HasColumnType("integer") + .HasColumnName("ExecutionDuration"); + + b.Property("ExecutionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("ExecutionTime"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("MethodName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("MethodName"); + + b.Property("Parameters") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("Parameters"); + + b.Property("ServiceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("ServiceName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AuditLogActions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditLogId") + .HasColumnType("uuid") + .HasColumnName("AuditLogId"); + + b.Property("ChangeTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("ChangeTime"); + + b.Property("ChangeType") + .HasColumnType("smallint") + .HasColumnName("ChangeType"); + + b.Property("EntityId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("EntityId"); + + b.Property("EntityTenantId") + .HasColumnType("uuid"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("EntityTypeFullName"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("EntityChanges", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EntityChangeId") + .HasColumnType("uuid"); + + b.Property("NewValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("NewValue"); + + b.Property("OriginalValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("OriginalValue"); + + b.Property("PropertyName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("PropertyName"); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("PropertyTypeFullName"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("EntityPropertyChanges", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsAbandoned") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JobArgs") + .IsRequired() + .HasMaxLength(1048576) + .HasColumnType("character varying(1048576)"); + + b.Property("JobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("LastTryTime") + .HasColumnType("timestamp without time zone"); + + b.Property("NextTryTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((byte)15); + + b.Property("TryCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.HasKey("Id"); + + b.HasIndex("IsAbandoned", "NextTryTime"); + + b.ToTable("BackgroundJobs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("boolean"); + + b.Property("IsVisibleToClients") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ValueType") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Features", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("FeatureGroups", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("FeatureValues", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Regex") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RegexDescription") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ValueType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ClaimTypes", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityLinkUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("SourceTenantId") + .HasColumnType("uuid"); + + b.Property("SourceUserId") + .HasColumnType("uuid"); + + b.Property("TargetTenantId") + .HasColumnType("uuid"); + + b.Property("TargetUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SourceUserId", "SourceTenantId", "TargetUserId", "TargetTenantId") + .IsUnique(); + + b.ToTable("LinkUsers", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("IsDefault"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("IsPublic"); + + b.Property("IsStatic") + .HasColumnType("boolean") + .HasColumnName("IsStatic"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClaimType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ClaimValue") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Action") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("ApplicationName") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("BrowserInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ClientIpAddress") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Identity") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TenantName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("SecurityLogs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySession", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Device") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("DeviceInfo") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("IpAddresses") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("SessionId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SignedIn") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Device"); + + b.HasIndex("SessionId"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("Sessions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("Email"); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("EmailConfirmed"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("IsActive"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsExternal"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastPasswordChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("LockoutEnabled"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Name"); + + b.Property("NormalizedEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("NormalizedEmail"); + + b.Property("NormalizedUserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("NormalizedUserName"); + + b.Property("OidcSub") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("PasswordHash"); + + b.Property("PhoneNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("PhoneNumber"); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("PhoneNumberConfirmed"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("SecurityStamp"); + + b.Property("ShouldChangePasswordOnNextLogin") + .HasColumnType("boolean"); + + b.Property("Surname") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Surname"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("TwoFactorEnabled"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("UserName"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClaimType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ClaimValue") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserDelegation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EndTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SourceUserId") + .HasColumnType("uuid"); + + b.Property("StartTime") + .HasColumnType("timestamp without time zone"); + + b.Property("TargetUserId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("UserDelegations", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderDisplayName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .IsRequired() + .HasMaxLength(196) + .HasColumnType("character varying(196)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("UserOrganizationUnits", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(95) + .HasColumnType("character varying(95)") + .HasColumnName("Code"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("OrganizationUnits", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("OrganizationUnitRoles", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("MultiTenancySide") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Providers") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("StateCheckers") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Permissions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("PermissionGrants", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("PermissionGroups", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ProviderKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ProviderName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey") + .IsUnique(); + + b.ToTable("Settings", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.SettingDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DefaultValue") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsEncrypted") + .HasColumnType("boolean"); + + b.Property("IsInherited") + .HasColumnType("boolean"); + + b.Property("IsVisibleToClients") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Providers") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("SettingDefinitions", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EntityVersion") + .HasColumnType("integer"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("NormalizedName"); + + b.ToTable("Tenants", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("TenantId", "Name"); + + b.ToTable("TenantConnectionStrings", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.SubSector", b => + { + b.HasOne("Unity.GrantManager.Locality.Sector", "Sector") + .WithMany("SubSectors") + .HasForeignKey("SectorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Sector"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("Unity.GrantManager.Locality.Sector", b => + { + b.Navigation("SubSectors"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Navigation("Actions"); + + b.Navigation("EntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Navigation("PropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Navigation("Claims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Navigation("Claims"); + + b.Navigation("Logins"); + + b.Navigation("OrganizationUnits"); + + b.Navigation("Roles"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Navigation("ConnectionStrings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.cs new file mode 100644 index 000000000..dd6924e54 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/20250514165300_DynamicUrls.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.HostMigrations +{ + /// + public partial class DynamicUrls : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DynamicUrls", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + KeyName = table.Column(type: "text", nullable: false), + Url = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true), + TenantId = table.Column(type: "uuid", nullable: true), + ExtraProperties = table.Column(type: "text", nullable: false, defaultValue: "{}"), + ConcurrencyStamp = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + CreationTime = table.Column(type: "timestamp without time zone", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: true), + LastModificationTime = table.Column(type: "timestamp without time zone", nullable: true), + LastModifierId = table.Column(type: "uuid", nullable: true), + IsDeleted = table.Column(type: "boolean", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uuid", nullable: true), + DeletionTime = table.Column(type: "timestamp without time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DynamicUrls", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DynamicUrls"); + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs index f75f52d54..fbaa4e0de 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/HostMigrations/GrantManagerDbContextModelSnapshot.cs @@ -706,6 +706,55 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Sectors", (string)null); }); + modelBuilder.Entity("Unity.GrantManager.Locality.DynamicUrl", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("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("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("KeyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DynamicUrls", (string)null); + }); + modelBuilder.Entity("Unity.GrantManager.Locality.SubSector", b => { b.Property("Id") diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819224936_FixSnapshotAgain_V3.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819224936_FixSnapshotAgain_V3.Designer.cs new file mode 100644 index 000000000..1752c6509 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819224936_FixSnapshotAgain_V3.Designer.cs @@ -0,0 +1,4233 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20250819224936_FixSnapshotAgain_V3")] + partial class FixSnapshotAgain_V3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("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("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationSize") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("OidcSubUser"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSumbissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("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("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("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("RenderFormIoToHtml") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("RenderedHTML") + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.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("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.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("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("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("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("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.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.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("OidcSubUser") + .HasPrincipalKey("OidcSub"); + + b.Navigation("Application"); + }); + + 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(); + }); + + 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() + .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.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.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.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("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/20250819224936_FixSnapshotAgain_V3.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819224936_FixSnapshotAgain_V3.cs new file mode 100644 index 000000000..69c852ccc --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819224936_FixSnapshotAgain_V3.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class FixSnapshotAgain_V3 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819225154_Add_ApplicationLinksType_Column.Designer.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819225154_Add_ApplicationLinksType_Column.Designer.cs new file mode 100644 index 000000000..1d542b825 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819225154_Add_ApplicationLinksType_Column.Designer.cs @@ -0,0 +1,4239 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Unity.GrantManager.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + [DbContext(typeof(GrantTenantDbContext))] + [Migration("20250819225154_Add_ApplicationLinksType_Column")] + partial class Add_ApplicationLinksType_Column + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.PostgreSql) + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("ScoresheetInstanceId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ScoresheetInstanceId"); + + b.ToTable("Answers", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("Questions", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Scoresheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ScoresheetId"); + + b.ToTable("ScoresheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.CustomFieldValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CustomFieldId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetInstanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetInstanceId"); + + b.ToTable("CustomFieldValues", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("CurrentValue") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetCorrelationId") + .HasColumnType("uuid"); + + b.Property("WorksheetCorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("WorksheetInstances", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetLinks.WorksheetLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UiAnchor") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetLinks", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.CustomField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Definition") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("SectionId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("CustomFields", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Worksheets", "Flex"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("WorksheetId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorksheetId"); + + b.ToTable("WorksheetSections", "Flex"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(600) + .HasColumnType("character varying(600)"); + + b.Property("ApproxNumberOfEmployees") + .HasColumnType("text"); + + b.Property("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("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FiscalDay") + .HasColumnType("integer"); + + b.Property("FiscalMonth") + .HasColumnType("text"); + + b.Property("IndigenousOrgInd") + .HasColumnType("text"); + + b.Property("IsDuplicated") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MatchPercentage") + .HasColumnType("numeric"); + + b.Property("NonRegOrgName") + .HasColumnType("text"); + + b.Property("NonRegisteredBusinessName") + .HasColumnType("text"); + + b.Property("OrgName") + .HasColumnType("text"); + + b.Property("OrgNumber") + .HasColumnType("text"); + + b.Property("OrgStatus") + .HasColumnType("text"); + + b.Property("OrganizationSize") + .HasColumnType("text"); + + b.Property("OrganizationType") + .HasColumnType("text"); + + b.Property("RedStop") + .HasColumnType("boolean"); + + b.Property("Sector") + .HasColumnType("text"); + + b.Property("SectorSubSectorIndustryDesc") + .HasColumnType("text"); + + b.Property("SiteId") + .HasColumnType("uuid"); + + b.Property("StartedOperatingDate") + .HasColumnType("date"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SubSector") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UnityApplicantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantName"); + + b.ToTable("Applicants", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAddress", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasColumnType("integer"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Postal") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Street") + .HasColumnType("text"); + + b.Property("Street2") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Unit") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicantAddresses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicantAgent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BceidBusinessGuid") + .HasColumnType("uuid"); + + b.Property("BceidBusinessName") + .HasColumnType("text"); + + b.Property("BceidUserGuid") + .HasColumnType("uuid"); + + b.Property("BceidUserName") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactOrder") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IdentityEmail") + .HasColumnType("text"); + + b.Property("IdentityName") + .HasColumnType("text"); + + b.Property("IdentityProvider") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSubUser") + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("Phone2") + .HasColumnType("text"); + + b.Property("Phone2Extension") + .HasColumnType("text"); + + b.Property("PhoneExtension") + .HasColumnType("text"); + + b.Property("RoleForApplicant") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationId") + .IsUnique(); + + b.HasIndex("OidcSubUser"); + + b.ToTable("ApplicantAgents", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Acquisition") + .HasColumnType("text"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationStatusId") + .HasColumnType("uuid"); + + b.Property("ApprovedAmount") + .HasColumnType("numeric"); + + b.Property("AssessmentResultDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AssessmentResultStatus") + .HasColumnType("text"); + + b.Property("AssessmentStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Community") + .HasColumnType("text"); + + b.Property("CommunityPopulation") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractExecutionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ContractNumber") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeclineRational") + .HasColumnType("text"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("DueDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DueDiligenceStatus") + .HasColumnType("text"); + + b.Property("EconomicRegion") + .HasColumnType("text"); + + b.Property("ElectoralDistrict") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FinalDecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Forestry") + .HasColumnType("text"); + + b.Property("ForestryFocus") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LikelihoodOfFunding") + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("NotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Payload") + .HasColumnType("jsonb"); + + b.Property("PercentageTotalProjectBudget") + .HasColumnType("double precision"); + + b.Property("Place") + .HasColumnType("text"); + + b.Property("ProjectEndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectFundingTotal") + .HasColumnType("numeric"); + + b.Property("ProjectName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectStartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ProjectSummary") + .HasColumnType("text"); + + b.Property("ProposalDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecommendedAmount") + .HasColumnType("numeric"); + + b.Property("ReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RegionalDistrict") + .HasColumnType("text"); + + b.Property("RequestedAmount") + .HasColumnType("numeric"); + + b.Property("RiskRanking") + .HasColumnType("text"); + + b.Property("SigningAuthorityBusinessPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityCellPhone") + .HasColumnType("text"); + + b.Property("SigningAuthorityEmail") + .HasColumnType("text"); + + b.Property("SigningAuthorityFullName") + .HasColumnType("text"); + + b.Property("SigningAuthorityTitle") + .HasColumnType("text"); + + b.Property("SubStatus") + .HasColumnType("text"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TotalProjectBudget") + .HasColumnType("numeric"); + + b.Property("TotalScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.HasIndex("ApplicationStatusId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Applications", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssigneeId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Duty") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("AssigneeId"); + + b.ToTable("ApplicationAssignments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationChefsFileAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsFileId") + .HasColumnType("text"); + + b.Property("ChefsSumbissionId") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationChefsFileAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationContact", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContactEmail") + .HasColumnType("text"); + + b.Property("ContactFullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactMobilePhone") + .HasColumnType("text"); + + b.Property("ContactTitle") + .HasColumnType("text"); + + b.Property("ContactType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContactWorkPhone") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationContact", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationForm", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasColumnType("text"); + + b.Property("ApplicationFormDescription") + .HasColumnType("text"); + + b.Property("ApplicationFormName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AttemptedConnectionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsCriteriaFormGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ConnectionHttpStatus") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("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("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("Payable") + .HasColumnType("boolean"); + + b.Property("PaymentApprovalThreshold") + .HasColumnType("numeric"); + + b.Property("PreventPayment") + .HasColumnType("boolean"); + + b.Property("RenderFormIoToHtml") + .HasColumnType("boolean"); + + b.Property("ScoresheetId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IntakeId"); + + b.ToTable("ApplicationForms", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormSubmission", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("ApplicationFormVersionId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChefsSubmissionGuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormVersionId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("RenderedHTML") + .HasColumnType("text"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Submission") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantId"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormSubmissions", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationFormVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationFormId") + .HasColumnType("uuid"); + + b.Property("AvailableChefsFields") + .HasColumnType("text"); + + b.Property("ChefsApplicationFormGuid") + .HasColumnType("text"); + + b.Property("ChefsFormVersionGuid") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FormSchema") + .HasColumnType("jsonb"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.Property("ReportColumns") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportKeys") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportViewName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionHeaderMapping") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormId"); + + b.ToTable("ApplicationFormVersion", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + + b.Property("LinkedApplicationId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationLinks", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationStatus", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StatusCode") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("StatusCode") + .IsUnique(); + + b.ToTable("ApplicationStatuses", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.ApplicationTags", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("TagId"); + + b.ToTable("ApplicationTags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.AssessmentAttachment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("S3ObjectKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.ToTable("AssessmentAttachments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.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("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.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("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("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("AssessmentId"); + + b.HasIndex("CommenterId"); + + b.ToTable("AssessmentComments", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.GlobalTag.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Tags", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Identity.Person", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Badge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("OidcDisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OidcSub") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("OidcSub"); + + b.ToTable("Persons", (string)null); + }); + + modelBuilder.Entity("Unity.GrantManager.Intakes.Intake", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Budget") + .HasColumnType("double precision"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EndDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IntakeName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("StartDate") + .HasColumnType("timestamp without time zone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Intakes", (string)null); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.EmailGroups.EmailGroupUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("EmailGroupUsers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Emails.EmailLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("AssessmentId") + .HasColumnType("uuid"); + + b.Property("BCC") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyType") + .IsRequired() + .HasColumnType("text"); + + b.Property("CC") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesMsgId") + .HasColumnType("uuid"); + + b.Property("ChesResponse") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChesStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("text"); + + b.Property("RetryAttempts") + .HasColumnType("integer"); + + b.Property("SendOnDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("SentDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("ToAddress") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EmailLogs", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.EmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BodyHTML") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyText") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendFrom") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("EmailTemplates", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Subscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Subscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionGroups", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriberId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("SubscriberId"); + + b.ToTable("SubscriptionGroupSubscribers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TemplateVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MapTo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TemplateVariables", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.Trigger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("InternalName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Triggers", "Notifications"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("SubscriptionGroupId") + .HasColumnType("uuid"); + + b.Property("TemplateId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("TriggerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionGroupId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("TriggerId"); + + b.ToTable("TriggerSubscriptions", "Notifications"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.AccountCodings.AccountCoding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("MinistryClient") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Responsibility") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServiceLine") + .IsRequired() + .HasColumnType("text"); + + b.Property("Stob") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AccountCodings", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentConfigurations.PaymentConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DefaultAccountCodingId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentIdPrefix") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("PaymentConfigurations", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DecisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("DecisionUserId") + .HasColumnType("uuid"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.ToTable("ExpenseApprovals", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountCodingId") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("BatchName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BatchNumber") + .HasColumnType("numeric"); + + b.Property("CasHttpStatusCode") + .HasColumnType("integer"); + + b.Property("CasResponse") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("ContractNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("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("ReferenceNumber") + .IsUnique(); + + b.HasIndex("SiteId"); + + b.ToTable("PaymentRequests", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("PaymentRequestId") + .HasColumnType("uuid"); + + b.Property("TagId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("PaymentRequestId"); + + b.HasIndex("TagId"); + + b.ToTable("PaymentTags", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentThresholds.PaymentThreshold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.Property("Threshold") + .HasColumnType("numeric"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("PaymentThresholds", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressLine1") + .HasColumnType("text"); + + b.Property("AddressLine2") + .HasColumnType("text"); + + b.Property("AddressLine3") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("Country") + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("EFTAdvicePref") + .HasColumnType("text"); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCas") + .HasColumnType("timestamp without time zone"); + + b.Property("MarkDeletedInUse") + .HasColumnType("boolean"); + + b.Property("Number") + .IsRequired() + .HasColumnType("text"); + + b.Property("PaymentGroup") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SiteProtected") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.ToTable("Sites", "Payments"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BusinessNumber") + .HasColumnType("text"); + + b.Property("City") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("CorrelationProvider") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uuid") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uuid") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("timestamp without time zone") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uuid") + .HasColumnName("LastModifierId"); + + b.Property("LastUpdatedInCAS") + .HasColumnType("timestamp without time zone"); + + b.Property("MailingAddress") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("text"); + + b.Property("PostalCode") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("SIN") + .HasColumnType("text"); + + b.Property("StandardIndustryClassification") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.Property("Subcategory") + .HasColumnType("text"); + + b.Property("SupplierProtected") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("Suppliers", "Payments"); + }); + + modelBuilder.Entity("Unity.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.HasOne("Unity.GrantManager.Identity.Person", null) + .WithMany() + .HasForeignKey("OidcSubUser") + .HasPrincipalKey("OidcSub"); + + b.Navigation("Application"); + }); + + 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(); + }); + + 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() + .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.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.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.Notifications.EmailGroups.EmailGroupUser", b => + { + b.HasOne("Unity.Notifications.EmailGroups.EmailGroup", null) + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.SubscriptionGroupSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("GroupId"); + + b.HasOne("Unity.Notifications.Templates.Subscriber", "Subscriber") + .WithMany() + .HasForeignKey("SubscriberId"); + + b.Navigation("Subscriber"); + + b.Navigation("SubscriptionGroup"); + }); + + modelBuilder.Entity("Unity.Notifications.Templates.TriggerSubscription", b => + { + b.HasOne("Unity.Notifications.Templates.SubscriptionGroup", "SubscriptionGroup") + .WithMany() + .HasForeignKey("SubscriptionGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.EmailTemplate", "EmailTemplate") + .WithMany() + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.Notifications.Templates.Trigger", "Trigger") + .WithMany() + .HasForeignKey("TriggerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmailTemplate"); + + b.Navigation("SubscriptionGroup"); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.ExpenseApproval", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", "PaymentRequest") + .WithMany("ExpenseApprovals") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PaymentRequest"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentRequests.PaymentRequest", b => + { + b.HasOne("Unity.Payments.Domain.AccountCodings.AccountCoding", "AccountCoding") + .WithMany() + .HasForeignKey("AccountCodingId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("Unity.Payments.Domain.Suppliers.Site", "Site") + .WithMany() + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("AccountCoding"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.PaymentTags.PaymentTag", b => + { + b.HasOne("Unity.Payments.Domain.PaymentRequests.PaymentRequest", null) + .WithMany("PaymentTags") + .HasForeignKey("PaymentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Unity.GrantManager.GlobalTag.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Unity.Payments.Domain.Suppliers.Site", b => + { + b.HasOne("Unity.Payments.Domain.Suppliers.Supplier", "Supplier") + .WithMany("Sites") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.ScoresheetInstances.ScoresheetInstance", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.Scoresheet", b => + { + b.Navigation("Instances"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Scoresheets.ScoresheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.WorksheetInstances.WorksheetInstance", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.Worksheet", b => + { + b.Navigation("Links"); + + b.Navigation("Sections"); + }); + + modelBuilder.Entity("Unity.Flex.Domain.Worksheets.WorksheetSection", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Applicant", b => + { + b.Navigation("ApplicantAddresses"); + }); + + modelBuilder.Entity("Unity.GrantManager.Applications.Application", b => + { + b.Navigation("ApplicantAddresses"); + + b.Navigation("ApplicantAgent"); + + b.Navigation("ApplicationAssignments"); + + b.Navigation("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/20250819225154_Add_ApplicationLinksType_Column.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819225154_Add_ApplicationLinksType_Column.cs new file mode 100644 index 000000000..ce7c2e8be --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Migrations/TenantMigrations/20250819225154_Add_ApplicationLinksType_Column.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Unity.GrantManager.Migrations.TenantMigrations +{ + /// + public partial class Add_ApplicationLinksType_Column : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LinkType", + table: "ApplicationLinks", + type: "text", + nullable: false, + defaultValue: "Related"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LinkType", + table: "ApplicationLinks"); + } + } +} 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 0f0cc39bc..e339ff024 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 @@ -1866,6 +1866,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("LastModifierId"); + b.Property("LinkType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Related"); + b.Property("LinkedApplicationId") .HasColumnType("uuid"); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs index 39e673a1c..2928e5ffc 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/ApplicationRepository.cs @@ -191,4 +191,4 @@ public override async Task> WithDetailsAsync() .AsNoTracking() // read?only; drop this line if you need tracking .FirstOrDefaultAsync(a => a.Id == id); } -} +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs new file mode 100644 index 000000000..e5f4c24d8 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/Repositories/DynamicUrlRepository.cs @@ -0,0 +1,19 @@ +using System; +using Unity.GrantManager.Applications; +using Unity.GrantManager.EntityFrameworkCore; +using Unity.GrantManager.Integrations; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Unity.GrantManager.Repositories +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IDynamicUrlRepository))] + public class DynamicUrlRepository : EfCoreRepository, IDynamicUrlRepository + { + public DynamicUrlRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/FormController.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/FormController.cs index e1888f74d..bf90e9424 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/FormController.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.HttpApi/Controllers/FormController.cs @@ -5,36 +5,25 @@ using System.Threading.Tasks; using Unity.GrantManager.ApplicationForms; using Unity.GrantManager.Applications; -using Unity.GrantManager.Integration.Chefs; using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Domain.Entities; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using Unity.GrantManager.Integrations.Chefs; namespace Unity.GrantManager.Controllers { [Route("/api/app")] - public partial class FormController : AbpController + public partial class FormController( + IApplicationFormRepository applicationFormRepository, + IApplicationFormSubmissionRepository applicationFormSubmissionRepository, + IApplicationFormVersionAppService applicationFormVersionAppService, + IFormsApiService formsApiService) : AbpController { - private readonly IApplicationFormRepository _applicationFormRepository; - private readonly IApplicationFormVersionAppService _applicationFormVersionAppService; - private readonly IApplicationFormSubmissionRepository _applicationFormSubmissionRepository; - private readonly IFormsApiService _formsApiService; - - protected ILogger logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - - public FormController( - IApplicationFormRepository applicationFormRepository, - IApplicationFormSubmissionRepository applicationFormSubmissionRepository, - IApplicationFormVersionAppService applicationFormVersionAppService, - IFormsApiService formsApiService) - { - _applicationFormSubmissionRepository = applicationFormSubmissionRepository; - _applicationFormRepository = applicationFormRepository; - _applicationFormVersionAppService = applicationFormVersionAppService; - _formsApiService = formsApiService; - } + private readonly IApplicationFormRepository _applicationFormRepository = applicationFormRepository; + private readonly IApplicationFormVersionAppService _applicationFormVersionAppService = applicationFormVersionAppService; + private readonly IApplicationFormSubmissionRepository _applicationFormSubmissionRepository = applicationFormSubmissionRepository; + private readonly IFormsApiService _formsApiService = formsApiService; [HttpPost("form/{formId}/version/{formVersionId}")] public async Task SynchronizeChefsAvailableFields(string formId, string formVersionId) @@ -62,11 +51,13 @@ public async Task SynchronizeChefsAvailableFields(string formId, throw new BusinessException("Application Form API Key is Required"); } - var chefsFormVersion = await _formsApiService.GetFormDataAsync(formId, formVersionId); - + var chefsFormVersion = await _formsApiService.GetFormDataAsync(formId, formVersionId) + ?? throw new BusinessException("Chefs Form Version data could not be retrieved."); + + var result = await _applicationFormVersionAppService - .UpdateOrCreateApplicationFormVersion(formId, formVersionId, applicationForm.Id, chefsFormVersion); - + .UpdateOrCreateApplicationFormVersion(formId, formVersionId, applicationForm.Id, chefsFormVersion); + return Ok(result); } catch (EntityNotFoundException ex) @@ -80,7 +71,7 @@ public async Task SynchronizeChefsAvailableFields(string formId, catch (Exception ex) { string ExceptionMessage = ex.Message; - logger.LogError(ex, "FormController->SynchronizeChefsAvailableFields: {ExceptionMessage}", ExceptionMessage); + Logger.LogError(ex, "FormController->SynchronizeChefsAvailableFields: {ExceptionMessage}", ExceptionMessage); return StatusCode(500, "An error occurred while processing your request."); } } @@ -99,7 +90,7 @@ public async Task StoreSubmissionHtml([FromBody] ApplicationSubmi { if (!ModelState.IsValid) { - logger.LogWarning("Invalid model state for StoreSubmissionHtml"); + Logger.LogWarning("Invalid model state for StoreSubmissionHtml"); return BadRequest(ModelState); } @@ -121,7 +112,7 @@ public async Task StoreSubmissionHtml([FromBody] ApplicationSubmi catch (Exception ex) { string ExceptionMessage = ex.Message; - logger.LogError(ex, "FormController->StoreSubmissionHtml: {ExceptionMessage}", ExceptionMessage); + Logger.LogError(ex, "FormController->StoreSubmissionHtml: {ExceptionMessage}", ExceptionMessage); return StatusCode(500, "An error occurred while processing your request."); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs index 9886f8e7b..ab17dea52 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs @@ -12,7 +12,7 @@ namespace Unity.GrantManager.Web.Identity [ExposeServices(typeof(ICurrentUser))] public class CurrentUser : ICurrentUser, ITransientDependency { - private static readonly Claim[] EmptyClaimsArray = Array.Empty(); + private static readonly Claim[] EmptyClaimsArray = []; public virtual bool IsAuthenticated => Id.HasValue; @@ -38,7 +38,7 @@ public class CurrentUser : ICurrentUser, ITransientDependency private string[] FindRoleClaims() { - return FindClaims(UnityClaimsTypes.Role).Select(c => c.Value).Distinct().ToArray(); + return [.. FindClaims(UnityClaimsTypes.Role).Select(c => c.Value).Distinct()]; } private readonly ICurrentPrincipalAccessor _principalAccessor; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs index 8bb88ea44..c80469dbd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs @@ -27,7 +27,7 @@ internal class IdentityProfileLoginAdminHandler : IdentityProfileLoginBase IdentityPermissions.UserLookup.Default, IdentityConsts.ITAdminPermissionName ); - + internal async Task Handle(TokenValidatedContext validatedTokenContext, IList userTenantAccounts, string? idp) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs index 875934589..a13314a84 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs @@ -11,6 +11,7 @@ using Unity.GrantManager.Identity; using Unity.GrantManager.Permissions; using Unity.GrantManager.Web.Exceptions; +using Unity.Modules.Shared.Permissions; using Volo.Abp.Identity; using Volo.Abp.PermissionManagement; using Volo.Abp.Security.Claims; @@ -19,8 +20,15 @@ namespace Unity.GrantManager.Web.Identity.LoginHandlers { internal class IdentityProfileLoginUserHandler : IdentityProfileLoginBase { - internal readonly ImmutableArray _userPermissions = ImmutableArray - .Create(GrantManagerPermissions.Default, IdentityPermissions.UserLookup.Default); + internal readonly ImmutableArray _userPermissions = [ + GrantManagerPermissions.Default, + IdentityPermissions.UserLookup.Default + ]; + + internal readonly ImmutableArray _itOperationsPermissions = [ + GrantManagerPermissions.Endpoints.ManageEndpoints, + IdentityConsts.ITOperationsPermissionName + ]; internal async Task Handle(TokenValidatedContext validatedTokenContext, IList? userTenantAccounts, @@ -48,6 +56,11 @@ internal async Task Handle(TokenValidatedContext validated throw new NoGrantProgramsLinkedException("User is not linked to any grant programs"); } } + + if (validatedTokenContext.Principal != null && validatedTokenContext.Principal.IsInRole(IdentityConsts.ITOperationsRoleName)) + { + AssignITOperationsPermissions(validatedTokenContext.Principal); + } UserTenantAccountDto? userTenantAccount = null; var setTenant = validatedTokenContext.Request.Cookies["set_tenant"]; @@ -76,12 +89,11 @@ internal async Task Handle(TokenValidatedContext validated var userPermissions = (await PermissionManager.GetAllForUserAsync(userTenantAccount.Id)).Where(s => s.IsGranted); - foreach (var permissionName in userPermissions.Select(s => s.Name)) + foreach (var permissionName in userPermissions + .Select(s => s.Name) + .Where(permissionName => !principal.HasClaim(UnityClaimsTypes.Permission, permissionName))) { - if (!principal.HasClaim(UnityClaimsTypes.Permission, permissionName)) - { - principal.AddClaim(UnityClaimsTypes.Permission, permissionName); - } + principal.AddClaim(UnityClaimsTypes.Permission, permissionName); } } @@ -93,6 +105,11 @@ internal async Task Handle(TokenValidatedContext validated return userTenantAccount; } + private void AssignITOperationsPermissions(ClaimsPrincipal claimsPrincipal) + { + claimsPrincipal.AddPermissions(_itOperationsPermissions); + } + private async Task> AutoRegisterUserWithDefaultAsync(string userIdentifier, string username, string firstName, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs index 5da56c246..292e7d38a 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs @@ -193,6 +193,13 @@ internal static void Register(ServiceConfigurationContext context) context.User.HasClaim(c => c.Type == PermissionConstant && c.Value == IdentityConsts.ITAdminPermissionName) )); + // IT Operations Policies + authorizationBuilder.AddPolicy(IdentityConsts.ITOperationsPolicyName, + policy => policy.RequireAssertion(context => + context.User.IsInRole(IdentityConsts.ITOperationsRoleName) || + context.User.HasClaim(c => c.Type == PermissionConstant && c.Value == IdentityConsts.ITOperationsPermissionName) + )); + // Project Info Policies authorizationBuilder.AddPolicy(UnitySelector.Project.Default, policy => policy.RequireClaim(PermissionConstant, UnitySelector.Project.Default)); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs index 00ab97c38..0811ad138 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs @@ -2,6 +2,7 @@ using Unity.GrantManager.Localization; using Unity.GrantManager.Permissions; using Unity.Identity.Web.Navigation; +using Unity.Modules.Shared.Permissions; using Unity.TenantManagement; using Unity.TenantManagement.Web.Navigation; using Volo.Abp.Identity; @@ -91,7 +92,19 @@ private static Task ConfigureMainMenuAsync(MenuConfigurationContext context) requiredPermissionName: GrantApplicationPermissions.Dashboard.Default ) ); + // Displayed in the Grant Manager - Used at Tenant Level if the user in the IT Operations role + context.Menu.AddItem( + new ApplicationMenuItem( + GrantManagerMenus.EndpointManagement, + displayName: "Endpoints", + "~/EndpointManagement/Endpoints", + requiredPermissionName: IdentityConsts.ITOperationsPermissionName + ) + ); + + // ******************** + // Admin - Tenant Management context.Menu.AddItem( new ApplicationMenuItem( TenantManagementMenuNames.Tenants, @@ -103,6 +116,17 @@ private static Task ConfigureMainMenuAsync(MenuConfigurationContext context) ) ); + // Displayed on the Tenant Managment area if the user has the ITAdministrator Role + context.Menu.AddItem( + new ApplicationMenuItem( + GrantManagerMenus.EndpointManagement, + displayName: "Endpoints", + "~/EndpointManagement/Endpoints", + requiredPermissionName: TenantManagementPermissions.Tenants.Default + ) + ); + + // End Admin ******************** #pragma warning disable S125 // Sections of code should not be commented out /* - will complete later after fixing ui sub menu issue */ //var administration = context.Menu.GetAdministration(); diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs index 07b33690b..ff26a1487 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs @@ -16,4 +16,5 @@ public static class GrantManagerMenus public const string Welcome = Prefix + ".Welcome"; public const string Intakes = Prefix + ".Intakes"; public const string ApplicationForms = Prefix + ".ApplicationForms"; + public const string EndpointManagement = Prefix + ".EndpointManagement"; } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml.cs index 54f447565..5f9d1ecee 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.cshtml.cs @@ -77,7 +77,7 @@ public async Task OnGetAsync() { ApplicationFormDto = await _applicationFormAppService.GetAsync(ApplicationId); ScoresheetId = ApplicationFormDto.ScoresheetId; - ApplicationFormVersionDtoList = (List?)await _applicationFormAppService.GetVersionsAsync(ApplicationFormDto.Id); + ApplicationFormVersionDtoList = (List?) await _applicationFormAppService.GetVersionsAsync(ApplicationFormDto.Id); FlexEnabled = await _featureChecker.IsEnabledAsync("Unity.Flex"); if (ApplicationFormVersionDtoList != null) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml index 018a8766a..64aa5e04e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml @@ -1,25 +1,82 @@ @page @model Unity.GrantManager.Web.Pages.ApplicationLinks.ApplicationLinksModalModel @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Unity.GrantManager.Applications @{ Layout = null; } + + + +
- + -
- - - - - - -
+ +
+

You are adding a link from:

+ +
+ + +
+ + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ + +

+ + Note: Cannot link the submissions that are already connected as either a child or parent to an existing submission. +

+ + + + + + + + +
- +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml.cs index ff444e604..cafe5f71c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.cshtml.cs @@ -6,9 +6,20 @@ using System.ComponentModel; using System.Linq; using System.Threading.Tasks; +using Unity.GrantManager.Applications; using Unity.GrantManager.GrantApplications; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +namespace Unity.GrantManager.Web.Pages.ApplicationLinks +{ + public class LinkWithType + { + public string ReferenceNumber { get; set; } = string.Empty; + public string ProjectName { get; set; } = string.Empty; + public ApplicationLinkType LinkType { get; set; } = ApplicationLinkType.Related; + } +} + namespace Unity.GrantManager.Web.Pages.ApplicationLinks { public class ApplicationLinksModalModel : AbpPageModel @@ -49,6 +60,11 @@ public class ApplicationLinksModalModel : AbpPageModel [BindProperty] public Guid? CurrentApplicationId { get; set; } + [BindProperty] + public string? LinksWithTypes { get; set; } = string.Empty; + + public ApplicationLinksInfoDto? CurrentApplication { get; set; } + public ApplicationLinksModalModel(IApplicationLinksService applicationLinksService, IGrantApplicationAppService grantApplicationAppService) { _applicationLinksService = applicationLinksService ?? throw new ArgumentNullException(nameof(applicationLinksService)); @@ -60,6 +76,10 @@ public async Task OnGetAsync(Guid applicationId) try { CurrentApplicationId = applicationId; + + // Get current application info for display + CurrentApplication = await _applicationLinksService.GetCurrentApplicationInfoAsync(applicationId); + var grantApplications = await _grantApplicationAppService.GetAllApplicationsAsync(); var tempGrantApplications = new List(grantApplications); var currentApplication = tempGrantApplications.Single(item => item.Id == applicationId); @@ -70,7 +90,7 @@ public async Task OnGetAsync(Guid applicationId) // remove current application id from ths suggestion list tempGrantApplications.Remove(currentApplication); - var formattedAllApplications = tempGrantApplications.Select(item => item.ReferenceNo + " - " + item.ProjectName).ToList(); + var formattedAllApplications = tempGrantApplications.Select(item => item.ReferenceNo + " - " + item.ApplicantName).ToList(); var formattedLinkedApplications = filteredLinkedApplications.Select(item => item.ReferenceNumber + " - " + item.ProjectName).ToList(); AllApplications = string.Join(",", formattedAllApplications); @@ -81,7 +101,7 @@ public async Task OnGetAsync(Guid applicationId) } catch (Exception ex) { - Logger.LogError(ex, message: "Error loading tag select list"); + Logger.LogError(ex, message: "Error loading application links modal data"); } } @@ -89,55 +109,116 @@ public async Task OnPostAsync() { try { - if (SelectedApplications != null) { - string[]? selectedApplicationsArray = JsonConvert.DeserializeObject(SelectedApplications); + if (!string.IsNullOrEmpty(LinksWithTypes)) + { + List? selectedLinksWithTypes = JsonConvert.DeserializeObject>(LinksWithTypes); List? grantApplications = JsonConvert.DeserializeObject>(GrantApplicationsList!); List? linkedApplications = JsonConvert.DeserializeObject>(LinkedApplicationsList!); - foreach (var item in selectedApplicationsArray!) + if (selectedLinksWithTypes != null && grantApplications != null && linkedApplications != null) { - var itemArr = item.Split('-'); - var referenceNo = itemArr[0].Trim(); - ApplicationLinksInfoDto applicationLinksInfoDto = linkedApplications!.Find(application => application.ReferenceNumber == referenceNo)!; - - // Add new link only if its not already existing - if (applicationLinksInfoDto == null) { - Guid linkedApplicationId = grantApplications!.Find(application => application.ReferenceNo == referenceNo)!.Id; - - //For CurrentApplication - await _applicationLinksService.CreateAsync(new ApplicationLinksDto{ - ApplicationId = CurrentApplicationId ?? Guid.Empty, - LinkedApplicationId = linkedApplicationId - }); - - //For LinkedApplication - await _applicationLinksService.CreateAsync(new ApplicationLinksDto + // Add new links and update existing ones + foreach (var linkWithType in selectedLinksWithTypes) + { + var existingLink = linkedApplications.Find(app => app.ReferenceNumber == linkWithType.ReferenceNumber); + + if (existingLink == null) + { + // Add new link + var targetApplication = grantApplications.Find(app => app.ReferenceNo == linkWithType.ReferenceNumber); + if (targetApplication != null) + { + var linkedApplicationId = targetApplication.Id; + + // For CurrentApplication -> LinkedApplication + await _applicationLinksService.CreateAsync(new ApplicationLinksDto + { + ApplicationId = CurrentApplicationId ?? Guid.Empty, + LinkedApplicationId = linkedApplicationId, + LinkType = linkWithType.LinkType + }); + + // For LinkedApplication -> CurrentApplication (reverse link with appropriate type) + var reverseLinkType = GetReverseLinkType(linkWithType.LinkType); + await _applicationLinksService.CreateAsync(new ApplicationLinksDto + { + ApplicationId = linkedApplicationId, + LinkedApplicationId = CurrentApplicationId ?? Guid.Empty, + LinkType = reverseLinkType + }); + } + } + else { - ApplicationId = linkedApplicationId, - LinkedApplicationId = CurrentApplicationId ?? Guid.Empty - }); + // Check if the link type has changed + if (existingLink.LinkType != linkWithType.LinkType) + { + // Update the existing link's type + await _applicationLinksService.UpdateLinkTypeAsync(existingLink.Id, linkWithType.LinkType); + + // Also update the reverse link + var reverseLink = await _applicationLinksService.GetLinkedApplicationAsync(CurrentApplicationId ?? Guid.Empty, existingLink.ApplicationId); + var reverseLinkType = GetReverseLinkType(linkWithType.LinkType); + await _applicationLinksService.UpdateLinkTypeAsync(reverseLink.Id, reverseLinkType); + + Logger.LogInformation("Updated link type for {ReferenceNumber} from {OldType} to {NewType}", + linkWithType.ReferenceNumber, existingLink.LinkType, linkWithType.LinkType); + } + } } - } - // For removing the deleted links - foreach (ApplicationLinksInfoDto linked in linkedApplications!) - { - var selectedIndex = selectedApplicationsArray!.FindIndex(selected => selected.Split('-')[0].Trim() == linked.ReferenceNumber); - if(selectedIndex < 0) { - await _applicationLinksService.DeleteAsync(linked.Id); - - var linkApp = await _applicationLinksService.GetLinkedApplicationAsync(CurrentApplicationId ?? Guid.Empty, linked.ApplicationId); - await _applicationLinksService.DeleteAsync(linkApp.Id); + // Remove deleted links + foreach (var linkedApp in linkedApplications) + { + var stillSelected = selectedLinksWithTypes.Any(selected => selected.ReferenceNumber == linkedApp.ReferenceNumber); + if (!stillSelected) + { + await _applicationLinksService.DeleteAsync(linkedApp.Id); + + var reverseLink = await _applicationLinksService.GetLinkedApplicationAsync(CurrentApplicationId ?? Guid.Empty, linkedApp.ApplicationId); + await _applicationLinksService.DeleteAsync(reverseLink.Id); + } } } } } catch (Exception ex) { - Logger.LogError(ex, message: "Error updating application tags"); + Logger.LogError(ex, message: "Error updating application links"); } - return NoContent(); + return new JsonResult(new { success = true }); + } + + private static ApplicationLinkType GetReverseLinkType(ApplicationLinkType linkType) + { + return linkType switch + { + ApplicationLinkType.Parent => ApplicationLinkType.Child, + ApplicationLinkType.Child => ApplicationLinkType.Parent, + ApplicationLinkType.Related => ApplicationLinkType.Related, + _ => ApplicationLinkType.Related + }; + } + + public async Task OnGetApplicationDetailsByReferenceAsync(string referenceNumber) + { + try + { + var details = await _applicationLinksService.GetApplicationDetailsByReferenceAsync(referenceNumber); + return new JsonResult(details); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting application details for reference number: {ReferenceNumber}", referenceNumber); + return new JsonResult(new ApplicationLinksInfoDto + { + ReferenceNumber = referenceNumber, + ApplicantName = "Error loading", + Category = "Error loading", + ApplicationStatus = "Error loading" + }); + } } } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.css new file mode 100644 index 000000000..b9903cfdd --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationLinks/ApplicationLinksModal.css @@ -0,0 +1,272 @@ +/* ApplicationLinksModal CSS */ + + +.links-display-area { + background-color: #ffffff; + border: 1px solid #dee2e6; + min-height: 80px; + max-height: 200px; /* Reduced from 300px to 200px */ + overflow-y: auto; +} + +.link-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + margin-bottom: 8px; + border-radius: 4px; + background-color: #f8f9fa; + border: 1px solid #e9ecef; +} + +.link-item:last-child { + margin-bottom: 0; +} + +/* All link items have the same background - matching disabled input fields */ +.link-item.child, +.link-item.parent, +.link-item.related { + background-color: var(--bc-colors-grey-hover); + border-color: var(--bs-border-color); +} + +.link-info { + flex: 1; +} + +.link-reference { + font-weight: bold; + color: #495057; + margin-right: 8px; +} + +.link-applicant { + color: #6c757d; + margin-right: 8px; +} + +.link-category { + color: #6c757d; + font-style: italic; +} + +/* Application status styling - complete styling to match other components */ +.link-item .application-status { + display: inline-block; + margin-left: 8px; + padding: 0.125rem 0.5rem; + font-size: 0.8rem; + font-weight: 700; + color: var(--bc-colors-blue-text-links); + text-transform: uppercase; + border: 3px solid var(--bc-colors-blue-text-links); + border-radius: 1rem; +} + +.link-type-badge { + display: inline-block; + margin-left: auto; + margin-right: 10px; + padding: 3px 12px; + border-radius: 40px; + font-size: 0.8rem; + font-weight: 700; + text-transform: uppercase; + background-color: #013366; + color: white; +} + +.link-delete-btn { + background: none; + border: none; + color: #0066cc; + cursor: pointer; + font-size: 18px; + padding: 4px 8px; + border-radius: 4px; + transition: background-color 0.2s; +} + +.link-delete-btn:hover { + background-color: #f8f9fa; + color: #004494; +} + +.link-delete-btn:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; +} + +/* Auto-suggest dropdown styling */ +.links-suggestion-container { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 1000; + background: white; + border: 1px solid #dee2e6; + border-top: none; + border-radius: 0 0 4px 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + max-height: 200px; + overflow-y: auto; +} + +.links-suggestion-title { + padding: 8px 12px; + background-color: #f8f9fa; + color: #6c757d; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + border-bottom: 1px solid #dee2e6; +} + +.links-suggestion-element { + padding: 10px 12px; + cursor: pointer; + border-bottom: 1px solid #f8f9fa; + transition: background-color 0.2s; +} + +.links-suggestion-element:hover, +.links-suggestion-element.suggestion-active { + background-color: #e3f2fd; + color: #1976d2; +} + +.links-suggestion-element:last-child { + border-bottom: none; +} + +/* Search input container */ +.search-input-container { + position: relative; +} + +/* Empty state styling */ +#noLinksMessage { + text-align: center; + color: #6c757d; + padding: 20px; + font-style: italic; +} + +/* Modal adjustments */ +#applicationLinksModal .modal-dialog { + max-width: 800px; /* Increased from default 500px to allow more space for links */ + max-height: 90vh; /* Ensure modal fits within viewport height */ +} + +#applicationLinksModal .modal-content { + max-height: 90vh; /* Ensure content area fits within viewport */ + overflow: hidden; /* Prevent modal content overflow */ +} + +#applicationLinksModal .modal-body { + overflow-y: auto; /* Allow scrolling within modal body if needed */ + max-height: 70vh; /* Limit body height to ensure buttons remain visible */ + padding: 1.5rem; +} + +#applicationLinksModal .form-label { + font-weight: 600; + color: #495057; + margin-bottom: 0.5rem; +} + + +/* Loading state styling */ +.link-item.loading { + opacity: 0.7; +} + +.loading-text { + color: #6c757d; + font-style: italic; +} + +@keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 1; } + 100% { opacity: 0.6; } +} + +.link-item.loading .loading-text { + animation: pulse 1.5s ease-in-out infinite; +} + +/* Error state styling */ +.link-item.error { + background-color: #fff3cd; + border-color: #ffeaa7; +} + +.link-error-icon { + color: #856404; + cursor: pointer; + font-size: 14px; + transition: color 0.2s; +} + +.link-error-icon:hover { + color: #533f03; +} + +/* New item styling */ +@keyframes slideInFade { + 0% { + opacity: 0; + transform: translateY(-10px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.link-item.new-item { + position: relative; + animation: slideInFade 0.3s ease-out; +} + +/* NEW badge styling - styled like application-status but with green colors */ +.badge.bg-success { + padding: 0.125rem 0.5rem !important; + font-size: 0.8rem !important; + font-weight: 700 !important; + color: #28a745 !important; + text-transform: uppercase !important; + border: 3px solid #28a745 !important; + border-radius: 1rem !important; + background-color: transparent !important; +} + + +/* Responsive adjustments */ +@media (max-width: 768px) { + .link-item { + flex-direction: column; + align-items: flex-start; + } + + .link-type-badge { + margin-left: 0; + margin-right: 0; + margin-top: 8px; + } + + .link-delete-btn { + align-self: flex-end; + margin-top: 8px; + } + + .badge.bg-success, + .link-error-icon { + margin-left: 0; + margin-top: 4px; + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js index 9306c08d6..d5bb4d1bd 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js @@ -913,7 +913,9 @@ function updateCommentsCounters() { function updateLinksCounters() { setTimeout(() => { $('.links-container').map(function () { - $('#' + $(this).data('linkscounttag')).html($(this).data('count')); + const tag = $(this).data('linkscounttag'); + const count = $(this).attr('data-count'); + $('#' + tag).text(count); }).get(); }, 100); } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/ApplicationLinksWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/ApplicationLinksWidgetViewComponent.cs index 9c355d760..756b6706d 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/ApplicationLinksWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/ApplicationLinksWidgetViewComponent.cs @@ -26,10 +26,9 @@ public ApplicationLinksWidgetViewComponent(IApplicationLinksService applicationL public async Task InvokeAsync(Guid applicationId) { - var applicationList = await _applicationLinksService.GetListByApplicationAsync(applicationId); - List applicationLinks = applicationList.Where(item => item.ApplicationId != applicationId).ToList(); + // DataTables will load the data via AJAX, so we don't need to pre-load it here ApplicationLinksWidgetViewModel model = new() { - ApplicationLinks = applicationLinks, + ApplicationLinks = new List(), // Empty list since DataTables will load the data ApplicationId = applicationId }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/Default.cshtml index b3c026aa9..7cf9629a8 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicationLinksWidget/Default.cshtml @@ -4,33 +4,15 @@ @{ Layout = null; } -