Skip to content
Merged

Dev #2030

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
3b2f133
Merge pull request #1959 from bcgov/test
DarylTodosichuk Jan 28, 2026
6ae65ee
AB#31824: Dont delete supplier
aurelio-aot Feb 6, 2026
22b64a5
Merge pull request #1976 from bcgov/hotfix/AB#31824-associate-supplie…
JamesPasta Feb 6, 2026
65c9599
AB#31824: Deassociate Applicant from Supplier
aurelio-aot Feb 9, 2026
12c7496
Merge branch 'main' into hotfix/AB#31824-associate-supplier-dont-delete
aurelio-aot Feb 9, 2026
5c25861
Potential fix for pull request finding 'Constant condition'
aurelio-aot Feb 9, 2026
ceab0ae
Merge pull request #1981 from bcgov/hotfix/AB#31824-associate-supplie…
JamesPasta Feb 9, 2026
16607a9
AB#31867: Validate empty SupplierNumber in PaymentRequest frontend
aurelio-aot Feb 11, 2026
f41b5a1
AB#31867: Dont save empty suppliernumber from CAS
aurelio-aot Feb 11, 2026
dbcb1e8
AB#31867: Fix sonarqube issue
aurelio-aot Feb 11, 2026
fa897bf
AB#31867: Improve validation message wording
aurelio-aot Feb 11, 2026
8f3bd32
Merge pull request #1986 from bcgov/hotfix/AB#31867-validate-empty-su…
JamesPasta Feb 11, 2026
5b6d68f
Merge pull request #2013 from bcgov/test
aurelio-aot Feb 19, 2026
2773334
AB#25283 submission data provider for profiles
AndreGAot Feb 20, 2026
f316ee3
AB#25283 fix unit test
AndreGAot Feb 20, 2026
f43730d
AB#25283 missing xml docs
AndreGAot Feb 20, 2026
10f8f60
Update applications/Unity.GrantManager/src/Unity.GrantManager.Applica…
AndreGAot Feb 20, 2026
ed4b24f
Merge pull request #2019 from bcgov/feature/AB#25283-profile-submissions
AndreGAot Feb 20, 2026
0cc221b
AB#32001 Improve AI payload logging controls and output formatting
jacobwillsmith Feb 21, 2026
77ca0f3
AB#32001 Update gitignore for local dev artifacts
jacobwillsmith Feb 21, 2026
de09143
AB#31384 - Add aggregated payment summary DTO and batch queries
plavoie-BC Feb 23, 2026
15ae161
AB#31384 - Add aggregated payment summary tests and test modifications
plavoie-BC Feb 23, 2026
13f58bd
AB#31384 - Fix file encoding issue raising SQ warning
plavoie-BC Feb 23, 2026
535dab3
AB#31384 - Fix circular dependency on IApplicationLinksService
plavoie-BC Feb 23, 2026
9a9d32a
AB#31384 - Payment Request query code quality improvements
plavoie-BC Feb 23, 2026
19bb97f
AB#31384 - Add Payment Request Repository Tests
plavoie-BC Feb 23, 2026
97c8d62
AB#31384 - Add integration tests for PaymentRequestRepository and cle…
plavoie-BC Feb 23, 2026
d729e49
AB#31384 - Rename calculation objects from Payment Summary to Payment…
plavoie-BC Feb 23, 2026
9074cf2
AB#32001 Update gitignore for only code-workspace
jacobwillsmith Feb 24, 2026
d746aad
AB#32001 Cache JsonSerializerOptions for AI payload log formatting
jacobwillsmith Feb 24, 2026
c92ea21
AB#32001 Address Copilot feedback for AI payload logging safety and J…
jacobwillsmith Feb 24, 2026
e6662e8
AB#32001 Refine AI payload logging gates and local file logging behavior
jacobwillsmith Feb 24, 2026
60b681b
AB#32001 AI logging single local file flag with comment
jacobwillsmith Feb 24, 2026
6f0ca6d
AB#001 Fix nullability warnings
jacobwillsmith Feb 24, 2026
31b4a29
Quick Date Range now displayed with blue border to match the original…
DavidBrightBcGov Feb 24, 2026
3f7d43b
Merge pull request #2022 from bcgov/feature/AB#32001-ImproveAIPayload…
jacobwillsmith Feb 24, 2026
ea29ca1
AB#30430 applicant profile address info provider
AndreGAot Feb 24, 2026
2d484ed
When using clear filter, the search field will be reset to empty, and…
DavidBrightBcGov Feb 24, 2026
f5e4f75
AB#32046 Rename OpenAI config keys to Azure prefix
jacobwillsmith Feb 24, 2026
2226ce4
AB#30430 sonarQube fixes
AndreGAot Feb 24, 2026
a1e2115
Merge branch 'dev' into feature/AB#30430-applicant-profile-addresses
AndreGAot Feb 25, 2026
676fd06
Applied logic to use a default <option selected> rather than hardcodi…
DavidBrightBcGov Feb 25, 2026
06d3bdd
Merge pull request #2025 from bcgov/bugfix/AB#32028-quick-date-range-…
DavidBrightBcGov Feb 25, 2026
d5b1d89
Merge pull request #2026 from bcgov/feature/AB#32046-RenameOpenAIConf…
jacobwillsmith Feb 25, 2026
a557db9
Merge pull request #2027 from bcgov/feature/AB#30430-applicant-profil…
AndreGAot Feb 25, 2026
83ef797
Merge pull request #2028 from bcgov/bugfix/AB#32029-clear-filter-fix
DavidBrightBcGov Feb 25, 2026
8728c3f
Merge branch 'dev' into bugfix/AB#31384-aggregate-payment
plavoie-BC Feb 25, 2026
c4718df
AB#31384 - Add Application Links Comments
plavoie-BC Feb 25, 2026
9f32555
AB#31384 - Fix renaming for batch rollup
plavoie-BC Feb 25, 2026
d9e1edb
hotfix/AB#31672-ClientCodes
JamesPasta Feb 25, 2026
cf42a8a
Merge pull request #2020 from bcgov/bugfix/AB#31384-aggregate-payment
JamesPasta Feb 25, 2026
f64df0e
Merge pull request #2029 from bcgov/hotfix/AB#31672-ClientCodes
JamesPasta Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.rsuser
*.userosscache
*.sln.docstates
*.code-workspace

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Unity.Payments.PaymentRequests;

[Serializable]
public class ApplicationPaymentRollupDto
{
public Guid ApplicationId { get; set; }
public decimal TotalPaid { get; set; }
public decimal TotalPending { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ public interface IPaymentRequestAppService : IApplicationService
Task<decimal?> GetUserPaymentThresholdAsync();
Task ManuallyAddPaymentRequestsToReconciliationQueue(List<Guid> paymentRequestIds);
Task<List<PaymentRequestDto>> GetPaymentPendingListByCorrelationIdAsync(Guid applicationId);
Task<ApplicationPaymentRollupDto> GetApplicationPaymentRollupAsync(Guid applicationId);
Task<Dictionary<Guid, ApplicationPaymentRollupDto>> GetApplicationPaymentRollupBatchAsync(List<Guid> applicationIds);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Payments.PaymentRequests;
using Volo.Abp.Domain.Repositories;

namespace Unity.Payments.Domain.PaymentRequests
Expand All @@ -14,6 +15,6 @@ public interface IPaymentRequestRepository : IRepository<PaymentRequest, Guid>
Task<PaymentRequest?> GetPaymentRequestByInvoiceNumber(string invoiceNumber);
Task<List<PaymentRequest>> GetPaymentRequestsByFailedsStatusAsync();
Task<List<PaymentRequest>> GetPaymentPendingListByCorrelationIdAsync(Guid correlationId);

Task<List<ApplicationPaymentRollupDto>> GetBatchPaymentRollupsByCorrelationIdsAsync(List<Guid> correlationIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@ public interface IPaymentRequestQueryManager

// Pending Payments
Task<List<PaymentRequestDto>> GetPaymentPendingListByCorrelationIdAsync(Guid applicationId);

// Payment Rollups (paid + pending aggregation)
Task<ApplicationPaymentRollupDto> GetApplicationPaymentRollupAsync(Guid applicationId, List<Guid> childApplicationIds);
Task<Dictionary<Guid, ApplicationPaymentRollupDto>> GetApplicationPaymentRollupBatchAsync(List<Guid> applicationIds, Dictionary<Guid, List<Guid>> childApplicationIdsByParent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,21 @@ public async Task<PaymentRequestDto> CreatePaymentRequestDtoAsync(Guid paymentRe
public async Task<List<PaymentDetailsDto>> GetListByApplicationIdsAsync(List<Guid> applicationIds)
{
var paymentsQueryable = await paymentRequestRepository.GetQueryableAsync();
var payments = await paymentsQueryable.Include(pr => pr.Site).ToListAsync();
var filteredPayments = payments.Where(pr => applicationIds.Contains(pr.CorrelationId)).ToList();
var filteredPayments = await paymentsQueryable
.Include(pr => pr.Site)
.Where(pr => applicationIds.Contains(pr.CorrelationId))
.ToListAsync();

return objectMapper.Map<List<PaymentRequest>, List<PaymentDetailsDto>>(filteredPayments);
}

public async Task<List<PaymentDetailsDto>> GetListByApplicationIdAsync(Guid applicationId)
{
var paymentsQueryable = await paymentRequestRepository.GetQueryableAsync();
var payments = await paymentsQueryable.Include(pr => pr.Site).ToListAsync();
var filteredPayments = payments.Where(e => e.CorrelationId == applicationId).ToList();
var filteredPayments = await paymentsQueryable
.Include(pr => pr.Site)
.Where(e => e.CorrelationId == applicationId)
.ToListAsync();

return objectMapper.Map<List<PaymentRequest>, List<PaymentDetailsDto>>(filteredPayments);
}
Expand Down Expand Up @@ -218,5 +222,98 @@ public async Task<List<PaymentRequestDto>> GetPaymentPendingListByCorrelationIdA
var payments = await paymentRequestRepository.GetPaymentPendingListByCorrelationIdAsync(applicationId);
return objectMapper.Map<List<PaymentRequest>, List<PaymentRequestDto>>(payments);
}

/// <summary>
/// Retrieves a payment rollup for the specified application and its associated child applications.
/// </summary>
/// <remarks>This method combines payment information from both the main application and its child
/// applications, providing an overall view of payment status. Use this method to obtain a single rollup when
/// displaying applications with related child records.</remarks>
/// <param name="applicationId">The unique identifier of the main application for which the payment rollup is requested.</param>
/// <param name="childApplicationIds">A list of unique identifiers representing child applications whose payment data will be included in the
/// rollup. Cannot be null.</param>
/// <returns>An instance of <see cref="ApplicationPaymentRollupDto"/> containing the aggregated total paid and pending
/// amounts for the main application and its child applications.</returns>
public async Task<ApplicationPaymentRollupDto> GetApplicationPaymentRollupAsync(Guid applicationId, List<Guid> childApplicationIds)
{
var allCorrelationIds = new List<Guid> { applicationId };
allCorrelationIds.AddRange(childApplicationIds);

var batchRollup = await paymentRequestRepository.GetBatchPaymentRollupsByCorrelationIdsAsync(allCorrelationIds);

return new ApplicationPaymentRollupDto
{
ApplicationId = applicationId,
TotalPaid = batchRollup.Sum(s => s.TotalPaid),
TotalPending = batchRollup.Sum(s => s.TotalPending)
};
}

/// <summary>
/// Retrieves batch payment rollup information for the specified application IDs, aggregating totals
/// for both paid and pending amounts from parent and child applications.
/// </summary>
/// <remarks>This method performs a single database query to efficiently aggregate payment data
/// for all specified parent and child applications. The results include all relevant payment information
/// for each application ID provided.</remarks>
/// <param name="applicationIds">A list of unique identifiers for the applications whose payment rollups are to be retrieved. Cannot be
/// null or empty.</param>
/// <param name="childApplicationIdsByParent">A dictionary that maps each parent application ID to a list of its child application IDs. Must not be null
/// and should contain valid GUIDs.</param>
/// <returns>A dictionary where each key is an application ID and the value is an <see cref="ApplicationPaymentRollupDto"/> containing
/// the total paid and pending amounts for that application, including amounts from any child applications.</returns>
public async Task<Dictionary<Guid, ApplicationPaymentRollupDto>> GetApplicationPaymentRollupBatchAsync(
List<Guid> applicationIds,
Dictionary<Guid, List<Guid>> childApplicationIdsByParent)
{
// Collect all unique correlation IDs (parents + all children) for a single DB query
var allCorrelationIds = new HashSet<Guid>(applicationIds);
foreach (var childIds in childApplicationIdsByParent.Values)
{
foreach (var childId in childIds)
{
allCorrelationIds.Add(childId);
}
}

var paymentRollups = await paymentRequestRepository.GetBatchPaymentRollupsByCorrelationIdsAsync(allCorrelationIds.ToList());
var rollupLookup = paymentRollups.ToDictionary(s => s.ApplicationId);

var result = new Dictionary<Guid, ApplicationPaymentRollupDto>();
foreach (var applicationId in applicationIds)
{
decimal totalPaid = 0;
decimal totalPending = 0;

// Add the parent application's own amounts
if (rollupLookup.TryGetValue(applicationId, out var parentRollup))
{
totalPaid += parentRollup.TotalPaid;
totalPending += parentRollup.TotalPending;
}

// Add child application amounts
if (childApplicationIdsByParent.TryGetValue(applicationId, out var childIds))
{
foreach (var childId in childIds)
{
if (rollupLookup.TryGetValue(childId, out var childApplicationRollup))
{
totalPaid += childApplicationRollup.TotalPaid;
totalPending += childApplicationRollup.TotalPending;
}
}
}

result[applicationId] = new ApplicationPaymentRollupDto
{
ApplicationId = applicationId,
TotalPaid = totalPaid,
TotalPending = totalPending
};
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.Payments.Codes;
using Unity.Payments.Domain.PaymentRequests;
using Unity.Payments.EntityFrameworkCore;
using Unity.Payments.Enums;
using Unity.Payments.PaymentRequests;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;

Expand All @@ -16,9 +18,6 @@ public class PaymentRequestRepository : EfCoreRepository<PaymentsDbContext, Paym
private List<string> ReCheckStatusList { get; set; } = new List<string>();
private List<string> FailedStatusList { get; set; } = new List<string>();




public PaymentRequestRepository(IDbContextProvider<PaymentsDbContext> dbContextProvider) : base(dbContextProvider)
{
ReCheckStatusList.Add(CasPaymentRequestStatus.ServiceUnavailable);
Expand All @@ -32,25 +31,27 @@ public PaymentRequestRepository(IDbContextProvider<PaymentsDbContext> dbContextP
public async Task<int> GetCountByCorrelationId(Guid correlationId)
{
var dbSet = await GetDbSetAsync();
return dbSet.Count(s => s.CorrelationId == correlationId);
return await dbSet.CountAsync(s => s.CorrelationId == correlationId);
}

public async Task<int> GetPaymentRequestCountBySiteId(Guid siteId)
{
var dbSet = await GetDbSetAsync();
return dbSet.Where(s => s.SiteId == siteId).Count();
}
return await dbSet.Where(s => s.SiteId == siteId)
.CountAsync();
}

public async Task<PaymentRequest?> GetPaymentRequestByInvoiceNumber(string invoiceNumber)
{
var dbSet = await GetDbSetAsync();
return dbSet.Where(s => s.InvoiceNumber == invoiceNumber).FirstOrDefault();
return await dbSet.Where(s => s.InvoiceNumber == invoiceNumber)
.FirstOrDefaultAsync();
}

public async Task<decimal> GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid correlationId)
{
var dbSet = await GetDbSetAsync();
decimal applicationPaymentRequestsTotal = dbSet
decimal applicationPaymentRequestsTotal = await dbSet
.Where(p => p.CorrelationId.Equals(correlationId))
.Where(p => p.Status != PaymentRequestStatus.L1Declined
&& p.Status != PaymentRequestStatus.L2Declined
Expand All @@ -59,23 +60,25 @@ public async Task<decimal> GetTotalPaymentRequestAmountByCorrelationIdAsync(Guid
&& p.InvoiceStatus != CasPaymentRequestStatus.ErrorFromCas)
.GroupBy(p => p.CorrelationId)
.Select(p => p.Sum(q => q.Amount))
.FirstOrDefault();
.FirstOrDefaultAsync();

return applicationPaymentRequestsTotal;
}

public async Task<List<PaymentRequest>> GetPaymentRequestsBySentToCasStatusAsync()
{
var dbSet = await GetDbSetAsync();
return dbSet.Where(p => p.InvoiceStatus != null && ReCheckStatusList.Contains(p.InvoiceStatus)).IncludeDetails().ToList();
return await dbSet.Where(p => p.InvoiceStatus != null && ReCheckStatusList.Contains(p.InvoiceStatus))
.IncludeDetails()
.ToListAsync();
}

public async Task<List<PaymentRequest>> GetPaymentRequestsByFailedsStatusAsync()
{
var dbSet = await GetDbSetAsync();
return dbSet.Where(p => p.InvoiceStatus != null
&& FailedStatusList.Contains(p.InvoiceStatus)
&& p.LastModificationTime >= DateTime.Now.AddDays(-2)).IncludeDetails().ToList();
return await dbSet.Where(p => p.InvoiceStatus != null
&& FailedStatusList.Contains(p.InvoiceStatus)
&& p.LastModificationTime >= DateTime.Now.AddDays(-2)).IncludeDetails().ToListAsync();
}

public override async Task<IQueryable<PaymentRequest>> WithDetailsAsync()
Expand All @@ -87,8 +90,52 @@ public override async Task<IQueryable<PaymentRequest>> WithDetailsAsync()
public async Task<List<PaymentRequest>> GetPaymentPendingListByCorrelationIdAsync(Guid correlationId)
{
var dbSet = await GetDbSetAsync();
return dbSet.Where(p => p.CorrelationId.Equals(correlationId))
.Where(p => p.Status == PaymentRequestStatus.L1Pending || p.Status == PaymentRequestStatus.L2Pending).IncludeDetails().ToList();
return await dbSet.Where(p => p.CorrelationId.Equals(correlationId))
.Where(p => p.Status == PaymentRequestStatus.L1Pending || p.Status == PaymentRequestStatus.L2Pending)
.IncludeDetails()
.ToListAsync();
}

/// <summary>
/// Asynchronously retrieves payment rollup information for each specified correlation ID.
/// </summary>
/// <remarks>This method queries the database for payment records associated with the provided
/// correlation IDs and aggregates payment amounts based on their status. Ensure that the correlation IDs are
/// valid to avoid empty results.</remarks>
/// <param name="correlationIds">A list of correlation IDs used to filter payment records. Each ID must be a valid GUID.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of
/// ApplicationPaymentRollupDto objects, each summarizing the total paid and total pending amounts for the
/// corresponding correlation ID.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance",
"CA1862:Use the 'StringComparison' method overloads to perform case-insensitive string comparisons",
Justification = "EF Core does not support StringComparison - https://github.com/dotnet/efcore/issues/1222")]
public async Task<List<ApplicationPaymentRollupDto>> GetBatchPaymentRollupsByCorrelationIdsAsync(List<Guid> correlationIds)
{
var dbSet = await GetDbSetAsync();

var results = await dbSet
.Where(p => correlationIds.Contains(p.CorrelationId))
.GroupBy(p => p.CorrelationId)
.Select(g => new ApplicationPaymentRollupDto
{
ApplicationId = g.Key,
TotalPaid = g
.Where(p => p.PaymentStatus != null
&& p.PaymentStatus.Trim().ToUpper() == CasPaymentRequestStatus.FullyPaid.ToUpper())
.Sum(p => p.Amount),
TotalPending = g
.Where(p => p.Status == PaymentRequestStatus.L1Pending
|| p.Status == PaymentRequestStatus.L2Pending
|| p.Status == PaymentRequestStatus.L3Pending
|| (p.Status == PaymentRequestStatus.Submitted
&& string.IsNullOrEmpty(p.PaymentStatus)
&& (string.IsNullOrEmpty(p.InvoiceStatus)
|| !p.InvoiceStatus.Contains(CasPaymentRequestStatus.ErrorFromCas))))
.Sum(p => p.Amount)
})
.ToListAsync();

return results;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ public async Task<CasPaymentSearchResult> GetCasInvoiceAsync(string invoiceNumbe
}
}

public async Task<CasPaymentSearchResult> GetCasPaymentAsync(string invoiceNumber, string supplierNumber, string siteNumber)
public async Task<CasPaymentSearchResult> GetCasPaymentAsync(Guid tenantId, string invoiceNumber, string supplierNumber, string siteNumber)
{
var authToken = await iTokenService.GetAuthTokenAsync(CurrentTenant.Id ?? Guid.Empty);
var authToken = await iTokenService.GetAuthTokenAsync(tenantId);
var casBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.PAYMENT_API_BASE);
var resource = $"{casBaseUrl}/{CFS_APINVOICE}/{invoiceNumber}/{supplierNumber}/{siteNumber}";
var response = await resilientHttpRequest.HttpAsync(HttpMethod.Get, resource, body: null, authToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public async Task ConsumeAsync(ReconcilePaymentMessages reconcilePaymentMessage)
{
if (reconcilePaymentMessage != null && !reconcilePaymentMessage.InvoiceNumber.IsNullOrEmpty() && reconcilePaymentMessage.TenantId != Guid.Empty)
{

// string invoiceNumber, string supplierNumber, string siteNumber)
// Go to CAS retrieve the status of the payment
CasPaymentSearchResult result = await invoiceService.GetCasPaymentAsync(
reconcilePaymentMessage.TenantId,
reconcilePaymentMessage.InvoiceNumber,
reconcilePaymentMessage.SupplierNumber,
reconcilePaymentMessage.SiteNumber);
Expand Down
Loading
Loading