diff --git a/applications/Unity.AutoUI/package-lock.json b/applications/Unity.AutoUI/package-lock.json index 59414612a..1fe73965d 100644 --- a/applications/Unity.AutoUI/package-lock.json +++ b/applications/Unity.AutoUI/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "Unity.AutoUI", "dependencies": { "cypress": "^13.13.1", "typescript": "^5.5.4" diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs index 3708cad7c..4b13baea0 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Scoresheets/QuestionDto.cs @@ -22,22 +22,74 @@ public class QuestionDto : ExtensibleEntityDto public string? GetMin() { - return JsonSerializer.Deserialize(Definition ?? "{}")?.Min.ToString(); + try + { + var def = JsonSerializer.Deserialize(Definition ?? "{}"); + if (def?.Min == null) + return null; + // Only allow Int64 values + if (long.TryParse(def.Min.ToString(), out var minValue)) + return minValue.ToString(); + return null; + } + catch + { + return null; + } } public string? GetMax() { - return JsonSerializer.Deserialize(Definition ?? "{}")?.Max.ToString(); + try + { + var def = JsonSerializer.Deserialize(Definition ?? "{}"); + if (def?.Max == null) + return null; + // Only allow Int64 values + if (long.TryParse(def.Max.ToString(), out var maxValue)) + return maxValue.ToString(); + return null; + } + catch + { + return null; + } } public string? GetMinLength() { - return JsonSerializer.Deserialize(Definition ?? "{}")?.MinLength.ToString(); + try + { + var def = JsonSerializer.Deserialize(Definition ?? "{}"); + if (def?.MinLength == null) + return null; + // Only allow Int64 values + if (long.TryParse(def.MinLength.ToString(), out var minLength)) + return minLength.ToString(); + return null; + } + catch + { + return null; + } } public string? GetMaxLength() { - return JsonSerializer.Deserialize(Definition ?? "{}")?.MaxLength.ToString(); + try + { + var def = JsonSerializer.Deserialize(Definition ?? "{}"); + if (def?.MaxLength == null) + return null; + // Only allow Int64 values + if (long.TryParse(def.MaxLength.ToString(), out var maxLength)) + return maxLength.ToString(); + return null; + } + catch + { + return null; + } } public string? GetYesValue() diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs index 22b68bdb8..e96a7ce99 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/FlexTypeExtensions.cs @@ -57,15 +57,27 @@ public static string ConvertInputType(this CustomFieldType type) public static CustomFieldDefinition? ConvertDefinition(this string definition, QuestionType type) { - return type switch + if (string.IsNullOrWhiteSpace(definition)) { - QuestionType.Text => JsonSerializer.Deserialize(definition), - QuestionType.Number => JsonSerializer.Deserialize(definition), - QuestionType.YesNo => JsonSerializer.Deserialize(definition), - QuestionType.SelectList => JsonSerializer.Deserialize(definition), - QuestionType.TextArea => JsonSerializer.Deserialize(definition), - _ => null, - }; + return null; + } + + try + { + return type switch + { + QuestionType.Text => JsonSerializer.Deserialize(definition), + QuestionType.Number => JsonSerializer.Deserialize(definition), + QuestionType.YesNo => JsonSerializer.Deserialize(definition), + QuestionType.SelectList => JsonSerializer.Deserialize(definition), + QuestionType.TextArea => JsonSerializer.Deserialize(definition), + _ => null, + }; + } + catch (JsonException) + { + return null; + } } public static string[] GetCheckedOptions(this string value) diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/NumericDefinitionWidget.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/NumericDefinitionWidget.cs index b8a1e9d89..f0d4745c1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/NumericDefinitionWidget.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Views/Shared/Components/NumericDefinitionWidget/NumericDefinitionWidget.cs @@ -29,23 +29,36 @@ public class NumericDefinitionWidget : AbpViewComponent .ApplyRequired(form); } + // Cache JsonSerializerOptions instance + private static readonly JsonSerializerOptions CachedJsonOptions = new JsonSerializerOptions + { + NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString + }; + public async Task InvokeAsync(string? definition) { - if (definition != null) + NumericDefinitionViewModel viewModel = new(); + + if (!string.IsNullOrWhiteSpace(definition)) { - NumericDefinition? numericDefinition = JsonSerializer.Deserialize(definition); - if (numericDefinition != null) + try { - return View(await Task.FromResult(new NumericDefinitionViewModel() + var numericDefinition = JsonSerializer.Deserialize(definition, CachedJsonOptions); + + if (numericDefinition != null) { - Min = numericDefinition.Min, - Max = numericDefinition.Max, - Required = numericDefinition.Required - })); + viewModel.Min = numericDefinition.Min; + viewModel.Max = numericDefinition.Max; + viewModel.Required = numericDefinition.Required; + } + } + catch (JsonException) + { + // Optionally log the error } } - return View(await Task.FromResult(new NumericDefinitionViewModel())); + return View(await Task.FromResult(viewModel)); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/EmailGroupUsersDto.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/EmailGroupUsersDto.cs new file mode 100644 index 000000000..a4ef1d5b4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/EmailGroupUsersDto.cs @@ -0,0 +1,13 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Notifications.EmailGroups +{ + public class EmailGroupUsersDto :EntityDto + { + + public Guid GroupId { get; set; } + public Guid UserId { get; set; } + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/IEmailGroupUsersAppService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/IEmailGroupUsersAppService.cs new file mode 100644 index 000000000..d4279dcf5 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroupUsers/IEmailGroupUsersAppService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + + +namespace Unity.Notifications.EmailGroups +{ + public interface IEmailGroupUsersAppService + { + Task InsertAsync (EmailGroupUsersDto dto); + Task DeleteUserAsync (Guid id); + Task DeleteUsersByGroupIdAsync (Guid id); + Task DeleteUsersByUserIdAsync(Guid id); + Task> GetEmailGroupUsersByGroupIdAsync(Guid id); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/EmailGroupDto.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/EmailGroupDto.cs new file mode 100644 index 000000000..7af15420e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/EmailGroupDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Notifications.EmailGroups +{ + public class EmailGroupDto :EntityDto + { + + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/IEmailGroupsAppService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/IEmailGroupsAppService.cs new file mode 100644 index 000000000..c9616322c --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/EmailGroups/IEmailGroupsAppService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + + +namespace Unity.Notifications.EmailGroups +{ + public interface IEmailGroupsAppService + { + Task CreateAsync (EmailGroupDto dto); + Task UpdateAsync (EmailGroupDto dto); + Task DeleteAsync (Guid id); + Task> GetListAsync(); + Task GetEmailGroupByIdAsync(Guid id); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs index e885a6fdb..be4828939 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Emails/EmailHistoryDto.cs @@ -10,6 +10,8 @@ public class EmailHistoryDto : ExtensibleAuditedEntityDto public string Status { get; set; } = string.Empty; public string FromAddress { get; set; } = string.Empty; public string ToAddress { get; set; } = string.Empty; + public string Cc { get; set; } = string.Empty; + public string Bcc { get; set; } = string.Empty; public DateTime? SentDateTime { get; set; } public string Body { get; set; } = string.Empty; public EmailHistoryUserDto? SentBy { get; set; } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroupUsers/EmailGroupUsersAppService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroupUsers/EmailGroupUsersAppService.cs new file mode 100644 index 000000000..55ed0fab8 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroupUsers/EmailGroupUsersAppService.cs @@ -0,0 +1,85 @@ +using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; + + +namespace Unity.Notifications.EmailGroups +{ + + [Authorize] + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(EmailGroupUsersAppService), typeof(IEmailGroupUsersAppService))] + public class EmailGroupUsersAppService : ApplicationService, IEmailGroupUsersAppService + { + private readonly IEmailGroupUsersRepository _emailGroupUsersRepository; + + public EmailGroupUsersAppService(IEmailGroupUsersRepository emailGroupUsersRepository) + { + _emailGroupUsersRepository = emailGroupUsersRepository; + } + public async Task InsertAsync(EmailGroupUsersDto dto) + { + var newUser = await _emailGroupUsersRepository.InsertAsync(new EmailGroupUser + { + GroupId = dto.GroupId, + UserId = dto.UserId, + + }); + return new EmailGroupUsersDto + { + Id = newUser.Id, + GroupId = newUser.GroupId, + UserId = newUser.UserId, + }; + } + + + + public async Task DeleteUserAsync(Guid id) + { + try + { + await _emailGroupUsersRepository.DeleteAsync(id); + return true; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error deleting email group with ID {id}: {ex.Message}"); + } + } + public async Task DeleteUsersByUserIdAsync(Guid id) + { + try + { + await _emailGroupUsersRepository.DeleteAsync(id); + return true; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error deleting email group with ID {id}: {ex.Message}"); + } + } + public async Task DeleteUsersByGroupIdAsync(Guid id) + { + try + { + await _emailGroupUsersRepository.DeleteAsync(id); + return true; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error deleting email group with ID {id}: {ex.Message}"); + } + } + + public async Task> GetEmailGroupUsersByGroupIdAsync(Guid id) + { + var users = await _emailGroupUsersRepository.GetListAsync(u => u.GroupId == id); + + return ObjectMapper.Map, List>(users); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroups/EmailGroupsAppService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroups/EmailGroupsAppService.cs new file mode 100644 index 000000000..bec6f3748 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailGroups/EmailGroupsAppService.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; + + +namespace Unity.Notifications.EmailGroups +{ + + [Authorize] + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(EmailGroupsAppService), typeof(IEmailGroupsAppService))] + public class EmailGroupsAppService : ApplicationService, IEmailGroupsAppService + { + private readonly IEmailGroupsRepository _emailGroupsRepository; + + public EmailGroupsAppService(IEmailGroupsRepository emailGroupsRepository) + { + _emailGroupsRepository = emailGroupsRepository; + } + public async Task CreateAsync(EmailGroupDto dto) + { + var newGroup = await _emailGroupsRepository.InsertAsync(new EmailGroup + { + Name = dto.Name, + Description = dto.Description, + Type = dto.Type + }); + return new EmailGroupDto + { + Id = newGroup.Id, + Name = newGroup.Name, + Description = newGroup.Description, + Type = newGroup.Type + }; + } + + public async Task UpdateAsync(EmailGroupDto dto) + { + var emailGroup = await _emailGroupsRepository.GetAsync(dto.Id, true); + emailGroup.Name = dto.Name; + emailGroup.Description = dto.Description; + emailGroup.Type = dto.Type; + await _emailGroupsRepository.UpdateAsync(emailGroup, autoSave: true); + return new EmailGroupDto + { + Id = emailGroup.Id, + Name = emailGroup.Name, + Description = emailGroup.Description, + Type = emailGroup.Type + }; + } + + public async Task DeleteAsync(Guid id) + { + try + { + await _emailGroupsRepository.DeleteAsync(id); + return true; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error deleting email group with ID {id}: {ex.Message}"); + } + } + + public async Task> GetListAsync() + { + var groups = await _emailGroupsRepository.GetListAsync(); + return ObjectMapper.Map, List>(groups); + } + + public async Task GetEmailGroupByIdAsync(Guid id) + { + var group = await _emailGroupsRepository.GetAsync(id); + return ObjectMapper.Map(group); + } + + } +} 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 e7687324f..5c084550a 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,27 +1,29 @@ using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Unity.Modules.Shared.Utils; using Unity.Notifications.Emails; using Unity.Notifications.Events; using Unity.Notifications.Integrations.Ches; using Unity.Notifications.Integrations.RabbitMQ; +using Unity.Notifications.Permissions; +using Unity.Notifications.Settings; using Unity.Notifications.TeamsNotifications; +using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; -using Volo.Abp.Users; -using Volo.Abp.SettingManagement; -using Unity.Notifications.Settings; -using Unity.Notifications.Permissions; -using Volo.Abp; using Volo.Abp.Features; -using Microsoft.AspNetCore.Http; +using Volo.Abp.SettingManagement; +using Volo.Abp.Users; namespace Unity.Notifications.EmailNotifications; @@ -60,52 +62,36 @@ IHttpContextAccessor httpContextAccessor _httpContextAccessor = httpContextAccessor; } - private const string approvalBody = - @"Hello,
-
- Thank you for your grant application. We are pleased to inform you that your project has been approved for funding.
- A representative from our Program Area will be reaching out to you shortly with more information on next steps.
-
- Kind regards.
-
- *ATTENTION - Please do not reply to this email as it is an automated notification which is unable to receive replies.
"; - - private const string declineBody = - @"Hello,
-
- Thank you for your application. We would like to advise you that after careful consideration, your project was not selected to receive funding from our Program.
-
- We know that a lot of effort goes into developing a proposed project and we appreciate the time you took to prepare your application.
-
- If you have any questions or concerns, please reach out to program team members who will provide further details regarding the funding decision.
-
- Thank you again for your application.
-
- *ATTENTION - Please do not reply to this email as it is an automated notification which is unable to receive replies.
"; - - public string GetApprovalBody() + public async Task DeleteEmail(Guid id) { - return approvalBody; + await _emailLogsRepository.DeleteAsync(id); } - public string GetDeclineBody() + public async Task GetEmailsChesWithNoResponseCountAsync() { - return declineBody; - } + var dbNow = DateTime.UtcNow; - public async Task DeleteEmail(Guid id) - { - await _emailLogsRepository.DeleteAsync(id); + // Create the expression to filter the email logs + Expression> filter = x => + (x.Status == EmailStatus.Sent && x.ChesResponse == null) || + (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 emailLogs = allEmailLogs.Where(filter.Compile()).ToList(); + + // Ensure we're returning 0 if no logs are found + return emailLogs?.Count ?? 0; } - public async Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status,string? emailTemplateName) + public async Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { if (string.IsNullOrEmpty(emailTo)) { return null; } - - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName); + + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC); EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId); emailLog = UpdateMappedEmailLog(emailLog, emailObject); emailLog.ApplicationId = applicationId; @@ -117,19 +103,19 @@ public async Task DeleteEmail(Guid id) return loggedEmail; } - public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName) + public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { - return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized, emailTemplateName); + return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized, emailTemplateName, emailCC, emailBCC); } [RemoteService(false)] - public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName) + public async Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { if (string.IsNullOrEmpty(emailTo)) { return null; } - var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC); EmailLog emailLog = new EmailLog(); emailLog = UpdateMappedEmailLog(emailLog, emailObject); emailLog.ApplicationId = applicationId; @@ -170,7 +156,6 @@ public async Task SendCommentNotification(EmailCommentDto i var pathBase = "/GrantApplications/Details?ApplicationId="; var baseUrl = $"{scheme}://{host}{pathBase}"; var commentLink = $"{baseUrl}{input.ApplicationId}"; - var subject = $"Unity-Comment: {input.Subject}"; var fromEmail = defaultFromAddress ?? "NoReply@gov.bc.ca"; string htmlBody = $@" @@ -234,9 +219,12 @@ public async Task SendCommentNotification(EmailCommentDto i /// The body of the email /// Subject Message /// From Email Address - /// Type of body email: html or text + /// Type of body email: html or text + /// Template name for the email + /// CC email addresses + /// BCC email addresses /// HttpResponseMessage indicating the result of the operation - public async Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName) + public async Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { try { @@ -250,7 +238,7 @@ 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); + var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName, emailCC, emailBCC); var response = await _chesClientService.SendAsync(emailObject); // Assuming SendAsync returns a HttpResponseMessage or equivalent: @@ -328,15 +316,11 @@ public async Task SendEmailToQueue(EmailLog emailLog) await _emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent); } - protected virtual async Task GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName) + protected virtual async Task GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { - List toList = new(); - string[] emails = emailTo.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries); - - foreach (string email in emails) - { - toList.Add(email.Trim()); - } + var toList = emailTo.ParseEmailList() ?? []; + var ccList = emailCC.ParseEmailList(); + var bccList = emailBCC.ParseEmailList(); var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress); @@ -344,6 +328,8 @@ protected virtual async Task GetEmailObjectAsync(string emailTo, string { body, bodyType = emailBodyType ?? "text", + cc = ccList, + bcc = bccList, encoding = "utf-8", from = emailFrom ?? defaultFromAddress ?? "NoReply@gov.bc.ca", priority = "normal", @@ -361,7 +347,9 @@ protected virtual EmailLog UpdateMappedEmailLog(EmailLog emailLog, dynamic email emailLog.Subject = emailDynamicObject.subject; emailLog.BodyType = emailDynamicObject.bodyType; emailLog.FromAddress = emailDynamicObject.from; - emailLog.ToAddress = String.Join(",", emailDynamicObject.to); + emailLog.ToAddress = string.Join(",", emailDynamicObject.to); + emailLog.CC = emailDynamicObject.cc != null ? string.Join(",", (IEnumerable)emailDynamicObject.cc) : string.Empty; + emailLog.BCC = emailDynamicObject.bcc != null ? string.Join(",", (IEnumerable)emailDynamicObject.bcc) : string.Empty; emailLog.TemplateName = emailDynamicObject.templateName; return emailLog; } @@ -373,7 +361,8 @@ public async Task UpdateSettings(NotificationsSettingsDto settingsDto) await UpdateTenantSettings(NotificationsSettings.Mailing.EmailMaxRetryAttempts, settingsDto.MaximumRetryAttempts); } - private async Task UpdateTenantSettings(string settingKey, string valueString) { + private async Task UpdateTenantSettings(string settingKey, string valueString) + { if (!valueString.IsNullOrWhiteSpace()) { await _settingManager.SetForCurrentTenantAsync(settingKey, valueString); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs index a4304c06c..d1be9e42a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs @@ -10,17 +10,16 @@ namespace Unity.Notifications.EmailNotifications { public interface IEmailNotificationService : IApplicationService { - Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName); - Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName); - Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName); + Task UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null); + Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null); + Task InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName, string? emailCC = null, string? emailBCC = null); Task GetEmailLogById(Guid id); Task SendCommentNotification(EmailCommentDto input); - Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName); + Task SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null); Task SendEmailToQueue(EmailLog emailLog); - string GetApprovalBody(); - string GetDeclineBody(); Task> GetHistoryByApplicationId(Guid applicationId); Task UpdateSettings(NotificationsSettingsDto settingsDto); Task DeleteEmail(Guid id); + Task GetEmailsChesWithNoResponseCountAsync(); } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs index a502e50a8..4d14db077 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Events/EmailNotificationEvent.cs @@ -16,7 +16,9 @@ public class EmailNotificationEvent public string Subject { get; set; } = string.Empty; public string? EmailFrom { get; set; } = string.Empty; public string EmailAddress { get; set; } = string.Empty; - public List EmailAddressList { get; set; } = new List(); + public List EmailAddressList { get; set; } = []; + public IEnumerable Cc { get; set; } = []; + public IEnumerable Bcc { get; set; } = []; [JsonConverter(typeof(JsonStringEnumConverter))] public EmailAction Action { get; set; } 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 f924ad56a..b53c3c860 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 @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Unity.Notifications.EmailNotifications; using Unity.Notifications.Emails; @@ -25,7 +26,7 @@ public async Task HandleEventAsync(EmailNotificationEvent eventData) } } - private async Task InitializeAndSendEmailToQueue(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName) + private async Task InitializeAndSendEmailToQueue(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { EmailLog emailLog = await InitializeEmail( emailTo, @@ -34,12 +35,14 @@ private async Task InitializeAndSendEmailToQueue(string emailTo, string body, st applicationId, emailFrom, EmailStatus.Initialized, - emailTemplateName); + emailTemplateName, + emailCC, + emailBCC); await emailNotificationService.SendEmailToQueue(emailLog); } - private async Task InitializeEmail(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string status, string? emailTemplateName) + private async Task InitializeEmail(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null) { EmailLog emailLog = await emailNotificationService.InitializeEmailLog( emailTo, @@ -48,7 +51,9 @@ private async Task InitializeEmail(string emailTo, string body, string applicationId, emailFrom, status, - emailTemplateName) ?? throw new UserFriendlyException("Unable to Initialize Email Log"); + emailTemplateName, + emailCC, + emailBCC) ?? throw new UserFriendlyException("Unable to Initialize Email Log"); return emailLog; } @@ -85,9 +90,12 @@ private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) string emailToAddress = String.Join(",", eventData.EmailAddressList); + string? emailCC = eventData.Cc?.Any() == true ? String.Join(",", eventData.Cc) : null; + string? emailBCC = eventData.Bcc?.Any() == true ? String.Join(",", eventData.Bcc) : null; + if (eventData.Id == Guid.Empty) { - await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, eventData.Subject, eventData.ApplicationId, eventData.EmailFrom,eventData.EmailTemplateName); + await InitializeAndSendEmailToQueue(emailToAddress, eventData.Body, eventData.Subject, eventData.ApplicationId, eventData.EmailFrom, eventData.EmailTemplateName, emailCC, emailBCC); } else { @@ -99,7 +107,9 @@ private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) eventData.ApplicationId, eventData.EmailFrom, EmailStatus.Initialized, - eventData.EmailTemplateName); + eventData.EmailTemplateName, + emailCC, + emailBCC); if (emailLog != null) { @@ -115,9 +125,9 @@ private async Task HandleSendCustomEmail(EmailNotificationEvent eventData) private async Task HandleSaveDraftEmail(EmailNotificationEvent eventData) { - - string emailToAddress = String.Join(",", eventData.EmailAddressList); + string? emailCC = eventData.Cc?.Any() == true ? String.Join(",", eventData.Cc) : null; + string? emailBCC = eventData.Bcc?.Any() == true ? String.Join(",", eventData.Bcc) : null; if (eventData.Id != Guid.Empty) { @@ -129,7 +139,9 @@ await emailNotificationService.UpdateEmailLog( eventData.ApplicationId, eventData.EmailFrom, EmailStatus.Draft, - eventData.EmailTemplateName); + eventData.EmailTemplateName, + emailCC, + emailBCC); } else { @@ -140,7 +152,9 @@ await InitializeEmail( eventData.ApplicationId, eventData.EmailFrom, EmailStatus.Draft, - eventData.EmailTemplateName); + eventData.EmailTemplateName, + emailCC, + emailBCC); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs index 2c901596c..51e5ce76d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailConsumer.cs @@ -64,7 +64,9 @@ private async Task ProcessEmailLogAsync(EmailLog emailLog, EmailNotificationEven emailLog.Body, emailLog.Subject, emailLog.FromAddress, "html", - emailLog.TemplateName); + emailLog.TemplateName, + emailLog.CC, + emailLog.BCC); // Update the response emailLog.ChesResponse = JsonConvert.SerializeObject(response); emailLog.ChesStatus = response.StatusCode.ToString(); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/NotificationsApplicationAutoMapperProfile.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/NotificationsApplicationAutoMapperProfile.cs index 9d6f93788..e9ad095c5 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/NotificationsApplicationAutoMapperProfile.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/NotificationsApplicationAutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Unity.Notifications.EmailGroups; using Unity.Notifications.Emails; using Volo.Abp.Users; @@ -11,5 +12,7 @@ public NotificationsApplicationAutoMapperProfile() CreateMap() .ForMember(x => x.SentBy, map => map.Ignore()); CreateMap(); + CreateMap(); + CreateMap(); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/EmailGroupUser.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/EmailGroupUser.cs new file mode 100644 index 000000000..f66c204f1 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/EmailGroupUser.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.Notifications.EmailGroups; + +public class EmailGroupUser : AuditedAggregateRoot, IMultiTenant +{ + + public virtual Guid? TenantId { get; protected set; } + public Guid GroupId { get; set; } + public Guid UserId { get; set; } + +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/IEmailGroupUsersRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/IEmailGroupUsersRepository.cs new file mode 100644 index 000000000..0f98b4936 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroupUsers/IEmailGroupUsersRepository.cs @@ -0,0 +1,9 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Unity.Notifications.EmailGroups; + +public interface IEmailGroupUsersRepository : IRepository +{ + +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/EmailGroup.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/EmailGroup.cs new file mode 100644 index 000000000..56fd2e896 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/EmailGroup.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.Notifications.EmailGroups; + +public class EmailGroup : AuditedAggregateRoot, IMultiTenant +{ + public virtual Guid? TenantId { get; protected set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/IEmailGroupsRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/IEmailGroupsRepository.cs new file mode 100644 index 000000000..782f20623 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/EmailGroups/IEmailGroupsRepository.cs @@ -0,0 +1,9 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Unity.Notifications.EmailGroups; + +public interface IEmailGroupsRepository : IRepository +{ + +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs index ae9b3c16b..9d304685a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Emails/EmailLog.cs @@ -14,21 +14,21 @@ public class EmailLog : AuditedAggregateRoot, IMultiTenant public Guid AssessmentId { get; set; } public Guid ApplicationId { get; set; } public Guid ApplicantId { get; set; } - public string FromAddress { get; set; } = ""; - public string ToAddress { get; set; } = ""; - public string CC { get; set; } = ""; - public string BCC { get; set; } = ""; - public string Subject { get; set; } = ""; - public string Body { get; set; } = ""; - public string BodyType { get; set; } = ""; - public string Priority { get; set; } = ""; - public string Tag { get; set; } = ""; + public string FromAddress { get; set; } = string.Empty; + public string ToAddress { get; set; } = string.Empty; + public string CC { get; set; } = string.Empty; + public string BCC { get; set; } = string.Empty; + public string Subject { get; set; } = string.Empty; + public string Body { get; set; } = string.Empty; + public string BodyType { get; set; } = string.Empty; + public string Priority { get; set; } = string.Empty; + public string Tag { get; set; } = string.Empty; public int RetryAttempts { get; set; } public Guid? ChesMsgId { get; set; } - public string ChesResponse { get; set; } = ""; - public string ChesStatus { get; set; } = ""; - public string Status { get; set; } = ""; + public string ChesResponse { get; set; } = string.Empty; + public string ChesStatus { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; public DateTime? SendOnDateTime { get; set; } public DateTime? SentDateTime { get; set; } - public string TemplateName { get; set; } = ""; + public string TemplateName { get; set; } = string.Empty; } 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 165ae2e8d..902524266 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,18 +1,24 @@ -using System.Collections.Generic; +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) + public NotificationsDataSeedContributor(ITemplateVariablesRepository templateVariablesRepository, IEmailGroupsRepository emailGroupsRepository) { _templateVariablesRepository = templateVariablesRepository; + _emailGroupsRepository = emailGroupsRepository; } public async Task SeedAsync(DataSeedContext context) @@ -45,28 +51,67 @@ public async Task SeedAsync(DataSeedContext context) new EmailTempateVariableDto { Name = "Applicant ID", Token = "applicant_id", MapTo = "applicant.unityApplicantId" } }; - foreach (var template in emailTemplateVariableDtos) + try { - var existingVariable = await _templateVariablesRepository.FindAsync(tv => tv.Token == template.Token); - if (existingVariable == null) + foreach (var template in emailTemplateVariableDtos) { - await _templateVariablesRepository.InsertAsync( - new TemplateVariable { Name = template.Name, Token = template.Token, MapTo = template.MapTo }, - autoSave: true - ); + 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); + } } - else if (existingVariable.Token == "category" && existingVariable.MapTo == "category") + } + 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) { - existingVariable.MapTo = "applicationForm.category"; - await _templateVariablesRepository.UpdateAsync(existingVariable, autoSave: true); + 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 EmailTempateVariableDto -{ - public string Name { get; set; } = string.Empty; - public string Token { get; set; } = string.Empty; - public string MapTo { get; set; } = string.Empty; + 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.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContext.cs index 2ccc1dea7..1bd185960 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContext.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Unity.Notifications.Emails; using Unity.Notifications.Templates; +using Unity.Notifications.EmailGroups; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -12,6 +13,8 @@ public class NotificationsDbContext : AbpDbContext, INot public DbSet EmailLogs { get; set; } public DbSet EmailTemplates { get; set; } public DbSet TemplateVariables { get; set; } + public DbSet EmailGroups { get; set; } + public DbSet EmailGroupUsers { get; set; } // Add DbSet for each Aggregate Root here. public NotificationsDbContext(DbContextOptions options) diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContextModelCreatingExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContextModelCreatingExtensions.cs index fb096ce08..2a294a8d2 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContextModelCreatingExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationsDbContextModelCreatingExtensions.cs @@ -1,6 +1,8 @@ using Microsoft.EntityFrameworkCore; + using Unity.Notifications.Emails; using Unity.Notifications.Templates; +using Unity.Notifications.EmailGroups; using Volo.Abp; using Volo.Abp.EntityFrameworkCore.Modeling; @@ -96,5 +98,21 @@ public static void ConfigureNotifications( b.ConfigureByConvention(); }); + modelBuilder.Entity(b => + { + b.ToTable(NotificationsDbProperties.DbTablePrefix + "EmailGroups", NotificationsDbProperties.DbSchema); + + b.ConfigureByConvention(); + }); + + modelBuilder.Entity(b => + { + b.ToTable(NotificationsDbProperties.DbTablePrefix + "EmailGroupUsers", NotificationsDbProperties.DbSchema); + + b.ConfigureByConvention(); + b.HasOne() + .WithMany() + .HasForeignKey(x => x.GroupId); + }); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupUsersRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupUsersRepository.cs new file mode 100644 index 000000000..d5b7ef572 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupUsersRepository.cs @@ -0,0 +1,16 @@ +using System; +using Unity.Notifications.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Unity.Notifications.EmailGroups; + +namespace Unity.Notifications.Repositories +{ + public class EmailGroupUsersRepository : EfCoreRepository, IEmailGroupUsersRepository + { + public EmailGroupUsersRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupsRepository.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupsRepository.cs new file mode 100644 index 000000000..7a5332c4a --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Repositories/EmailGroupsRepository.cs @@ -0,0 +1,16 @@ +using System; +using Unity.Notifications.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Unity.Notifications.EmailGroups; + + +namespace Unity.Notifications.Repositories +{ + public class EmailGroupsRepository : EfCoreRepository, IEmailGroupsRepository + { + public EmailGroupsRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs index 6d066a23c..16d557583 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Enums/PaymentRequestStatus.cs @@ -9,13 +9,14 @@ public enum PaymentRequestStatus L1Pending = 1, L1Declined = 2, L2Pending = 3, - L2Declined = 4, - L3Pending = 5, + L2Declined = 4, + L3Pending = 5, L3Declined = 6, Submitted = 7, Validated = 8, NotValidated = 9, Paid = 10, - Failed = 11, + Failed = 11, + FSB = 12, // Financial Services Branch - Prevent CAS Payment } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/PaymentConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/PaymentConfigurationDto.cs deleted file mode 100644 index 0bfeede64..000000000 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/PaymentConfigurationDto.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Volo.Abp.Application.Dtos; - -namespace Unity.Payments.PaymentConfigurations -{ - [Serializable] - public class PaymentConfigurationDto : ExtensibleFullAuditedEntityDto - { - public decimal PaymentThreshold { get; set; } - public string PaymentIdPrefix { get; set; } = string.Empty; - public string MinistryClient { get; set; } = string.Empty; - public string Responsibility { get; set; } = string.Empty; - public string ServiceLine { get; set; } = string.Empty; - public string Stob { get; set; } = string.Empty; - public string ProjectNumber { get; set; } = string.Empty; - } -} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/UpsertPaymentConfigurationDtoBase.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/UpsertPaymentConfigurationDtoBase.cs deleted file mode 100644 index cd6a2f37f..000000000 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/UpsertPaymentConfigurationDtoBase.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Unity.Payments.PaymentConfigurations -{ - [Serializable] - public class UpsertPaymentConfigurationDtoBase - { - public decimal PaymentThreshold { get; set; } - public string PaymentIdPrefix { get; set; } = string.Empty; - public string MinistryClient { get; set; } = string.Empty; - public string Responsibility { get; set; } = string.Empty; - public string ServiceLine { get; set; } = string.Empty; - public string Stob { get; set; } = string.Empty; - public string ProjectNumber { get; set; } = string.Empty; - } -} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/CreatePaymentConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/CreatePaymentConfigurationDto.cs similarity index 100% rename from applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/CreatePaymentConfigurationDto.cs rename to applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/CreatePaymentConfigurationDto.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/IPaymentConfigurationAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/IPaymentConfigurationAppService.cs similarity index 59% rename from applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/IPaymentConfigurationAppService.cs rename to applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/IPaymentConfigurationAppService.cs index e26fff6d0..fbb3645ba 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/IPaymentConfigurationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/IPaymentConfigurationAppService.cs @@ -1,14 +1,14 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Unity.Payments.PaymentConfigurations { public interface IPaymentConfigurationAppService { Task GetAsync(); - - Task CreateAsync(CreatePaymentConfigurationDto createPaymentConfigurationDto); - - Task UpdateAsync(UpdatePaymentConfigurationDto updatePaymentConfigurationDto); - Task GetAccountDistributionCodeAsync(); - } + Task UpdateAsync(UpdatePaymentConfigurationDto updatePaymentConfigurationDto); + Task CreateAsync(CreatePaymentConfigurationDto createUpdatePaymentConfigurationDto); + Task UpdatePaymentPrefixAsync(string paymentPrefix); + Task SetDefaultAccountCodeAsync(Guid accountCodingId); + } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/PaymentConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/PaymentConfigurationDto.cs new file mode 100644 index 000000000..9c1606c91 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/PaymentConfigurationDto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Payments.PaymentConfigurations +{ + [Serializable] + public class PaymentConfigurationDto : ExtensibleFullAuditedEntityDto + { + public string PaymentIdPrefix { get; set; } = string.Empty; + public Guid DefaultAccountCodingId { get; set; } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/UpdatePaymentConfigurationDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/UpdatePaymentConfigurationDto.cs similarity index 100% rename from applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfiguration/UpdatePaymentConfigurationDto.cs rename to applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/UpdatePaymentConfigurationDto.cs diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/UpsertPaymentConfigurationDtoBase.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/UpsertPaymentConfigurationDtoBase.cs new file mode 100644 index 000000000..0d8003614 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentConfigurations/UpsertPaymentConfigurationDtoBase.cs @@ -0,0 +1,11 @@ +using System; + +namespace Unity.Payments.PaymentConfigurations +{ + [Serializable] + public class UpsertPaymentConfigurationDtoBase + { + public string PaymentIdPrefix { get; set; } = string.Empty; + public Guid DefaultAccountCodingId { get; set; } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/AccountCodingDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/AccountCodingDto.cs new file mode 100644 index 000000000..be11613b4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/AccountCodingDto.cs @@ -0,0 +1,23 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Payments.PaymentRequests +{ + [Serializable] + public class AccountCodingDto : AuditedEntityDto + { + public string MinistryClient { get; private set; } + public string Responsibility { get; private set; } + public string ServiceLine { get; private set; } + public string Stob { get; private set; } + public string ProjectNumber { get; private set; } + public AccountCodingDto() + { + MinistryClient = string.Empty; + Responsibility = string.Empty; + ServiceLine = string.Empty; + Stob = string.Empty; + ProjectNumber = string.Empty; + } + } +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreatePaymentRequestDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreatePaymentRequestDto.cs index 0c76936c6..b12e3833a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreatePaymentRequestDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/CreatePaymentRequestDto.cs @@ -18,20 +18,21 @@ public class CreatePaymentRequestDto public string CorrelationProvider { get; set; } = string.Empty; public string BatchName { get; set; } public decimal BatchNumber { get; set; } = 0; - public string ReferenceNumber { get; set; } = string.Empty; + public string ReferenceNumber { get; set; } = string.Empty; public string SubmissionConfirmationCode { get; set; } = string.Empty; - public string? InvoiceStatus { get; set; } - public string? PaymentStatus { get; set; } - public string? PaymentNumber { get; set; } + public string? InvoiceStatus { get; set; } + public string? PaymentStatus { get; set; } + public string? PaymentNumber { get; set; } public string? PaymentDate { get; set; } - public decimal? PaymentThreshold { get; set; } = 500000m; + public Guid? AccountCodingId { get; set; } + public string? Note { get; set; } } public class UpdatePaymentStatusRequestDto { public Guid PaymentRequestId { get; set; } - public bool IsApprove { get; set; } + public string Note { get; set; } } #pragma warning restore CS8618 } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs index 18468a6be..9af5ab42d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/IPaymentRequestAppService.cs @@ -17,5 +17,7 @@ public interface IPaymentRequestAppService : IApplicationService Task GetPaymentRequestCountBySiteIdAsync(Guid siteId); Task> GetListByApplicationIdsAsync(List applicationIds); Task GetNextBatchInfoAsync(); + Task GetDefaultAccountCodingId(); + Task GetUserPaymentThresholdAsync(); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs index 3b3c09073..e782f3f36 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentRequests/PaymentRequestDto.cs @@ -32,7 +32,11 @@ public class PaymentRequestDto : AuditedEntityDto public decimal BatchNumber { get; set; } public string ReferenceNumber { get; set; } = string.Empty; public string SubmissionConfirmationCode { get; set; } = string.Empty; + public string? Note { get; set; } public string? ErrorSummary { get; set; } + public Guid? AccountCodingId { get; set; } + public AccountCodingDto? AccountCoding { get; set; } + public string AccountCodingDisplay { get; set; } = string.Empty; public PaymentUserDto? CreatorUser { get; set; } public Collection PaymentTags { get; set; } public Collection ExpenseApprovals { get; set; } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentThresholds/PaymentThresholdDto.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentThresholds/PaymentThresholdDto.cs new file mode 100644 index 000000000..343649578 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/PaymentThresholds/PaymentThresholdDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Unity.Payments.PaymentThresholds; + +[Serializable] +public class PaymentThresholdDto : AuditedEntityDto +{ + public Guid? TenantId { get; set; } + public Guid? UserId { get; set; } + public string? UserName { get; set; } + public decimal? Threshold { get; set; } + public string? Description { get; set; } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Unity.Payments.Application.Contracts.csproj b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Unity.Payments.Application.Contracts.csproj index 48cc8ecdb..006fe6bf2 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Unity.Payments.Application.Contracts.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application.Contracts/Unity.Payments.Application.Contracts.csproj @@ -13,6 +13,7 @@ + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/AccountCoding.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/AccountCoding.cs similarity index 64% rename from applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/AccountCoding.cs rename to applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/AccountCoding.cs index f9e47e303..f68e1684d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/AccountCoding.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/AccountCoding.cs @@ -1,37 +1,34 @@ -using System; -using System.Linq; -using Unity.Payments.Domain.Exceptions; +using Unity.Payments.Domain.Exceptions; using Volo.Abp; - +using System; +using Volo.Abp.Domain.Entities.Auditing; +using System.Linq; +using Volo.Abp.MultiTenancy; namespace Unity.Payments.Domain.AccountCodings { - public record AccountCoding + public class AccountCoding : AuditedAggregateRoot, IMultiTenant { public Guid? TenantId { get; set; } + public string MinistryClient { get; private set; } + public string Responsibility { get; private set; } + public string ServiceLine { get; private set; } + public string Stob { get; private set; } + public string ProjectNumber { get; private set; } - public string MinistryClient { get; set; } = string.Empty; - - public string Responsibility { get; set; } = string.Empty; - - public string ServiceLine { get; set; } = string.Empty; - - public string Stob { get; set; } = string.Empty; - - public string ProjectNumber { get; set; } = string.Empty; - - // Constructor for ORM - protected AccountCoding() + public AccountCoding() { - + MinistryClient = string.Empty; + Responsibility = string.Empty; + ServiceLine = string.Empty; + Stob = string.Empty; + ProjectNumber = string.Empty; } - - public AccountCoding( - string ministryClient, + private AccountCoding(string ministryClient, string responsibility, string serviceLine, string stob, - string projectNumber) + string projectNumber) { MinistryClient = ministryClient; Responsibility = responsibility; @@ -47,8 +44,8 @@ public static AccountCoding Create( string stob, string projectNumber) { - ValidateField(ministryClient, 3, nameof(MinistryClient), true); - ValidateField(responsibility, 5, nameof(Responsibility), true); + ValidateField(ministryClient, 3, nameof(MinistryClient), false); + ValidateField(responsibility, 5, nameof(Responsibility), false); ValidateField(serviceLine, 5, nameof(serviceLine), true); ValidateField(stob, 4, nameof(stob), true); ValidateField(projectNumber, 7, nameof(projectNumber), true); @@ -56,8 +53,9 @@ public static AccountCoding Create( return new AccountCoding(ministryClient, responsibility, serviceLine, stob, projectNumber); } - private static void ValidateField(string field, uint length, string fieldName, bool validateAlphanumeric = true) + private static void ValidateField(string field, uint length, string fieldName, bool validateAlphanumeric) { + bool validAlphanumeric = true; if (validateAlphanumeric) @@ -73,4 +71,5 @@ private static void ValidateField(string field, uint length, string fieldName, b } } } -} + +} \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/IAccountCodingRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/IAccountCodingRepository.cs new file mode 100644 index 000000000..ab91c12f1 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/AccountCodings/IAccountCodingRepository.cs @@ -0,0 +1,9 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Unity.Payments.Domain.AccountCodings; + +public interface IAccountCodingRepository : IBasicRepository +{ + +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/PaymentConfiguration.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/PaymentConfiguration.cs index 193d1545f..20304e414 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/PaymentConfiguration.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentConfigurations/PaymentConfiguration.cs @@ -1,5 +1,4 @@ using System; -using Unity.Payments.Domain.AccountCodings; using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.MultiTenancy; @@ -8,37 +7,11 @@ namespace Unity.Payments.Domain.PaymentConfigurations public class PaymentConfiguration : FullAuditedAggregateRoot, IMultiTenant { public Guid? TenantId { get; set; } + public Guid? DefaultAccountCodingId { get; set; } public string PaymentIdPrefix { get; set; } = string.Empty; - public decimal? PaymentThreshold { get; set; } - public string? MinistryClient { get; private set; } - public string? Responsibility { get; private set; } - public string? ServiceLine { get; private set; } - public string? Stob { get; private set; } - public string? ProjectNumber { get; private set; } - - - protected PaymentConfiguration() + public PaymentConfiguration() { /* This constructor is for ORMs to be used while getting the entity from the database. */ } - - public PaymentConfiguration( - decimal? paymentThreshold, - string paymentIdPrefix, - AccountCoding accountCoding) - { - PaymentThreshold = paymentThreshold; - PaymentIdPrefix = paymentIdPrefix; - SetAccountCoding(accountCoding); - } - - public void SetAccountCoding(AccountCoding accountCoding) - { - MinistryClient = accountCoding.MinistryClient; - Responsibility = accountCoding.Responsibility; - ServiceLine = accountCoding.ServiceLine; - Stob = accountCoding.Stob; - ProjectNumber = accountCoding.ProjectNumber; - } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/ExpenseApproval.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/ExpenseApproval.cs index b8c2b423c..43b6f42f6 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/ExpenseApproval.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/ExpenseApproval.cs @@ -49,7 +49,7 @@ public virtual PaymentRequest PaymentRequest } private PaymentRequest? _paymentRequest; - protected ExpenseApproval() + public ExpenseApproval() { /* This constructor is for ORMs to be used while getting the entity from the database. */ } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs index 01a862678..4ea6b8752 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/PaymentRequests/PaymentRequest.cs @@ -10,6 +10,7 @@ using Unity.Payments.Domain.Exceptions; using Unity.Payments.PaymentRequests; using Unity.Payments.Domain.PaymentTags; +using Unity.Payments.Domain.AccountCodings; namespace Unity.Payments.Domain.PaymentRequests { @@ -59,7 +60,9 @@ public virtual Site Site // Corperate Accounting System public virtual int? CasHttpStatusCode { get; private set; } = null; public virtual string? CasResponse { get; private set; } = string.Empty; - + public virtual Guid? AccountCodingId { get; private set; } + public virtual AccountCoding? AccountCoding { get; set; } = null; + public virtual string? Note { get; private set; } = null; protected PaymentRequest() { ExpenseApprovals = []; @@ -67,7 +70,7 @@ protected PaymentRequest() /* This constructor is for ORMs to be used while getting the entity from the database. */ } - private static Collection GenerateExpenseApprovals(decimal amount, decimal? paymentThreshold = 500000m) + private static Collection GenerateExpenseApprovals() { var expenseApprovals = new Collection() { @@ -75,11 +78,6 @@ private static Collection GenerateExpenseApprovals(decimal amou new(Guid.NewGuid(), ExpenseApprovalType.Level2) }; - if (amount >= paymentThreshold) - { - expenseApprovals.Add(new ExpenseApproval(Guid.NewGuid(), ExpenseApprovalType.Level3)); - } - return expenseApprovals; } @@ -99,11 +97,19 @@ public PaymentRequest(Guid id, CreatePaymentRequestDto createPaymentRequestDto) SubmissionConfirmationCode = createPaymentRequestDto.SubmissionConfirmationCode; BatchName = createPaymentRequestDto.BatchName; BatchNumber = createPaymentRequestDto.BatchNumber; - PaymentTags = null; - ExpenseApprovals = GenerateExpenseApprovals(createPaymentRequestDto.Amount, createPaymentRequestDto.PaymentThreshold); + AccountCodingId = createPaymentRequestDto.AccountCodingId; + PaymentTags = null; + Note = createPaymentRequestDto.Note; + ExpenseApprovals = GenerateExpenseApprovals(); ValidatePaymentRequest(); } + public PaymentRequest SetNote(string note) + { + Note = note; + return this; + } + public PaymentRequest SetAmount(decimal amount) { Amount = amount; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/IPaymentsManager.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/IPaymentsManager.cs index f98e10420..bccb0910c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/IPaymentsManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/IPaymentsManager.cs @@ -7,5 +7,7 @@ namespace Unity.Payments.Domain.Services public interface IPaymentsManager { Task UpdatePaymentStatusAsync(Guid paymentRequestId, PaymentApprovalAction triggerAction); + Task GetFormPreventPaymentStatusByPaymentRequestId(Guid paymentRequestId); + Task GetFormPreventPaymentStatusByApplicationId(Guid applicationId); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentsManager.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentsManager.cs index 76adb4252..73bd07d3c 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentsManager.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/Services/PaymentsManager.cs @@ -1,13 +1,14 @@ -using Stateless; +using Microsoft.EntityFrameworkCore; +using Stateless; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Unity.GrantManager.Applications; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.Shared; using Unity.Payments.Domain.Workflow; using Unity.Payments.Enums; -using Unity.Payments.Integrations.Cas; using Unity.Payments.PaymentRequests; using Unity.Payments.Permissions; using Volo.Abp.Authorization.Permissions; @@ -17,29 +18,15 @@ namespace Unity.Payments.Domain.Services { - public class PaymentsManager : DomainService, IPaymentsManager - { - /* To be implemented */ - private readonly IPaymentRequestRepository _paymentRequestRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; - private readonly IPermissionChecker _permissionChecker; - private readonly CasPaymentRequestCoordinator _casPaymentRequestCoordinator; - private readonly ICurrentUser _currentUser; - - public PaymentsManager( + public class PaymentsManager( + IApplicationRepository applicationRepository, + IApplicationFormRepository applicationFormRepository, CasPaymentRequestCoordinator casPaymentRequestCoordinator, - IInvoiceService invoiceService, IPaymentRequestRepository paymentRequestRepository, IUnitOfWorkManager unitOfWorkManager, IPermissionChecker permissionChecker, - ICurrentUser currentUser) - { - _casPaymentRequestCoordinator = casPaymentRequestCoordinator; - _paymentRequestRepository = paymentRequestRepository; - _unitOfWorkManager = unitOfWorkManager; - _permissionChecker = permissionChecker; - _currentUser = currentUser; - } + ICurrentUser currentUser) : DomainService, IPaymentsManager + { private void ConfigureWorkflow(StateMachine paymentStateMachine) { @@ -69,13 +56,12 @@ private void ConfigureWorkflow(StateMachine> GetActions(Guid paymentRequestsId) { - var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestsId, true); + var paymentRequest = await paymentRequestRepository.GetAsync(paymentRequestsId, true); var Workflow = new PaymentsWorkflow( () => paymentRequest.Status, @@ -100,8 +86,8 @@ public async Task> GetActions(Guid paymentRequests public async Task TriggerAction(Guid paymentRequestsId, PaymentApprovalAction triggerAction) { - var paymentRequest = await _paymentRequestRepository.GetAsync(paymentRequestsId, true); - var currentUserId = _currentUser.GetId(); + var paymentRequest = await paymentRequestRepository.GetAsync(paymentRequestsId, true); + var currentUserId = currentUser.GetId(); var statusChange = paymentRequest.Status; @@ -116,61 +102,91 @@ public async Task TriggerAction(Guid paymentRequestsId, PaymentA if (triggerAction == PaymentApprovalAction.L1Approve) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level1); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level1); paymentRequest.ExpenseApprovals[index].Approve(currentUserId); statusChangedTo = PaymentRequestStatus.L2Pending; } else if (triggerAction == PaymentApprovalAction.L1Decline) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level1); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level1); paymentRequest.ExpenseApprovals[index].Decline(currentUserId); statusChangedTo = PaymentRequestStatus.L1Declined; } else if (triggerAction == PaymentApprovalAction.L2Approve) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level2); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level2); paymentRequest.ExpenseApprovals[index].Approve(currentUserId); statusChangedTo = PaymentRequestStatus.L3Pending; } else if (triggerAction == PaymentApprovalAction.L2Decline) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level2); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level2); paymentRequest.ExpenseApprovals[index].Decline(currentUserId); statusChangedTo = PaymentRequestStatus.L2Declined; } else if (triggerAction == PaymentApprovalAction.L3Decline) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level3); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level3); paymentRequest.ExpenseApprovals[index].Decline(currentUserId); statusChangedTo = PaymentRequestStatus.L3Declined; } else if (triggerAction == PaymentApprovalAction.Submit) { - if (HasPermission(PaymentsPermissions.Payments.L2ApproveOrDecline)) + if (HasPermission(PaymentsPermissions.Payments.L2ApproveOrDecline) && paymentRequest.Status == PaymentRequestStatus.L2Pending) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level2); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level2); paymentRequest.ExpenseApprovals[index].Approve(currentUserId); } - else if (HasPermission(PaymentsPermissions.Payments.L3ApproveOrDecline)) + else if (HasPermission(PaymentsPermissions.Payments.L3ApproveOrDecline) && paymentRequest.Status == PaymentRequestStatus.L3Pending) { - var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == Enums.ExpenseApprovalType.Level3); + var index = paymentRequest.ExpenseApprovals.FindIndex(i => i.Type == ExpenseApprovalType.Level3); paymentRequest.ExpenseApprovals[index].Approve(currentUserId); } + bool preventPayment = await GetFormPreventPaymentStatusByApplicationId(paymentRequest.CorrelationId); - statusChangedTo = PaymentRequestStatus.Submitted; - await _casPaymentRequestCoordinator.AddPaymentRequestsToInvoiceQueue(paymentRequest); + if (preventPayment) + { + statusChangedTo = PaymentRequestStatus.FSB; + } + else + { + statusChangedTo = PaymentRequestStatus.Submitted; + await casPaymentRequestCoordinator.AddPaymentRequestsToInvoiceQueue(paymentRequest); + } + + } paymentRequest.SetPaymentRequestStatus(statusChangedTo); - return await _paymentRequestRepository.UpdateAsync(paymentRequest); + return await paymentRequestRepository.UpdateAsync(paymentRequest); + } + + public async Task GetFormPreventPaymentStatusByPaymentRequestId(Guid paymentRequestId) + { + PaymentRequest paymentRequest = await paymentRequestRepository.GetAsync(paymentRequestId); + Guid applicationId = paymentRequest.CorrelationId; + var applicationQueryable = await applicationRepository.GetQueryableAsync(); + var applicationWithIncludes = await applicationQueryable.Where(a => a.Id == applicationId) + .Include(a => a.ApplicationForm).ToListAsync(); + + var appForm = applicationWithIncludes.FirstOrDefault()?.ApplicationForm; + return appForm != null && appForm.PreventPayment; } + + public async Task GetFormPreventPaymentStatusByApplicationId(Guid applicationId) + { + Application application = await applicationRepository.GetAsync(applicationId); + Guid formId = application.ApplicationForm.Id; + ApplicationForm appForm = await applicationFormRepository.GetAsync(formId); + return appForm.PreventPayment; + } public async Task UpdatePaymentStatusAsync(Guid paymentRequestId, PaymentApprovalAction triggerAction) { - using var uow = _unitOfWorkManager.Begin(); + using var uow = unitOfWorkManager.Begin(); await TriggerAction(paymentRequestId, triggerAction); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/IPaymentConfigurationRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/IPaymentConfigurationRepository.cs new file mode 100644 index 000000000..965690780 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/IPaymentConfigurationRepository.cs @@ -0,0 +1,9 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace Unity.Payments.Domain.PaymentThresholds; + +public interface IPaymentThresholdRepository : IRepository +{ + +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/PaymentThreshold.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/PaymentThreshold.cs new file mode 100644 index 000000000..40d33b942 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Domain/UserPaymentThresholds/PaymentThreshold.cs @@ -0,0 +1,20 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Unity.Payments.Domain.PaymentThresholds +{ + public class PaymentThreshold : FullAuditedAggregateRoot, IMultiTenant + { + public Guid? TenantId { get; set; } + public Guid? UserId { get; set; } + public decimal? Threshold { get; set; } + public string? Description { get; set; } + + public PaymentThreshold() + { + /* This constructor is for ORMs to be used while getting the entity from the database. */ + } + + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs index 025c1daf7..3bf75c8cd 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/IPaymentsDbContext.cs @@ -1,8 +1,10 @@ using Microsoft.EntityFrameworkCore; using Unity.Payments.Domain; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.Suppliers; +using Unity.Payments.Domain.PaymentThresholds; using Unity.Payments.Domain.PaymentTags; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -12,11 +14,12 @@ namespace Unity.Payments.EntityFrameworkCore; [ConnectionStringName(PaymentsDbProperties.ConnectionStringName)] public interface IPaymentsDbContext : IEfCoreDbContext { - + public DbSet AccountCoding { get; } public DbSet PaymentRequests { get; } public DbSet ExpenseApproval { get; } public DbSet Suppliers { get; } public DbSet Sites { get; } public DbSet PaymentConfigurations { get; } + public DbSet PaymentThresholds { get; } public DbSet PaymentTags { get; } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs index 8318dcf86..48bebd39d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContext.cs @@ -1,9 +1,11 @@ using Microsoft.EntityFrameworkCore; using Unity.Payments.Domain; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.PaymentTags; using Unity.Payments.Domain.Suppliers; +using Unity.Payments.Domain.PaymentThresholds; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -11,12 +13,13 @@ namespace Unity.Payments.EntityFrameworkCore; [ConnectionStringName(PaymentsDbProperties.ConnectionStringName)] public class PaymentsDbContext : AbpDbContext, IPaymentsDbContext -{ - +{ public DbSet PaymentRequests { get; set; } + public DbSet AccountCoding { get; set; } public DbSet ExpenseApproval { get; set; } public DbSet Suppliers { get;set; } public DbSet PaymentConfigurations { get;set; } + public DbSet PaymentThresholds { get; set; } public DbSet Sites { get; set; } public DbSet PaymentTags { get; set; } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs index 4ed2f4fe0..9d41429a1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/PaymentsDbContextModelCreatingExtensions.cs @@ -5,6 +5,8 @@ using Unity.Payments.Domain; using Unity.Payments.Domain.Suppliers; using Unity.Payments.Domain.PaymentConfigurations; +using Unity.Payments.Domain.AccountCodings; +using Unity.Payments.Domain.PaymentThresholds; using Unity.Payments.Domain.PaymentTags; using Unity.GrantManager; using Unity.GrantManager.GlobalTag; @@ -32,6 +34,11 @@ public static void ConfigurePayments( .WithMany() .HasForeignKey(x => x.SiteId) .OnDelete(DeleteBehavior.NoAction); + + b.HasOne(e => e.AccountCoding) + .WithMany() + .HasForeignKey(x => x.AccountCodingId) + .OnDelete(DeleteBehavior.NoAction); b.HasIndex(e => e.ReferenceNumber).IsUnique(); }); @@ -66,6 +73,14 @@ public static void ConfigurePayments( b.ConfigureByConvention(); }); + modelBuilder.Entity(b => + { + b.ToTable(PaymentsDbProperties.DbTablePrefix + "AccountCodings", + PaymentsDbProperties.DbSchema); + + b.ConfigureByConvention(); + }); + modelBuilder.Entity(b => { b.ToTable(PaymentsDbProperties.DbTablePrefix + "PaymentConfigurations", @@ -74,6 +89,14 @@ public static void ConfigurePayments( b.ConfigureByConvention(); }); + modelBuilder.Entity(b => + { + b.ToTable(PaymentsDbProperties.DbTablePrefix + "PaymentThresholds", + PaymentsDbProperties.DbSchema); + + b.ConfigureByConvention(); + }); + modelBuilder.Entity(b => { b.ToTable(PaymentsDbProperties.DbTablePrefix + "PaymentTags", diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/AccountCodingRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/AccountCodingRepository.cs new file mode 100644 index 000000000..436c131f4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/AccountCodingRepository.cs @@ -0,0 +1,15 @@ +using System; +using Unity.Payments.Domain.AccountCodings; +using Unity.Payments.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Unity.Payments.Repositories +{ + public class AccountCodingRepository : EfCoreRepository, IAccountCodingRepository + { + public AccountCodingRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs index 4fd659fbf..4c61349b3 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs @@ -52,7 +52,10 @@ public async Task GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid var dbSet = await GetDbSetAsync(); decimal applicationPaymentRequestsTotal = dbSet .Where(p => p.CorrelationId.Equals(correlationId)) - .Where(p => p.Status != PaymentRequestStatus.L1Declined && p.Status != PaymentRequestStatus.L2Declined && p.Status != PaymentRequestStatus.L3Declined) + .Where(p => p.Status != PaymentRequestStatus.L1Declined + && p.Status != PaymentRequestStatus.L2Declined + && p.Status != PaymentRequestStatus.L3Declined + && p.InvoiceStatus != CasPaymentRequestStatus.ErrorFromCas) .GroupBy(p => p.CorrelationId) .Select(p => p.Sum(q => q.Amount)) .FirstOrDefault(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/UserPaymentThresholdRepository.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/UserPaymentThresholdRepository.cs new file mode 100644 index 000000000..e434d425c --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/UserPaymentThresholdRepository.cs @@ -0,0 +1,15 @@ +using System; +using Unity.Payments.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Unity.Payments.Domain.PaymentThresholds; + +namespace Unity.Payments.Repositories +{ + public class PaymentThresholdRepository : EfCoreRepository, IPaymentThresholdRepository + { + public PaymentThresholdRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + } +} 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 e31a10ac6..1eb47ec87 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 @@ -9,7 +9,6 @@ using System.Collections.Generic; using Unity.Payments.Enums; using Unity.Payments.Domain.Suppliers; -using Unity.Payments.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Volo.Abp.DependencyInjection; using Unity.Payments.Codes; @@ -17,60 +16,46 @@ using Microsoft.Extensions.Logging; using Volo.Abp.Uow; using Unity.Modules.Shared.Http; +using Unity.Payments.PaymentConfigurations; +using Unity.Payments.Domain.AccountCodings; namespace Unity.Payments.Integrations.Cas { + [IntegrationService] [ExposeServices(typeof(InvoiceService), typeof(IInvoiceService))] - public class InvoiceService : ApplicationService, IInvoiceService +#pragma warning disable S107 // Methods should not have too many parameters + public class InvoiceService( + ICasTokenService iTokenService, + IAccountCodingRepository accountCodingRepository, + PaymentConfigurationAppService paymentConfigurationAppService, + IPaymentRequestRepository paymentRequestRepository, + IResilientHttpRequest resilientHttpRequest, + IOptions casClientOptions, + ISupplierRepository iSupplierRepository, + ISiteRepository iSiteRepository, + IUnitOfWorkManager unitOfWorkManager) : ApplicationService, IInvoiceService +#pragma warning restore S107 // Methods should not have too many parameters { - private readonly ICasTokenService _iTokenService; - private readonly IPaymentRequestRepository _iPaymentRequestRepository; - private readonly IResilientHttpRequest _resilientRestClient; - private readonly ISiteRepository _iSiteRepository; - private readonly ISupplierRepository _iSupplierRepository; - private readonly IOptions _casClientOptions; - private readonly IPaymentConfigurationAppService _paymentConfigurationAppService; - private readonly IUnitOfWorkManager _unitOfWorkManager; - private const string CFS_APINVOICE = "cfs/apinvoice"; - private readonly Dictionary CASPaymentGroup = new Dictionary + private readonly Dictionary CASPaymentGroup = new() { - { (int)PaymentGroup.EFT, "GEN EFT" }, - { (int)PaymentGroup.Cheque, "GEN CHQ" } + [(int)PaymentGroup.EFT] = "GEN EFT", + [(int)PaymentGroup.Cheque] = "GEN CHQ" }; - public InvoiceService( - ICasTokenService iTokenService, - IPaymentRequestRepository paymentRequestRepository, - IPaymentConfigurationAppService paymentConfigurationAppService, - IResilientHttpRequest resilientHttpRequest, - IOptions casClientOptions, - ISupplierRepository iSupplierRepository, - ISiteRepository iSiteRepository, - IUnitOfWorkManager unitOfWorkManager) - { - _iTokenService = iTokenService; - _iPaymentRequestRepository = paymentRequestRepository; - _paymentConfigurationAppService = paymentConfigurationAppService; - _resilientRestClient = resilientHttpRequest; - _casClientOptions = casClientOptions; - _iSupplierRepository = iSupplierRepository; - _iSiteRepository = iSiteRepository; - _unitOfWorkManager = unitOfWorkManager; - } - protected virtual async Task InitializeCASInvoice(PaymentRequest paymentRequest, - string? accountDistributionCode) + string? accountDistributionCode) { - Invoice? casInvoice = new Invoice(); + Invoice? casInvoice = new(); Site? site = await GetSiteByPaymentRequestAsync(paymentRequest); if (site != null && site.Supplier != null && site.Supplier.Number != null && accountDistributionCode != null) { // This can not be UTC Now it is sent to cas and can not be in the future - this is not being stored in Unity as a date - var localDateTime = DateTime.UtcNow.ToLocalTime(); + var vancouverTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + var localDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, vancouverTimeZone); var currentMonth = localDateTime.ToString("MMM").Trim('.'); var currentDay = localDateTime.ToString("dd"); var currentYear = localDateTime.ToString("yyyy"); @@ -88,10 +73,12 @@ public InvoiceService( casInvoice.InvoiceBatchName = paymentRequest.BatchName; casInvoice.PaymentAdviceComments = paymentRequest.Description; - InvoiceLineDetail invoiceLineDetail = new InvoiceLineDetail(); - invoiceLineDetail.InvoiceLineNumber = 1; - invoiceLineDetail.InvoiceLineAmount = paymentRequest.Amount; - invoiceLineDetail.DefaultDistributionAccount = accountDistributionCode; // This will be at the tenant level + InvoiceLineDetail invoiceLineDetail = new() + { + InvoiceLineNumber = 1, + InvoiceLineAmount = paymentRequest.Amount, + DefaultDistributionAccount = accountDistributionCode // This will be at the tenant level + }; casInvoice.InvoiceLineDetails = new List { invoiceLineDetail }; } @@ -100,9 +87,12 @@ public InvoiceService( public async Task GetSiteByPaymentRequestAsync(PaymentRequest paymentRequest) { - Site? site = await _iSiteRepository.GetAsync(paymentRequest.SiteId, true); - Supplier supplier = await _iSupplierRepository.GetAsync(site.SupplierId); - site.Supplier = supplier; + Site? site = await iSiteRepository.GetAsync(paymentRequest.SiteId, true); + if (site?.SupplierId != null) + { + Supplier supplier = await iSupplierRepository.GetAsync(site.SupplierId); + site.Supplier = supplier; + } return site; } @@ -111,27 +101,36 @@ public InvoiceService( InvoiceResponse invoiceResponse = new(); try { - PaymentRequest? paymentRequest = await _iPaymentRequestRepository.GetPaymentRequestByInvoiceNumber(invoiceNumber); - if (paymentRequest == null) + PaymentRequest? paymentRequest = await paymentRequestRepository.GetPaymentRequestByInvoiceNumber(invoiceNumber); + if (paymentRequest is null) { throw new UserFriendlyException("CreateInvoiceByPaymentRequestAsync: Payment Request not found"); } - string? accountDistributionCode = await _paymentConfigurationAppService.GetAccountDistributionCodeAsync(); - if (accountDistributionCode != null) + 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 + + if (!string.IsNullOrEmpty(accountDistributionCode)) { Invoice? invoice = await InitializeCASInvoice(paymentRequest, accountDistributionCode); - if (invoice != null) + if (invoice is not null) { invoiceResponse = await CreateInvoiceAsync(invoice); - if (invoiceResponse != null) + if (invoiceResponse is not null) { await UpdatePaymentRequestWithInvoice(paymentRequest.Id, invoiceResponse); } } } - } catch (Exception ex) { + } + catch (Exception ex) + { string ExceptionMessage = ex.Message; Logger.LogError(ex, "CreateInvoiceByPaymentRequestAsync Exception: {ExceptionMessage}", ExceptionMessage); } @@ -143,8 +142,8 @@ private async Task UpdatePaymentRequestWithInvoice(Guid paymentRequestId, Invoic { try { - using var uow = _unitOfWorkManager.Begin(); - PaymentRequest? paymentRequest = await _iPaymentRequestRepository.GetAsync(paymentRequestId); + using var uow = unitOfWorkManager.Begin(); + PaymentRequest? paymentRequest = await paymentRequestRepository.GetAsync(paymentRequestId); paymentRequest.SetCasHttpStatusCode((int)invoiceResponse.CASHttpStatusCode); paymentRequest.SetCasResponse(invoiceResponse.CASReturnedMessages); // Set the status - for the payment request @@ -156,8 +155,8 @@ private async Task UpdatePaymentRequestWithInvoice(Guid paymentRequestId, Invoic { paymentRequest.SetInvoiceStatus(CasPaymentRequestStatus.ErrorFromCas); } - - await _iPaymentRequestRepository.UpdateAsync(paymentRequest, autoSave: false); + + await paymentRequestRepository.UpdateAsync(paymentRequest, autoSave: false); await uow.SaveChangesAsync(); } catch (Exception ex) @@ -170,13 +169,13 @@ private async Task UpdatePaymentRequestWithInvoice(Guid paymentRequestId, Invoic 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 _resilientRestClient.HttpAsyncWithBody(HttpMethod.Post, resource, jsonString, authToken); + var authToken = await iTokenService.GetAuthTokenAsync(); + var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_APINVOICE}/"; + var response = await resilientHttpRequest.HttpAsyncWithBody(HttpMethod.Post, resource, jsonString, authToken); if (response != null) { - if(response.Content != null && response.StatusCode != HttpStatusCode.NotFound) + if (response.Content != null && response.StatusCode != HttpStatusCode.NotFound) { var contentString = ResilientHttpRequest.ContentToString(response.Content); var result = JsonSerializer.Deserialize(contentString) @@ -187,7 +186,8 @@ public async Task CreateInvoiceAsync(Invoice casAPInvoice) else if (response.RequestMessage != null) { throw new UserFriendlyException("CAS InvoiceService CreateInvoiceAsync Exception: " + response.RequestMessage); - } else + } + else { throw new UserFriendlyException("CAS InvoiceService CreateInvoiceAsync Exception: " + response); } @@ -200,30 +200,30 @@ 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 response = await _resilientRestClient.HttpAsync(HttpMethod.Get, resource, authToken); + var authToken = await iTokenService.GetAuthTokenAsync(); + var resource = $"{casClientOptions.Value.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 = Unity.Modules.Shared.Http.ResilientHttpRequest.ContentToString(response.Content); + string contentString = ResilientHttpRequest.ContentToString(response.Content); var result = JsonSerializer.Deserialize(contentString); return result ?? new CasPaymentSearchResult(); } else { - return new CasPaymentSearchResult() {}; + return new CasPaymentSearchResult() { }; } } 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 response = await _resilientRestClient.HttpAsync(HttpMethod.Get, resource, authToken); - CasPaymentSearchResult casPaymentSearchResult = new CasPaymentSearchResult(); + var authToken = await iTokenService.GetAuthTokenAsync(); + var resource = $"{casClientOptions.Value.CasBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{siteNumber}"; + var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, authToken); + CasPaymentSearchResult casPaymentSearchResult = new(); if (response != null && response.Content != null @@ -233,7 +233,7 @@ public async Task GetCasPaymentAsync(string invoiceNumbe var result = JsonSerializer.Deserialize(content.Result); return result ?? casPaymentSearchResult; } - else if(response != null) + else if (response != null) { casPaymentSearchResult.InvoiceStatus = response.StatusCode.ToString(); } 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 4cc6a4911..cbaff113e 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 @@ -82,7 +82,7 @@ private async Task UpdateSupplierInfo(dynamic casSupplierResponse, Guid applican 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."); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/RabbitMQ/InvoiceConsumer.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/RabbitMQ/InvoiceConsumer.cs index 8f3c238e8..1165ebd44 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/RabbitMQ/InvoiceConsumer.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Integrations/RabbitMQ/InvoiceConsumer.cs @@ -3,33 +3,20 @@ using Unity.Payments.RabbitMQ.QueueMessages; using System; using Volo.Abp.MultiTenancy; -using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Integrations.Cas; namespace Unity.Payments.Integrations.RabbitMQ; -public class InvoiceConsumer : IQueueConsumer +public class InvoiceConsumer(InvoiceService invoiceService, + ICurrentTenant currentTenant) : IQueueConsumer { - private readonly ICurrentTenant _currentTenant; - private readonly InvoiceService _invoiceService; - - public InvoiceConsumer( - InvoiceService invoiceService, - IPaymentRequestRepository paymentRequestRepository, - ICurrentTenant currentTenant - ) - { - _invoiceService = invoiceService; - _currentTenant = currentTenant; - } - public async Task ConsumeAsync(InvoiceMessages invoiceMessage) { if (invoiceMessage != null && !invoiceMessage.InvoiceNumber.IsNullOrEmpty() && invoiceMessage.TenantId != Guid.Empty) { - using (_currentTenant.Change(invoiceMessage.TenantId)) + using (currentTenant.Change(invoiceMessage.TenantId)) { - await _invoiceService.CreateInvoiceByPaymentRequestAsync(invoiceMessage.InvoiceNumber); + await invoiceService.CreateInvoiceByPaymentRequestAsync(invoiceMessage.InvoiceNumber); } } return Task.CompletedTask; diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentConfigurations/PaymentConfigurationAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentConfigurations/PaymentConfigurationAppService.cs index f1283952c..d4dc6b0aa 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentConfigurations/PaymentConfigurationAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentConfigurations/PaymentConfigurationAppService.cs @@ -1,21 +1,16 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.Exceptions; using Unity.Payments.Domain.PaymentConfigurations; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Features; namespace Unity.Payments.PaymentConfigurations { [RequiresFeature("Unity.Payments")] - public class PaymentConfigurationAppService : PaymentsAppService, IPaymentConfigurationAppService + public class PaymentConfigurationAppService(IPaymentConfigurationRepository paymentConfigurationRepository) : PaymentsAppService, IPaymentConfigurationAppService { - private readonly IPaymentConfigurationRepository _paymentConfigurationRepository; - - public PaymentConfigurationAppService(IPaymentConfigurationRepository paymentConfigurationRepository) - { - _paymentConfigurationRepository = paymentConfigurationRepository; - } - public virtual async Task GetAsync() { PaymentConfiguration? paymentConfiguration = await FindPaymentConfigurationAsync(); @@ -25,47 +20,33 @@ public PaymentConfigurationAppService(IPaymentConfigurationRepository paymentCon return ObjectMapper.Map(paymentConfiguration); } - public virtual async Task GetAccountDistributionCodeAsync() + public virtual Task GetAccountDistributionCode(AccountCoding accountCoding) { - PaymentConfiguration? paymentConfiguration = await FindPaymentConfigurationAsync(); string accountDistributionCode = ""; - if (paymentConfiguration != null - && paymentConfiguration.Responsibility != null - && paymentConfiguration.ServiceLine != null - && paymentConfiguration.Stob != null - && paymentConfiguration.MinistryClient != null - && paymentConfiguration.ProjectNumber != null) + if (accountCoding != null + && accountCoding.Responsibility != null + && accountCoding.ServiceLine != null + && accountCoding.Stob != null + && accountCoding.MinistryClient != null + && accountCoding.ProjectNumber != null) { string accountDistributionPostFix = "000000.0000"; accountDistributionCode = - $"{paymentConfiguration.MinistryClient}.{paymentConfiguration.Responsibility}.{paymentConfiguration.ServiceLine}.{paymentConfiguration.Stob}.{paymentConfiguration.ProjectNumber}.{accountDistributionPostFix}"; + $"{accountCoding.MinistryClient}.{accountCoding.Responsibility}.{accountCoding.ServiceLine}.{accountCoding.Stob}.{accountCoding.ProjectNumber}.{accountDistributionPostFix}"; } - return accountDistributionCode; + return Task.FromResult(accountDistributionCode); } - public virtual async Task CreateAsync(CreatePaymentConfigurationDto createPaymentConfigurationDto) + public virtual async Task CreateAsync(CreatePaymentConfigurationDto createUpdatePaymentConfigurationDto) { - PaymentConfiguration? paymentConfiguration = await FindPaymentConfigurationAsync(); - - if (paymentConfiguration != null) + PaymentConfiguration? paymentConfiguration = new PaymentConfiguration { - throw new ConfigurationExistsException(L[ErrorConsts.ConfigurationExists]); - } - - var newPaymentConfiguration = await _paymentConfigurationRepository.InsertAsync(new PaymentConfiguration - ( - createPaymentConfigurationDto.PaymentThreshold, - createPaymentConfigurationDto.PaymentIdPrefix, - AccountCoding.Create( - createPaymentConfigurationDto.MinistryClient, - createPaymentConfigurationDto.Responsibility, - createPaymentConfigurationDto.ServiceLine, - createPaymentConfigurationDto.Stob, - createPaymentConfigurationDto.ProjectNumber - ) - )); + DefaultAccountCodingId = createUpdatePaymentConfigurationDto.DefaultAccountCodingId, + PaymentIdPrefix = createUpdatePaymentConfigurationDto.PaymentIdPrefix + }; + var newPaymentConfiguration = await paymentConfigurationRepository.InsertAsync(paymentConfiguration); return ObjectMapper.Map(newPaymentConfiguration); } @@ -74,24 +55,48 @@ public virtual async Task UpdateAsync(UpdatePaymentConf PaymentConfiguration? paymentConfiguration = await FindPaymentConfigurationAsync() ?? throw new ConfigurationExistsException(L[ErrorConsts.ConfigurationDoesNotExist]); - paymentConfiguration.PaymentThreshold = updatePaymentConfigurationDto.PaymentThreshold; paymentConfiguration.PaymentIdPrefix = updatePaymentConfigurationDto.PaymentIdPrefix; - - paymentConfiguration.SetAccountCoding(AccountCoding.Create(updatePaymentConfigurationDto.MinistryClient, - updatePaymentConfigurationDto.Responsibility, - updatePaymentConfigurationDto.ServiceLine, - updatePaymentConfigurationDto.Stob, - updatePaymentConfigurationDto.ProjectNumber)); - - var updatedConfiguration = await _paymentConfigurationRepository.UpdateAsync(paymentConfiguration); + var updatedConfiguration = await paymentConfigurationRepository.UpdateAsync(paymentConfiguration); return ObjectMapper.Map(updatedConfiguration); } + + public async Task UpdatePaymentPrefixAsync(string paymentPrefix) + { + PaymentConfiguration? paymentConfiguration = await paymentConfigurationRepository.FirstOrDefaultAsync(); + if (paymentConfiguration == null) + { + CreatePaymentConfigurationDto paymentConfigurationDto = new CreatePaymentConfigurationDto(); + paymentConfigurationDto.PaymentIdPrefix = paymentPrefix; + await CreateAsync(paymentConfigurationDto); + } + else + { + paymentConfiguration.PaymentIdPrefix = paymentPrefix; + await paymentConfigurationRepository.UpdateAsync(paymentConfiguration); + } + } + public async Task SetDefaultAccountCodeAsync(Guid accountCodingId) + { + PaymentConfiguration? paymentConfiguration = await paymentConfigurationRepository.FirstOrDefaultAsync(); + + if (paymentConfiguration == null) + { + CreatePaymentConfigurationDto paymentConfigurationDto = new CreatePaymentConfigurationDto(); + paymentConfigurationDto.DefaultAccountCodingId = accountCodingId; + await CreateAsync(paymentConfigurationDto); + } + else + { + paymentConfiguration.DefaultAccountCodingId = accountCodingId; + await paymentConfigurationRepository.UpdateAsync(paymentConfiguration); + } + } protected virtual async Task FindPaymentConfigurationAsync() { - var paymentConfigurations = await _paymentConfigurationRepository.GetListAsync(); + var paymentConfigurations = await paymentConfigurationRepository.GetListAsync(); var paymentConfiguration = paymentConfigurations.Count > 0 ? paymentConfigurations[0] : null; return paymentConfiguration; } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/FinancialSummaryService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/FinancialSummaryService.cs index 220c1c4a5..1b21e64e0 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/FinancialSummaryService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentRequests/FinancialSummaryService.cs @@ -13,6 +13,7 @@ using Unity.Notifications.Events; using System.Text; using Volo.Abp.EventBus.Local; +using Unity.GrantManager.Identity; namespace Unity.Payments.PaymentRequests { @@ -23,7 +24,6 @@ public class FinancialSummaryService : ApplicationService private readonly ICurrentTenant _currentTenant; private readonly IIdentityUserIntegrationService _identityUserLookupAppService; private readonly ILocalEventBus _localEventBus; - public const string FinancialAnalyst = "financial_analyst"; public FinancialSummaryService ( IIdentityUserIntegrationService identityUserIntegrationService, @@ -108,7 +108,7 @@ public async Task> GetFinancialAnalystEmails() foreach (var user in users.Items) { var roles = await _identityUserLookupAppService.GetRoleNamesAsync(user.Id); - if(roles != null && roles.Contains(FinancialAnalyst) ) + if(roles != null && roles.Contains(UnityRoles.FinancialAnalyst) ) { financialAnalystEmails.Add(user.Email); } 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 64537771c..b54839aa6 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 @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Unity.Payment.Shared; using Unity.Payments.Domain.Exceptions; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; @@ -15,73 +14,55 @@ using Unity.Payments.Permissions; using Volo.Abp; using Volo.Abp.Application.Dtos; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Data; using Volo.Abp.Features; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Users; +using Unity.Payments.Domain.PaymentThresholds; +using Volo.Abp.Domain.Repositories; +using Unity.GrantManager.Applications; namespace Unity.Payments.PaymentRequests { [RequiresFeature("Unity.Payments")] [Authorize] - public class PaymentRequestAppService : PaymentsAppService, IPaymentRequestAppService - { - private readonly ICurrentUser _currentUser; - private readonly IDataFilter _dataFilter; - private readonly IExternalUserLookupServiceProvider _externalUserLookupServiceProvider; - private readonly IPaymentConfigurationRepository _paymentConfigurationRepository; - private readonly IPaymentsManager _paymentsManager; - private readonly IPaymentRequestRepository _paymentRequestsRepository; - private readonly IPermissionChecker _permissionChecker; - - public PaymentRequestAppService( - ICurrentUser currentUser, - IDataFilter dataFilter, - IExternalUserLookupServiceProvider externalUserLookupServiceProvider, - IPaymentConfigurationRepository paymentConfigurationRepository, - IPaymentsManager paymentsManager, - IPaymentRequestRepository paymentRequestsRepository, - IPermissionChecker permissionChecker) - { - _currentUser = currentUser; - _dataFilter = dataFilter; - _externalUserLookupServiceProvider = externalUserLookupServiceProvider; - _paymentConfigurationRepository = paymentConfigurationRepository; - _paymentsManager = paymentsManager; - _paymentRequestsRepository = paymentRequestsRepository; - _permissionChecker = permissionChecker; - } + #pragma warning disable S107 // Suppress "Constructor has too many parameters" + public class PaymentRequestAppService( + ICurrentUser currentUser, + IDataFilter dataFilter, + IExternalUserLookupServiceProvider externalUserLookupServiceProvider, + IApplicationRepository applicationRepository, + IApplicationFormRepository applicationFormRepository, + IPaymentConfigurationRepository paymentConfigurationRepository, + IPaymentsManager paymentsManager, + IPaymentRequestRepository paymentRequestsRepository, + IPaymentThresholdRepository paymentThresholdRepository, + IPermissionChecker permissionChecker) : PaymentsAppService, IPaymentRequestAppService + #pragma warning restore S107 - protected virtual async Task<(PaymentConfiguration? Config, decimal Threshold)> GetPaymentConfigurationWithThresholdAsync() + { + public async Task GetDefaultAccountCodingId() { - var paymentConfigs = await _paymentConfigurationRepository.GetListAsync(); - var paymentConfig = paymentConfigs.FirstOrDefault(); - - if (paymentConfig == null) + Guid? accountCodingId = null; + // If no account coding is found look up the payment configuration + PaymentConfiguration? paymentConfiguration = await GetPaymentConfigurationAsync(); + if (paymentConfiguration != null && paymentConfiguration.DefaultAccountCodingId.HasValue) { - return (null, PaymentSharedConsts.DefaultThresholdAmount); + accountCodingId = paymentConfiguration.DefaultAccountCodingId; } - - return (paymentConfig, paymentConfig.PaymentThreshold ?? PaymentSharedConsts.DefaultThresholdAmount); + return accountCodingId; } [Authorize(PaymentsPermissions.Payments.RequestPayment)] public virtual async Task> CreateAsync(List paymentRequests) { List createdPayments = []; - var (paymentConfig, paymentThreshold) = await GetPaymentConfigurationWithThresholdAsync(); + var paymentConfig = await GetPaymentConfigurationAsync(); var paymentIdPrefix = string.Empty; - if (paymentConfig != null) + if (paymentConfig != null && !paymentConfig.PaymentIdPrefix.IsNullOrEmpty()) { - if (paymentConfig.PaymentThreshold != null) - { - paymentThreshold = (decimal)paymentConfig.PaymentThreshold; - } - if (!paymentConfig.PaymentIdPrefix.IsNullOrEmpty()) - { - paymentIdPrefix = paymentConfig.PaymentIdPrefix; - } + paymentIdPrefix = paymentConfig.PaymentIdPrefix; } var batchNumber = await GetMaxBatchNumberAsync(); @@ -98,16 +79,15 @@ public virtual async Task> CreateAsync(List> CreateAsync(List GetNextBatchInfoAsync() { - var (paymentConfig, _) = await GetPaymentConfigurationWithThresholdAsync(); + var paymentConfig = await GetPaymentConfigurationAsync(); var paymentIdPrefix = string.Empty; if (paymentConfig != null && !paymentConfig.PaymentIdPrefix.IsNullOrEmpty()) @@ -176,7 +156,7 @@ private static string GenerateReferenceNumberPrefixAsync(string paymentIdPrefix) private async Task GetMaxBatchNumberAsync() { - var paymentRequestList = await _paymentRequestsRepository.GetListAsync(); + var paymentRequestList = await paymentRequestsRepository.GetListAsync(); decimal batchNumber = 1; // Lookup max plus 1 if (paymentRequestList != null && paymentRequestList.Count > 0) { @@ -193,18 +173,16 @@ private async Task GetMaxBatchNumberAsync() public Task GetPaymentRequestCountBySiteIdAsync(Guid siteId) { - return _paymentRequestsRepository.GetPaymentRequestCountBySiteId(siteId); + return paymentRequestsRepository.GetPaymentRequestCountBySiteId(siteId); } public virtual async Task> UpdateStatusAsync(List paymentRequests) { - List updatedPayments = []; - - var paymentThreshold = await GetPaymentThresholdAsync(); + List updatedPayments = []; // Check approval batches var approvalRequests = paymentRequests.Where(r => r.IsApprove).Select(x => x.PaymentRequestId).ToList(); - var approvalList = await _paymentRequestsRepository.GetListAsync(x => approvalRequests.Contains(x.Id), includeDetails: true); + var approvalList = await paymentRequestsRepository.GetListAsync(x => approvalRequests.Contains(x.Id), includeDetails: true); // Rule AB#26693: Reject Payment Request update batch if violates L1 and L2 separation of duties if (approvalList.Any( @@ -220,12 +198,24 @@ public virtual async Task> UpdateStatusAsync(List> UpdateStatusAsync(List DetermineTriggerActionAsync( UpdatePaymentStatusRequestDto dto, - PaymentRequest payment, - decimal paymentThreshold) + PaymentRequest payment) { - if (await CanPerformLevel1ActionAsync(payment.Status)) + if (payment == null) { - return dto.IsApprove ? PaymentApprovalAction.L1Approve : PaymentApprovalAction.L1Decline; + Logger.LogWarning("Payment is null in DetermineTriggerActionAsync."); + return PaymentApprovalAction.None; } - if (await CanPerformLevel2ActionAsync(payment, dto.IsApprove)) + try { - if (dto.IsApprove) - { - return payment.Amount > paymentThreshold - ? PaymentApprovalAction.L2Approve - : PaymentApprovalAction.Submit; - } - return PaymentApprovalAction.L2Decline; - } + if (await CanPerformLevel1ActionAsync(payment.Status)) + return dto.IsApprove ? PaymentApprovalAction.L1Approve : PaymentApprovalAction.L1Decline; + + if (await CanPerformLevel2ActionAsync(payment, dto.IsApprove)) + return await GetLevel2ApprovalActionAsync(dto, payment); - if (await CanPerformLevel3ActionAsync(payment.Status)) + if (await CanPerformLevel3ActionAsync(payment.Status)) + return dto.IsApprove ? PaymentApprovalAction.Submit : PaymentApprovalAction.L3Decline; + } + catch (Exception ex) { - return dto.IsApprove ? PaymentApprovalAction.Submit : PaymentApprovalAction.L3Decline; + Logger.LogException(ex); } return PaymentApprovalAction.None; } + private async Task GetLevel2ApprovalActionAsync(UpdatePaymentStatusRequestDto dto, PaymentRequest payment) + { + if (!dto.IsApprove) + return PaymentApprovalAction.L2Decline; + + decimal? threshold = null; + try + { + decimal? userPaymentThreshold = await GetUserPaymentThresholdAsync(); + threshold = await GetPaymentRequestThresholdByApplicationIdAsync(payment.CorrelationId, userPaymentThreshold); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Failed to get payment threshold for applicationId: {CorrelationId}", payment.CorrelationId); + } + + if (threshold.HasValue && payment.Amount > threshold.Value) + return PaymentApprovalAction.L2Approve; + + return PaymentApprovalAction.Submit; + } + public async Task GetPaymentRequestThresholdByApplicationIdAsync(Guid applicationId, decimal? userPaymentThreshold = null) + { + 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."); + } + + var appForm = application.ApplicationForm ?? + (application.ApplicationFormId != Guid.Empty + ? await applicationFormRepository.GetAsync(application.ApplicationFormId) + : null); + + var formThreshold = appForm?.PaymentApprovalThreshold; + + if (formThreshold.HasValue && userPaymentThreshold.HasValue) + { + return Math.Min(formThreshold.Value, userPaymentThreshold.Value); + } + + return formThreshold ?? userPaymentThreshold ?? 0m; + } + private async Task CanPerformLevel1ActionAsync(PaymentRequestStatus status) { List level1Approvals = new() { PaymentRequestStatus.L1Pending, PaymentRequestStatus.L1Declined }; - return await _permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L1ApproveOrDecline) && level1Approvals.Contains(status); + 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 }; - + // 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; if (IsSameApprover && IsApprove) @@ -285,18 +322,18 @@ private async Task CanPerformLevel2ActionAsync(PaymentRequest payment, boo code: ErrorConsts.L2ApproverRestriction, message: L[ErrorConsts.L2ApproverRestriction]); } - return await _permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L2ApproveOrDecline) && level2Approvals.Contains(payment.Status); + return await permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L2ApproveOrDecline) && level2Approvals.Contains(payment.Status); } private async Task CanPerformLevel3ActionAsync(PaymentRequestStatus status) { List level3Approvals = new() { PaymentRequestStatus.L3Pending, PaymentRequestStatus.L3Declined }; - return await _permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L3ApproveOrDecline) && level3Approvals.Contains(status); + return await permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.L3ApproveOrDecline) && level3Approvals.Contains(status); } private async Task CreatePaymentRequestDtoAsync(Guid paymentRequestId) { - var payment = await _paymentRequestsRepository.GetAsync(paymentRequestId); + var payment = await paymentRequestsRepository.GetAsync(paymentRequestId); return new PaymentRequestDto { Id = payment.Id, @@ -312,13 +349,14 @@ private async Task CreatePaymentRequestDtoAsync(Guid paymentR CreationTime = payment.CreationTime, Status = payment.Status, ReferenceNumber = payment.ReferenceNumber, - SubmissionConfirmationCode = payment.SubmissionConfirmationCode + SubmissionConfirmationCode = payment.SubmissionConfirmationCode, + Note = payment.Note }; } public async Task> GetListByApplicationIdsAsync(List applicationIds) { - var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync(); + var paymentsQueryable = await paymentRequestsRepository.GetQueryableAsync(); var payments = await paymentsQueryable.Include(pr => pr.Site).ToListAsync(); var filteredPayments = payments.Where(pr => applicationIds.Contains(pr.CorrelationId)).ToList(); @@ -327,20 +365,24 @@ public async Task> GetListByApplicationIdsAsync(List> GetListAsync(PagedAndSortedResultRequestDto input) { - var totalCount = await _paymentRequestsRepository.GetCountAsync(); - using (_dataFilter.Disable()) + var totalCount = await paymentRequestsRepository.GetCountAsync(); + using (dataFilter.Disable()) { - await _paymentRequestsRepository + await paymentRequestsRepository .GetPagedListAsync(input.SkipCount, input.MaxResultCount, input.Sorting ?? string.Empty, includeDetails: true); // Include PaymentTags in the query - var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync(); - var paymentsWithTags = await paymentsQueryable + var paymentsQueryable = await paymentRequestsRepository.GetQueryableAsync(); + // Changing this breaks the code so suppressing the warning +#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + var paymentWithIncludes = await paymentsQueryable + .Include(pr => pr.AccountCoding) .Include(pr => pr.PaymentTags) .ThenInclude(pt => pt.Tag) .ToListAsync(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. - var mappedPayments = await MapToDtoAndLoadDetailsAsync(paymentsWithTags); + var mappedPayments = await MapToDtoAndLoadDetailsAsync(paymentWithIncludes); ApplyErrorSummary(mappedPayments); @@ -372,7 +414,7 @@ protected internal async Task> MapToDtoAndLoadDetailsAsy var allUserIds = paymentRequesterIds.Concat(expenseApprovalCreatorIds).Distinct(); foreach (var userId in allUserIds) { - var userInfo = await _externalUserLookupServiceProvider.FindByIdAsync(userId); + var userInfo = await externalUserLookupServiceProvider.FindByIdAsync(userId); if (userInfo != null) { userDictionary[userId] = ObjectMapper.Map(userInfo); @@ -388,6 +430,11 @@ protected internal async Task> MapToDtoAndLoadDetailsAsy paymentRequestDto.CreatorUser = paymentRequestUserDto; } + if(paymentRequestDto != null && paymentRequestDto.AccountCoding != null) + { + paymentRequestDto.AccountCodingDisplay = await GetAccountDistributionCode(paymentRequestDto.AccountCoding); + } + foreach (var expenseApproval in paymentRequestDto.ExpenseApprovals) { if (expenseApproval.DecisionUserId.HasValue @@ -401,6 +448,25 @@ protected internal async Task> MapToDtoAndLoadDetailsAsy return paymentDtos; } + public virtual Task GetAccountDistributionCode(AccountCodingDto? accountCoding) + { + string accountDistributionCode = ""; + if (accountCoding == null) return Task.FromResult(accountDistributionCode); + + if (accountCoding.Responsibility != null + && accountCoding.ServiceLine != null + && accountCoding.Stob != null + && accountCoding.MinistryClient != null + && accountCoding.ProjectNumber != null) + { + const string DefaultAccountDistributionPostfix = "000000.0000"; + accountDistributionCode = + $"{accountCoding.MinistryClient}.{accountCoding.Responsibility}.{accountCoding.ServiceLine}.{accountCoding.Stob}.{accountCoding.ProjectNumber}.{DefaultAccountDistributionPostfix}"; + } + + return Task.FromResult(accountDistributionCode); + } + private static void ApplyErrorSummary(List mappedPayments) { mappedPayments.ForEach(mappedPayment => @@ -415,19 +481,19 @@ private static void ApplyErrorSummary(List mappedPayments) public async Task> GetListByApplicationIdAsync(Guid applicationId) { - using (_dataFilter.Disable()) + using (dataFilter.Disable()) { - var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync(); + var paymentsQueryable = await paymentRequestsRepository.GetQueryableAsync(); var payments = await paymentsQueryable.Include(pr => pr.Site).ToListAsync(); var filteredPayments = payments.Where(e => e.CorrelationId == applicationId).ToList(); - return new List(ObjectMapper.Map, List>(filteredPayments)); + return ObjectMapper.Map, List>(filteredPayments); } } public async Task> GetListByPaymentIdsAsync(List paymentIds) { - var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync(); + var paymentsQueryable = await paymentRequestsRepository.GetQueryableAsync(); var payments = await paymentsQueryable .Where(e => paymentIds.Contains(e.Id)) .Include(pr => pr.Site) @@ -439,17 +505,23 @@ public async Task> GetListByPaymentIdsAsync(List p public virtual async Task GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid correlationId) { - return await _paymentRequestsRepository.GetTotalPaymentRequestAmountByCorrelationIdAsync(correlationId); + return await paymentRequestsRepository.GetTotalPaymentRequestAmountByCorrelationIdAsync(correlationId); + } + + public async Task GetUserPaymentThresholdAsync() + { + var userThreshold = await paymentThresholdRepository.FirstOrDefaultAsync(x => x.UserId == currentUser.Id); + return userThreshold?.Threshold; } protected virtual string GetCurrentRequesterName() { - return $"{_currentUser.Name} {_currentUser.SurName}"; + return $"{currentUser.Name} {currentUser.SurName}"; } protected virtual async Task GetPaymentConfigurationAsync() { - var paymentConfigs = await _paymentConfigurationRepository.GetListAsync(); + var paymentConfigs = await paymentConfigurationRepository.GetListAsync(); if (paymentConfigs.Count > 0) { @@ -460,23 +532,10 @@ protected virtual string GetCurrentRequesterName() return null; } - protected virtual async Task GetPaymentThresholdAsync() - { - var paymentConfigs = await _paymentConfigurationRepository.GetListAsync(); - - if (paymentConfigs.Count > 0) - { - var paymentConfig = paymentConfigs[0]; - return paymentConfig.PaymentThreshold ?? PaymentSharedConsts.DefaultThresholdAmount; - } - - return PaymentSharedConsts.DefaultThresholdAmount; - } - private async Task GetNextSequenceNumberAsync(int currentYear) { // Retrieve all payment requests - var payments = await _paymentRequestsRepository.GetListAsync(); + var payments = await paymentRequestsRepository.GetListAsync(); // Filter payments for the current year var filteredPayments = payments diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs index 2e0eb04c4..50188c4ff 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentTags/PaymentTagAppService.cs @@ -6,12 +6,12 @@ using System.Threading.Tasks; using Unity.Modules.Shared; using Unity.Payments.Domain.PaymentTags; +using Unity.Payments.Events; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Features; using Volo.Abp.EventBus.Local; -using Unity.Payments.Events; +using Volo.Abp.Features; namespace Unity.Payments.PaymentTags @@ -51,13 +51,11 @@ public async Task> GetListWithPaymentRequestIdsAsync(List(paymentTags); } - + public async Task> AssignTagsAsync(AssignPaymentTagDto input) { var existingApplicationTags = await _paymentTagRepository.GetListAsync(e => e.PaymentRequestId == input.PaymentRequestId); - - var existingTagIds = existingApplicationTags.Select(t => t.TagId).ToHashSet(); var inputTagIds = input.Tags?.Select(t => t.Id).ToHashSet() ?? new HashSet(); var newTagsToAdd = input.Tags? @@ -68,17 +66,17 @@ public async Task> AssignTagsAsync(AssignPaymentTagDto input TagId = tag.Id }) .ToList(); + var tagsToRemove = existingApplicationTags - .Where(et => !inputTagIds.Contains(et.TagId)) - .ToList(); - + .Where(et => !inputTagIds.Contains(et.TagId)) + .ToList(); - if (tagsToRemove.Count > 0) + if (tagsToRemove.Count > 0 && await AuthorizationService.IsGrantedAsync(UnitySelector.Payment.Tags.Delete)) { await _paymentTagRepository.DeleteManyAsync(tagsToRemove, autoSave: true); } - - if (newTagsToAdd?.Count > 0) + + if (newTagsToAdd?.Count > 0 && await AuthorizationService.IsGrantedAsync(UnitySelector.Payment.Tags.Create)) { await _paymentTagRepository.InsertManyAsync(newTagsToAdd, autoSave: true); var tagIds = newTagsToAdd.Select(x => x.TagId).ToList(); @@ -92,7 +90,7 @@ public async Task> AssignTagsAsync(AssignPaymentTagDto input } else { - return new List(); + return []; } } @@ -161,12 +159,12 @@ public async Task> RenameTagAsync(string originalTag, string replacem /// /// String of tag to be deleted. [Authorize(UnitySelector.SettingManagement.Tags.Delete)] - public async Task DeleteTagAsync(Guid id ) + public async Task DeleteTagAsync(Guid id) { - await _paymentTagRepository.DeleteAsync(id); - } - - + await _paymentTagRepository.DeleteAsync(id); + } + + [Authorize(UnitySelector.SettingManagement.Tags.Delete)] public async Task DeleteTagWithTagIdAsync(Guid tagId) { diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs index a2ccc203a..1903c3ef4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/PaymentsApplicationAutoMapperProfile.cs @@ -1,16 +1,21 @@ using AutoMapper; using Unity.GrantManager.Applications; using Unity.GrantManager.GlobalTag; +using Unity.GrantManager.Payments; +using Unity.Payments.Domain.AccountCodings; using Unity.Payments.Domain.PaymentConfigurations; using Unity.Payments.Domain.PaymentRequests; using Unity.Payments.Domain.PaymentTags; +using Unity.Payments.Domain.PaymentThresholds; using Unity.Payments.Domain.Suppliers; using Unity.Payments.PaymentConfigurations; using Unity.Payments.PaymentRequests; using Unity.Payments.PaymentTags; +using Unity.Payments.PaymentThresholds; using Unity.Payments.Suppliers; using Volo.Abp.Users; + namespace Unity.Payments; public class PaymentsApplicationAutoMapperProfile : Profile @@ -18,7 +23,9 @@ public class PaymentsApplicationAutoMapperProfile : Profile public PaymentsApplicationAutoMapperProfile() { CreateMap() - .ForMember(dest => dest.ErrorSummary, options => options.Ignore()) + .ForMember(dest => dest.ErrorSummary, opt => opt.Ignore()) + .ForMember(dest => dest.AccountCoding, opt => opt.MapFrom(src => src.AccountCoding)) + .ForMember(dest => dest.AccountCodingDisplay, opt => opt.Ignore()) .ForMember(dest => dest.Site, opt => opt.MapFrom(src => src.Site)) .ForMember(dest => dest.CreatorUser, opt => opt.Ignore()) .ForMember(dest => dest.PaymentTags, opt => opt.MapFrom(src => src.PaymentTags)); @@ -30,7 +37,44 @@ public PaymentsApplicationAutoMapperProfile() CreateMap() .ForMember(dest => dest.PaymentGroup, opt => opt.MapFrom(s => s.PaymentGroup.ToString())); CreateMap(); + + CreateMap() + .ForMember(dest => dest.TenantId, opt => opt.Ignore()) + .ForMember(dest => dest.LastModificationTime, opt => opt.Ignore()) + .ForMember(dest => dest.LastModifierId, opt => opt.Ignore()) + .ForMember(dest => dest.CreationTime, opt => opt.Ignore()) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.ExtraProperties, opt => opt.Ignore()) + .ForMember(dest => dest.ConcurrencyStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Ignore()); CreateMap(); + CreateMap(); + CreateMap(); + CreateMap() + .ForMember(dest => dest.TenantId, opt => opt.Ignore()) + .ForMember(dest => dest.LastModificationTime, opt => opt.Ignore()) + .ForMember(dest => dest.LastModifierId, opt => opt.Ignore()) + .ForMember(dest => dest.CreationTime, opt => opt.Ignore()) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.ExtraProperties, opt => opt.Ignore()) + .ForMember(dest => dest.ConcurrencyStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.UserName, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.UserName, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.TenantId, opt => opt.Ignore()) + .ForMember(dest => dest.IsDeleted, opt => opt.Ignore()) + .ForMember(dest => dest.DeleterId, opt => opt.Ignore()) + .ForMember(dest => dest.DeletionTime, opt => opt.Ignore()) + .ForMember(dest => dest.LastModificationTime, opt => opt.Ignore()) + .ForMember(dest => dest.LastModifierId, opt => opt.Ignore()) + .ForMember(dest => dest.CreationTime, opt => opt.Ignore()) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.ExtraProperties, opt => opt.Ignore()) + .ForMember(dest => dest.ConcurrencyStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Ignore()); CreateMap(); CreateMap(); CreateMap(); diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs index b1805a955..8f9610d1b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/Permissions/PaymentsPermissionDefinitionProvider.cs @@ -1,4 +1,4 @@ -using Unity.Modules.Shared; +using Unity.Modules.Shared; using Unity.Payments.Localization; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; @@ -21,6 +21,14 @@ public override void Define(IPermissionDefinitionContext context) //-- PAYMENT INFO PERMISSIONS grantApplicationPermissionsGroup.Add_PaymentInfo_Permissions(); + paymentsPermissions.AddChild(PaymentsPermissions.Payments.EditFormPaymentConfiguration, L("Permission:Payments.EditFormPaymentConfiguration")); + + var tagsPermissionsGroup = context.GetGroupOrNull("Tags"); + if (tagsPermissionsGroup != null) + { + tagsPermissionsGroup.AddPermission(UnitySelector.Payment.Tags.Create, L(UnitySelector.Payment.Tags.Create)).RequireFeatures("Unity.Payments"); + tagsPermissionsGroup.AddPermission(UnitySelector.Payment.Tags.Delete, L(UnitySelector.Payment.Tags.Delete)).RequireFeatures("Unity.Payments"); + } } private static LocalizableString L(string name) @@ -36,7 +44,8 @@ public static void Add_PaymentInfo_Permissions(this PermissionGroupDefinition gr { #region PAYMENT INFO GRANULAR PERMISSIONS var upx_Payment = grantApplicationPermissionsGroup - .AddPermission(UnitySelector.Payment.Default, LocalizableString.Create(UnitySelector.Payment.Default)); + .AddPermission(UnitySelector.Payment.Default, LocalizableString.Create(UnitySelector.Payment.Default)) + .RequireFeatures("Unity.Payments"); var upx_Payment_Summary = upx_Payment.AddPaymentChild(UnitySelector.Payment.Summary.Default); @@ -47,10 +56,10 @@ public static void Add_PaymentInfo_Permissions(this PermissionGroupDefinition gr #endregion } - + public static PermissionDefinition AddPaymentChild(this PermissionDefinition parent, string name) { - return parent.AddChild(name, LocalizableString.Create(name)); + return parent.AddChild(name, LocalizableString.Create(name)).RequireFeatures("Unity.Payments"); } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json index d70f63207..6d2d0da0b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Localization/Payments/en.json @@ -24,6 +24,7 @@ "ApplicationPaymentRequest:BatchNumberName": "Batch #", "ApplicationPaymentRequest:NumberPayment": "Number of Payments", "ApplicationPaymentRequest:TotalAmount": "Total Amount", + "ApplicationPaymentRequest:BatchNote": "Note", "ApplicationPaymentRequest:Validations:RemainingAmountExceeded": "Cannot add a payment that exceeds the remaining amount of ", "ApplicationPaymentRequest:Validations:L2ApproverRestriction": "You cannot approve this payment as you have already approved it as an L1 Approver.", @@ -59,6 +60,7 @@ "ApplicationPaymentListTable:CASResponse": "CAS Response", "ApplicationPaymentListTable:InvoiceStatus": "Invoice Status", "ApplicationPaymentListTable:PaymentStatus": "CAS Payment Status", + "ApplicationPaymentListTable:Note": "Note", "ApplicantInfoView:SupplierInfoTitle": "Supplier Info", "ApplicantInfoView:ApplicantInfo:SupplierNumber": "Supplier #", @@ -116,7 +118,7 @@ "Permission:Payments.L2ApproveOrDecline": "Approve/Decline L2 Payments", "Permission:Payments.L3ApproveOrDecline": "Approve/Decline L3 Payments", "Permission:Payments.RequestPayment": "Request Payment", - + "Permission:Payments.EditFormPaymentConfiguration": "Edit Form Payment Configuration", "Enum:PaymentRequestStatus.L1Pending": "L1 Pending", "Enum:PaymentRequestStatus.L1Approved": "L1 Approved", "Enum:PaymentRequestStatus.L1Declined": "L1 Declined", @@ -127,6 +129,7 @@ "Enum:PaymentRequestStatus.L3Approved": "L3 Approved", "Enum:PaymentRequestStatus.L3Declined": "L3 Declined", "Enum:PaymentRequestStatus.Submitted": "Submitted to CAS", + "Enum:PaymentRequestStatus.FSB": "Sent to Accounts Payable", "Enum:PaymentRequestStatus.Validated": "Validated", "Enum:PaymentRequestStatus.NotValidated": "Not Validated", "Enum:PaymentRequestStatus.Paid": "Paid", @@ -137,6 +140,9 @@ "Unity.GrantManager.ApplicationManagement.Payment.Summary": "Payment Summary", "Unity.GrantManager.ApplicationManagement.Payment.Supplier": "Supplier Info", "Unity.GrantManager.ApplicationManagement.Payment.Supplier.Update": "Edit Supplier Info", - "Unity.GrantManager.ApplicationManagement.Payment.PaymentList": "Payment List" + "Unity.GrantManager.ApplicationManagement.Payment.PaymentList": "Payment List", + + "Unity.Payments.Tags.Create": "Assign Tag to Payments", + "Unity.Payments.Tags.Delete": "Remove Tag from Payments" } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs index 531c62646..fc2891703 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Shared/Permissions/PaymentsPermissions.cs @@ -15,6 +15,7 @@ public static class Payments public const string Decline = Default + ".Decline"; public const string RequestPayment = Default + ".RequestPayment"; public const string EditSupplierInfo = Default + ".EditSupplierInfo"; + public const string EditFormPaymentConfiguration = Default + ".EditFormPaymentConfiguration"; } public static string[] GetAll() diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml new file mode 100644 index 000000000..fcda3d04e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml @@ -0,0 +1,26 @@ +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model Unity.Payments.Web.Pages.AccountCoding.CreateModalModel + +@{ + Layout = null; +} + + + + + + + + + Account Coding + + + + + + + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml.cs new file mode 100644 index 000000000..41ce4e2e3 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/CreateModal.cshtml.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Unity.GrantManager.Payments; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.Payments.Web.Pages.AccountCoding; + +public class CreateModalModel(IAccountCodingAppService accountCodingAppService) : AbpPageModel +{ + [BindProperty] + public CreateUpdateAccountCodingDto? AccountCoding { get; set; } + + public async Task OnPostAsync() + { + try + { + await accountCodingAppService.CreateAsync(AccountCoding!); + return NoContent(); + } + catch (DbUpdateException ex) when (ex.InnerException?.Message?.Contains("duplicate key") == true) + { + throw new UserFriendlyException("This Account Coding already exists"); + } + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml new file mode 100644 index 000000000..900ec8ec4 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml @@ -0,0 +1,28 @@ +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal + +@model Unity.Payments.Web.Pages.AccountCoding.UpdateModalModel + +@{ + Layout = null; +} + + + + + + + + + Account Coding + + + + + + + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml.cs new file mode 100644 index 000000000..58a57638f --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/AccountCoding/UpdateModal.cshtml.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Unity.GrantManager.Payments; +using Unity.Payments.PaymentRequests; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Unity.Payments.Web.Pages.AccountCoding; + +public class UpdateModalModel(IAccountCodingAppService accountCodingAppService) : AbpPageModel +{ + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + [BindProperty] + public CreateUpdateAccountCodingDto? AccountCoding { get; set; } + + + public async Task OnGetAsync() + { + var accountCodingDto = await accountCodingAppService.GetAsync(Id); + AccountCoding = ObjectMapper.Map(accountCodingDto); + } + + public async Task OnPostAsync() + { + await accountCodingAppService.UpdateAsync(Id, AccountCoding!); + return NoContent(); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs index 45c3925de..6b7acbade 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/PaymentsApprovalModel.cs @@ -45,6 +45,7 @@ public class PaymentsApprovalModel : IValidatableObject public string ToStatusText { get; set; } = string.Empty; public Guid? PreviousL1Approver { get; set; } + public bool HasUserPaymentThreshold { get; set; } public bool IsApproval { get; set; } public bool IsValid { get; set; } = false; @@ -64,7 +65,14 @@ public IEnumerable Validate(ValidationContext validationContex errorMessage: localizer["ApplicationPaymentRequest:Validations:L2ApproverRestriction"], memberNames: [nameof(PreviousL1Approver)] ); - } + } else if (IsApproval + && Status == PaymentRequestStatus.L2Pending && !HasUserPaymentThreshold) + { + yield return new ValidationResult( + errorMessage: "Your User has not been configured with an Approved Payment Threshold. Please contact your system administrator.", + memberNames: [nameof(PreviousL1Approver)] + ); + } } } } diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml index 5540b4766..63ab8ef87 100644 --- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentApprovals/UpdatePaymentRequestStatus.cshtml @@ -19,7 +19,7 @@ { public static string GetContainerValidationState(bool isValid) { - return isValid ? "single-payment" : "single-payment bg-danger-subtle border-danger-subtle"; + return isValid ? "single-payment payment-item" : "single-payment bg-danger-subtle border-danger-subtle"; } } @@ -36,9 +36,28 @@ } + + + + + + + + + + + + + + + + + + + @if (Model.PaymentGroupings.Count > 0) @@ -68,6 +87,7 @@ +
@@ -142,10 +162,17 @@ } - @if(ModelState.ContainsKey(nameof(PaymentsApprovalModel.PreviousL1Approver)) + @if (ModelState.ContainsKey(nameof(PaymentsApprovalModel.PreviousL1Approver)) && ModelState[nameof(PaymentsApprovalModel.PreviousL1Approver)]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) { - @L["ApplicationPaymentRequest:Validations:L2ApproverRestrictionBatch"] + var previousL1ApproverState = ModelState[nameof(PaymentsApprovalModel.PreviousL1Approver)]; + if (previousL1ApproverState != null) + { + foreach (var error in previousL1ApproverState.Errors) + { + @error.ErrorMessage + } + } }