Skip to content

Commit 7424dcf

Browse files
authored
Merge pull request #1530 from bcgov/dev
Dev
2 parents bcf52fa + f225846 commit 7424dcf

15 files changed

Lines changed: 2827 additions & 204 deletions

File tree

applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net;
88
using System.Net.Http;
99
using System.Threading.Tasks;
10+
using System.Linq.Expressions;
1011
using Unity.Notifications.Emails;
1112
using Unity.Notifications.Events;
1213
using Unity.Notifications.Integrations.Ches;
@@ -60,51 +61,35 @@ IHttpContextAccessor httpContextAccessor
6061
_httpContextAccessor = httpContextAccessor;
6162
}
6263

63-
private const string approvalBody =
64-
@"Hello,<br>
65-
<br>
66-
Thank you for your grant application. We are pleased to inform you that your project has been approved for funding.<br>
67-
A representative from our Program Area will be reaching out to you shortly with more information on next steps.<br>
68-
<br>
69-
Kind regards.<br>
70-
<br>
71-
*ATTENTION - Please do not reply to this email as it is an automated notification which is unable to receive replies.<br>";
72-
73-
private const string declineBody =
74-
@"Hello,<br>
75-
<br>
76-
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.<br>
77-
<br>
78-
We know that a lot of effort goes into developing a proposed project and we appreciate the time you took to prepare your application.<br>
79-
<br>
80-
If you have any questions or concerns, please reach out to program team members who will provide further details regarding the funding decision.<br>
81-
<br>
82-
Thank you again for your application.<br>
83-
<br>
84-
*ATTENTION - Please do not reply to this email as it is an automated notification which is unable to receive replies.<br>";
85-
86-
public string GetApprovalBody()
64+
public async Task DeleteEmail(Guid id)
8765
{
88-
return approvalBody;
66+
await _emailLogsRepository.DeleteAsync(id);
8967
}
9068

91-
public string GetDeclineBody()
69+
public async Task<int> GetEmailsChesWithNoResponseCountAsync()
9270
{
93-
return declineBody;
94-
}
71+
var dbNow = DateTime.UtcNow;
9572

96-
public async Task DeleteEmail(Guid id)
97-
{
98-
await _emailLogsRepository.DeleteAsync(id);
73+
// Create the expression to filter the email logs
74+
Expression<Func<EmailLog, bool>> filter = x =>
75+
(x.Status == EmailStatus.Sent && x.ChesResponse == null) ||
76+
(x.Status == EmailStatus.Initialized && x.CreationTime.AddMinutes(10) < dbNow);
77+
78+
// Fetch all email logs and apply the filter using LINQ
79+
var allEmailLogs = await _emailLogsRepository.GetListAsync();
80+
var emailLogs = allEmailLogs.Where(filter.Compile()).ToList();
81+
82+
// Ensure we're returning 0 if no logs are found
83+
return emailLogs?.Count ?? 0;
9984
}
10085

101-
public async Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status,string? emailTemplateName)
86+
public async Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName)
10287
{
10388
if (string.IsNullOrEmpty(emailTo))
10489
{
10590
return null;
10691
}
107-
92+
10893
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName);
10994
EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId);
11095
emailLog = UpdateMappedEmailLog(emailLog, emailObject);
@@ -170,7 +155,6 @@ public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto i
170155
var pathBase = "/GrantApplications/Details?ApplicationId=";
171156
var baseUrl = $"{scheme}://{host}{pathBase}";
172157
var commentLink = $"{baseUrl}{input.ApplicationId}";
173-
174158
var subject = $"Unity-Comment: {input.Subject}";
175159
var fromEmail = defaultFromAddress ?? "NoReply@gov.bc.ca";
176160
string htmlBody = $@"

applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/IEmailNotificationService.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ public interface IEmailNotificationService : IApplicationService
1717
Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto input);
1818
Task<HttpResponseMessage> SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName);
1919
Task SendEmailToQueue(EmailLog emailLog);
20-
string GetApprovalBody();
21-
string GetDeclineBody();
2220
Task<List<EmailHistoryDto>> GetHistoryByApplicationId(Guid applicationId);
2321
Task UpdateSettings(NotificationsSettingsDto settingsDto);
2422
Task DeleteEmail(Guid id);
23+
Task<int> GetEmailsChesWithNoResponseCountAsync();
2524
}
2625
}

applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Application/EntityFrameworkCore/Repositories/PaymentRequestRepository.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ public async Task<decimal> GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid
5252
var dbSet = await GetDbSetAsync();
5353
decimal applicationPaymentRequestsTotal = dbSet
5454
.Where(p => p.CorrelationId.Equals(correlationId))
55-
.Where(p => p.Status != PaymentRequestStatus.L1Declined && p.Status != PaymentRequestStatus.L2Declined && p.Status != PaymentRequestStatus.L3Declined)
55+
.Where(p => p.Status != PaymentRequestStatus.L1Declined
56+
&& p.Status != PaymentRequestStatus.L2Declined
57+
&& p.Status != PaymentRequestStatus.L3Declined
58+
&& p.InvoiceStatus != CasPaymentRequestStatus.ErrorFromCas)
5659
.GroupBy(p => p.CorrelationId)
5760
.Select(p => p.Sum(q => q.Amount))
5861
.FirstOrDefault();
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using Microsoft.Extensions.Logging;
2+
using Quartz;
3+
using System;
4+
using System.Threading.Tasks;
5+
using Unity.GrantManager.Settings;
6+
using Unity.Modules.Shared.Utils;
7+
using Unity.Notifications.EmailNotifications;
8+
using Unity.Payments.Domain.PaymentRequests;
9+
using Unity.Payments.Enums;
10+
using Volo.Abp.BackgroundWorkers.Quartz;
11+
using Volo.Abp.MultiTenancy;
12+
using Volo.Abp.SettingManagement;
13+
using Volo.Abp.TenantManagement;
14+
15+
namespace Unity.GrantManager.HealthChecks.BackgroundWorkers
16+
{
17+
[DisallowConcurrentExecution]
18+
public class DataHealthCheckWorker : QuartzBackgroundWorkerBase
19+
{
20+
private readonly ICurrentTenant _currentTenant;
21+
private readonly ITenantRepository _tenantRepository;
22+
private readonly IEmailNotificationService _emailNotificationService;
23+
private readonly IPaymentRequestRepository _paymentRequestsRepository;
24+
25+
public DataHealthCheckWorker(ICurrentTenant currentTenant,
26+
ITenantRepository tenantRepository,
27+
ISettingManager settingManager,
28+
IEmailNotificationService emailNotificationService,
29+
IPaymentRequestRepository paymentRequestsRepository)
30+
{
31+
_currentTenant = currentTenant;
32+
_tenantRepository = tenantRepository;
33+
_emailNotificationService = emailNotificationService;
34+
_paymentRequestsRepository = paymentRequestsRepository;
35+
36+
string? envInfo = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
37+
38+
if (string.Equals(envInfo, "Production", StringComparison.OrdinalIgnoreCase))
39+
{
40+
string cronExpression = SettingDefinitions.GetSettingsValue(settingManager, SettingsConstants.BackgroundJobs.DataHealthCheckMonitor_Expression);
41+
42+
JobDetail = JobBuilder
43+
.Create<DataHealthCheckWorker>()
44+
.WithIdentity(nameof(DataHealthCheckWorker))
45+
.Build();
46+
47+
Trigger = TriggerBuilder
48+
.Create()
49+
.WithIdentity(nameof(DataHealthCheckWorker))
50+
.WithSchedule(CronScheduleBuilder.CronSchedule(cronExpression)
51+
.WithMisfireHandlingInstructionIgnoreMisfires())
52+
.Build();
53+
}
54+
}
55+
56+
public override async Task Execute(IJobExecutionContext context)
57+
{
58+
Logger.LogInformation("Executing DataHealthCheckWorker...");
59+
var tenants = await _tenantRepository.GetListAsync();
60+
bool sendEmail = false;
61+
var emailBodyBuilder = new System.Text.StringBuilder();
62+
63+
foreach (var tenant in tenants)
64+
{
65+
using (_currentTenant.Change(tenant.Id, tenant.Name))
66+
{
67+
// Lookup the missing emails
68+
var missingEmailsCount = await _emailNotificationService.GetEmailsChesWithNoResponseCountAsync();
69+
if (missingEmailsCount > 0)
70+
{
71+
Logger.LogWarning("Tenant {TenantName} has {MissingEmailsCount} missing email(s) with a status of Initialized or Sent but no CHES Response.", tenant.Name, missingEmailsCount);
72+
string missingEmailBody = $"Unity tenant {tenant.Name} has {missingEmailsCount} email(s) that were sent but have no CHES Response.";
73+
sendEmail = true;
74+
emailBodyBuilder.AppendLine($"{missingEmailBody}<br />");
75+
}
76+
// Lookup the missing payments
77+
var missingPayments = await GetPaymentsSentWithoutResponseCountAsync();
78+
if (missingPayments > 0)
79+
{
80+
Logger.LogWarning("Tenant {TenantName} has {MissingPaymentsCount} payments sent without a response.", tenant.Name, missingPayments);
81+
string missingPaymentBody = $"Unity tenant {tenant.Name} has {missingPayments} payment(s) that are in Submitted status but have no CAS Response.";
82+
sendEmail = true;
83+
emailBodyBuilder.AppendLine($"{missingPaymentBody}<br />");
84+
}
85+
}
86+
}
87+
88+
if (sendEmail)
89+
{
90+
string emailBody = emailBodyBuilder.ToString();
91+
await SendEmailAlert(emailBody, "Data Health Check Alert - Emails/Payments Missing Responses");
92+
}
93+
94+
Logger.LogInformation("DataHealthCheckWorker Executed...");
95+
await Task.CompletedTask;
96+
}
97+
98+
private async Task<int> GetPaymentsSentWithoutResponseCountAsync()
99+
{
100+
var payments = await _paymentRequestsRepository.GetListAsync(x => x.Status == PaymentRequestStatus.Submitted && x.CasHttpStatusCode == null);
101+
return payments.Count;
102+
}
103+
104+
private async Task SendEmailAlert(string emailBody, string subject)
105+
{
106+
107+
string htmlBody = $@"
108+
<html>
109+
<body style='font-family: Arial, sans-serif;'>
110+
<p>{emailBody}</p>
111+
<br />
112+
<p style='font-size: 12px; color: #999;'>*Note - Please do not reply to this email as it is an automated notification.</p>
113+
</body>
114+
</html>";
115+
116+
await _emailNotificationService.SendEmailNotification(
117+
"grantmanagementsupport@gov.bc.ca",
118+
htmlBody,
119+
subject,
120+
"NoReply@gov.bc.ca", "html",
121+
"");
122+
123+
Logger.LogInformation("Missing Alerts Sent...");
124+
125+
}
126+
}
127+
}

applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Settings/SettingsConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static class BackgroundJobs
2929
{
3030
public const string IntakeResync_Expression = "GrantManager.BackgroundJobs.IntakeResync_Expression";
3131
public const string IntakeResync_NumDaysToCheck = "GrantManager.BackgroundJobs.IntakeResync_NumDaysToCheck";
32+
public const string DataHealthCheckMonitor_Expression = "GrantManager.BackgroundJobs.DataHealthCheckMonitor_Expression";
3233
}
3334
}
3435
}

applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Intakes/IChefsMissedSubmissionsRepository.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Settings/GrantManagerSettingDefinitionProvider.cs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Collections.Generic;
22
using Unity.GrantManager.Localization;
3-
using Unity.Payments.Settings;
3+
using Unity.Payments.Settings;
44
using Volo.Abp.Localization;
55
using Volo.Abp.Settings;
66

@@ -11,13 +11,13 @@ public class GrantManagerSettingDefinitionProvider : SettingDefinitionProvider
1111
public override void Define(ISettingDefinitionContext context)
1212
{
1313
context.Add(
14-
new SettingDefinition(
15-
SettingsConstants.SectorFilterName,
16-
string.Empty,
17-
L("Setting:GrantManager.Locality.SectorFilter.DisplayName"),
18-
L("Setting:GrantManager.Locality.SectorFilter.Description"),
19-
isVisibleToClients: true,
20-
isInherited: false,
14+
new SettingDefinition(
15+
SettingsConstants.SectorFilterName,
16+
string.Empty,
17+
L("Setting:GrantManager.Locality.SectorFilter.DisplayName"),
18+
L("Setting:GrantManager.Locality.SectorFilter.Description"),
19+
isVisibleToClients: true,
20+
isInherited: false,
2121
isEncrypted: false).WithProviders(TenantSettingValueProvider.ProviderName)
2222
);
2323

@@ -37,40 +37,42 @@ public override void Define(ISettingDefinitionContext context)
3737
}
3838

3939
context.Add(
40-
new SettingDefinition(
41-
SettingsConstants.UI.Zones,
42-
string.Empty,
43-
L($"Setting:{SettingsConstants.UI.Zones}.DisplayName"),
44-
L($"Setting:{SettingsConstants.UI.Zones}.Description"),
45-
isVisibleToClients: false,
46-
isInherited: false,
40+
new SettingDefinition(
41+
SettingsConstants.UI.Zones,
42+
string.Empty,
43+
L($"Setting:{SettingsConstants.UI.Zones}.DisplayName"),
44+
L($"Setting:{SettingsConstants.UI.Zones}.Description"),
45+
isVisibleToClients: false,
46+
isInherited: false,
4747
isEncrypted: false)
4848
.WithProviders(
4949
TenantSettingValueProvider.ProviderName,
5050
FormSettingValueProvider.ProviderName)
5151
);
52-
53-
AddBackgroundJobSettingDefinition(context);
54-
}
55-
56-
private static void AddBackgroundJobSettingDefinition(ISettingDefinitionContext currentContext)
57-
{
58-
var backGroundSchedules = new Dictionary<string, string>
59-
{
60-
{ SettingsConstants.BackgroundJobs.IntakeResync_NumDaysToCheck, "-4" },
61-
// 23 = 11 pm So 23 + 8 UTC = 7 also at 19 = 11 am
62-
{ SettingsConstants.BackgroundJobs.IntakeResync_Expression, "0 0 7,19 1/1 * ? *" },
63-
// 24 = 12 am So 24 + 8 UTC = 8
64-
{ PaymentSettingsConstants.BackgroundJobs.CasPaymentsReconciliation_ProducerExpression, "0 0 8 1/1 * ? *" },
65-
// 24 = 1 am So 24 + 8 UTC = 9
66-
{ PaymentSettingsConstants.BackgroundJobs.CasFinancialNotificationSummary_ProducerExpression, "0 0 9 1/1 * ? *" }
67-
};
68-
69-
foreach (var setting in backGroundSchedules)
70-
{
71-
AddSettingDefinition(currentContext, setting.Key, setting.Value.ToString());
72-
}
73-
52+
53+
AddBackgroundJobSettingDefinition(context);
54+
}
55+
56+
private static void AddBackgroundJobSettingDefinition(ISettingDefinitionContext currentContext)
57+
{
58+
var backGroundSchedules = new Dictionary<string, string>
59+
{
60+
{ SettingsConstants.BackgroundJobs.IntakeResync_NumDaysToCheck, "-4" },
61+
// 23 = 11 pm So 23 + 8 UTC = 7 also at 19 = 11 am
62+
{ SettingsConstants.BackgroundJobs.IntakeResync_Expression, "0 0 7,19 1/1 * ? *" },
63+
// 24 = 12 am So 24 + 8 UTC = 8
64+
{ PaymentSettingsConstants.BackgroundJobs.CasPaymentsReconciliation_ProducerExpression, "0 0 8 1/1 * ? *" },
65+
// 24 = 1 am So 24 + 8 UTC = 9
66+
{ PaymentSettingsConstants.BackgroundJobs.CasFinancialNotificationSummary_ProducerExpression, "0 0 9 1/1 * ? *" },
67+
// Run hourly
68+
{ SettingsConstants.BackgroundJobs.DataHealthCheckMonitor_Expression, "0 0 * 1/1 * ? *" }
69+
};
70+
71+
foreach (var setting in backGroundSchedules)
72+
{
73+
AddSettingDefinition(currentContext, setting.Key, setting.Value.ToString());
74+
}
75+
7476
}
7577

7678
private static void AddSettingDefinition(ISettingDefinitionContext currentContext, string settingName, string defaultValue = "True")
@@ -88,8 +90,8 @@ private static void AddSettingDefinition(ISettingDefinitionContext currentContex
8890
isInherited: false,
8991
isEncrypted: false).WithProviders(TenantSettingValueProvider.ProviderName)
9092
);
91-
}
92-
93+
}
94+
9395
private static LocalizableString L(string name)
9496
{
9597
return LocalizableString.Create<GrantManagerResource>(name);

applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/GrantManagerDbContext.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public class GrantManagerDbContext :
3838
public DbSet<RegionalDistrict> RegionalDistricts { get; set; }
3939
public DbSet<TenantToken> TenantTokens { get; set; }
4040
public DbSet<Community> Communities { get; set; }
41-
public DbSet<ChefsMissedSubmission> ChefsMissedSubmissions { get; set; }
4241

4342

4443
#region Entities from the modules
@@ -148,14 +147,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
148147
b.ConfigureByConvention();
149148
});
150149

151-
modelBuilder.Entity<ChefsMissedSubmission>(b =>
152-
{
153-
b.ToTable(GrantManagerConsts.DbTablePrefix + "ChefsMissedSubmissions",
154-
GrantManagerConsts.DbSchema);
155-
156-
b.ConfigureByConvention();
157-
});
158-
159150
var allEntityTypes = modelBuilder.Model.GetEntityTypes();
160151
foreach (var type in allEntityTypes.Where(t => t.ClrType != typeof(ExtraPropertyDictionary)).Select(t => t.ClrType))
161152
{

0 commit comments

Comments
 (0)