using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Services.Communications; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Infrastructure.Database; namespace PostFundManagement.Application.Events; public sealed class LateProjectEvent : IEvent { public Guid Id { get; set; } = Guid.CreateVersion7(); public string Name { get; set; } = nameof(LateProjectEvent); public DateTimeOffset EnqueueAt { get; set; } = DateTimeOffset.UtcNow; public string CorrelationId { get; set; } = "lateproject-reminder"; public static LateProjectEvent Create() => new(); } public sealed class LateProjectEventHandler(IDbContextFactory contextFactory, ICommunicationsService service) : INotificationHandler { readonly int lateDays = 3; public async ValueTask Handle(LateProjectEvent notification, CancellationToken cancellationToken) { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var lateMilestones = await context.Milestones .AsNoTracking() .Include(i => i.Project!.Programme!.Portfolio!.Organisation!.User) .Where(m => (DateTime.UtcNow - m.Project!.StartedAt!).Value.TotalDays < lateDays) .OrderBy(o => o.CreatedAt) .GroupBy(g => g.ProjectId) .AsSplitQuery() .ToListAsync(cancellationToken); if (lateMilestones is null) return; using var htmlTemplateReader = new StreamReader(Assembly.GetAssembly(typeof(IDomain))? .GetManifestResourceStream("PostFundManagement.Domain.Templates.lateprojectsmilestones.html")!); var template = await htmlTemplateReader.ReadToEndAsync(cancellationToken); foreach (var group in lateMilestones) { if (!group.Any()) continue; var project = group.FirstOrDefault()?.Project; if (project is null) continue; var mostLateMilestone = group.OrderBy(m => m.DueAt).FirstOrDefault(); var missedMilestone = group.Where(m => DateTime.UtcNow > m.DueAt).FirstOrDefault(); var messageBody = template; messageBody = messageBody.Replace("[ProjectName]", project.Name); messageBody = messageBody.Replace("[ProjectStartDate]", $"{project.StartedAt:yyyy-MM-dd}"); messageBody = messageBody.Replace("[ProjectEndDate]", $"{project.EndedAt:yyyy-MM-dd}"); messageBody = messageBody.Replace("[TotalLateMiletones]", $"{group.Count()}"); // TODO: output information of most late milestone // TODO: output information of milestones already missed var organisation = mostLateMilestone?.Project?.Programme?.Portfolio?.Organisation; if (organisation is null) continue; var notificationRequest = new CreateNotificationRequest(organisation.Id, organisation.User!.Id, NotificationPlatform.Email, "Project Status Update", organisation.User!.Email, messageBody, NotificationStatus.Queued); _ = await service.CreateNotificationAsync(notificationRequest, SystemUser, cancellationToken); } } }