Skip to content
Closed

Test #1578

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
65424bd
hotfix/AB#29707-AccountCoding
jimmyPasta Aug 1, 2025
869eea3
Merge pull request #1560 from bcgov/hotfix/AB#29707-AccountCoding
JamesPasta Aug 1, 2025
e9eb419
hotfix/AB#29707-AccountCoding
jimmyPasta Aug 1, 2025
1a5bbdb
Merge pull request #1561 from bcgov/hotfix/AB#29707-AccountCoding
JamesPasta Aug 1, 2025
fa6173e
Merge pull request #1562 from bcgov/dev
JamesPasta Aug 1, 2025
caecdee
bugfix/AB#29715 Payment request update note
samsaravillo Aug 5, 2025
640bdff
bugfix/AB#29722-FixApiCAS
jimmyPasta Aug 5, 2025
f9cfeae
Merge pull request #1568 from bcgov/bugfix/AB#29722-FixApiCas
JamesPasta Aug 5, 2025
706e109
Merge pull request #1569 from bcgov/dev
JamesPasta Aug 5, 2025
c3f5ad5
bugfix/AB#29715 Append note to payment if existing note is present
samsaravillo Aug 6, 2025
1b49abd
bugfix/AB#29722-FixApiCAS
jimmyPasta Aug 6, 2025
a0164c0
Merge pull request #1570 from bcgov/bugfix/AB#29715-payments-note-column
JamesPasta Aug 6, 2025
9c0084f
Merge pull request #1571 from bcgov/bugfix/AB#29722-FixApiCas
JamesPasta Aug 6, 2025
743e465
Merge pull request #1572 from bcgov/dev
JamesPasta Aug 6, 2025
6898919
AB#29664 code QL suggestions
AndreGAot Aug 6, 2025
c6bf1b0
Merge pull request #1574 from bcgov/bugfix/AB#29664-electoral-distric…
AndreGAot Aug 6, 2025
f5b0aae
Merge branch 'dev' into hotfix/AB#-Aug6Hotfix-4
JamesPasta Aug 7, 2025
9fe5e76
Merge pull request #1580 from bcgov/hotfix/AB#-Aug6Hotfix-4
JamesPasta Aug 7, 2025
48b707c
Merge pull request #1581 from bcgov/dev
JamesPasta Aug 7, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using System.Linq;
using Volo.Abp.MultiTenancy;

namespace Unity.Payments.Domain.AccountCodings
{
public class AccountCoding : AuditedAggregateRoot<Guid>
public class AccountCoding : AuditedAggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string MinistryClient { get; private set; }
public string Responsibility { get; private set; }
public string ServiceLine { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,27 @@
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(ICasTokenService iTokenService,
IPaymentRequestRepository paymentRequestRepository,
IResilientHttpRequest resilientHttpRequest,
IOptions<CasClientOptions> casClientOptions,
ISupplierRepository iSupplierRepository,
ISiteRepository iSiteRepository,
IUnitOfWorkManager unitOfWorkManager) : 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> casClientOptions,
ISupplierRepository iSupplierRepository,
ISiteRepository iSiteRepository,
IUnitOfWorkManager unitOfWorkManager) : ApplicationService, IInvoiceService
#pragma warning restore S107 // Methods should not have too many parameters
{


private const string CFS_APINVOICE = "cfs/apinvoice";

private readonly Dictionary<int, string> CASPaymentGroup = new()
Expand All @@ -39,7 +45,6 @@ public class InvoiceService(ICasTokenService iTokenService,
[(int)PaymentGroup.Cheque] = "GEN CHQ"
};


protected virtual async Task<Invoice?> InitializeCASInvoice(PaymentRequest paymentRequest,
string? accountDistributionCode)
{
Expand All @@ -49,7 +54,8 @@ public class InvoiceService(ICasTokenService iTokenService,
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");
Expand Down Expand Up @@ -101,12 +107,13 @@ public class InvoiceService(ICasTokenService iTokenService,
throw new UserFriendlyException("CreateInvoiceByPaymentRequestAsync: Payment Request not found");
}

// Based on the payment request application id in the correlation id
// lookup the form id
// on the form is there an acount distribution code?
// If no account distribution code then we can use the default account distribution code from payment configuration default account distribution code id

string? accountDistributionCode = "";// this will be on the payment request
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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,17 @@ public virtual async Task<List<PaymentRequestDto>> UpdateStatusAsync(List<Update
try
{
var payment = await paymentRequestsRepository.GetAsync(dto.PaymentRequestId);
payment.SetNote(dto.Note);
if (payment == null)
continue;

if (!string.IsNullOrWhiteSpace(payment.Note) && !string.IsNullOrWhiteSpace(dto.Note))
{
payment.SetNote($"{payment.Note}; {dto.Note}");
}
else
{
payment.SetNote(dto.Note);
}

var triggerAction = await DetermineTriggerActionAsync(dto, payment);

Expand Down Expand Up @@ -419,8 +429,12 @@ protected internal async Task<List<PaymentRequestDto>> MapToDtoAndLoadDetailsAsy
{
paymentRequestDto.CreatorUser = paymentRequestUserDto;
}
paymentRequestDto.AccountCodingDisplay = await GetAccountDistributionCode(paymentRequestDto.AccountCoding);

if(paymentRequestDto != null && paymentRequestDto.AccountCoding != null)
{
paymentRequestDto.AccountCodingDisplay = await GetAccountDistributionCode(paymentRequestDto.AccountCoding);
}

foreach (var expenseApproval in paymentRequestDto.ExpenseApprovals)
{
if (expenseApproval.DecisionUserId.HasValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public PaymentsApplicationAutoMapperProfile()
CreateMap<Supplier, SupplierDto>();

CreateMap<CreateUpdateAccountCodingDto, AccountCoding>()
.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())
Expand All @@ -50,6 +51,7 @@ public PaymentsApplicationAutoMapperProfile()
CreateMap<AccountCoding, AccountCodingDto>();
CreateMap<AccountCodingDto, CreateUpdateAccountCodingDto>();
CreateMap<CreateUpdateAccountCodingDto, AccountCoding>()
.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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -64,7 +65,14 @@ public IEnumerable<ValidationResult> 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)]
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<input type="hidden" asp-for="@Model.PaymentGroupings[k].Items[i].ToStatus" />
<input type="hidden" asp-for="@Model.PaymentGroupings[k].Items[i].PreviousL1Approver" />
<input type="hidden" asp-for="@Model.PaymentGroupings[k].Items[i].IsValid" />
<input type="hidden" asp-for="@Model.PaymentGroupings[k].Items[i].HasUserPaymentThreshold" />
<abp-row id=@($"{@Model.PaymentGroupings[k].Items[i].Id}_header") class=" m-0 p-2">
<abp-column size="_12" class="px-1 d-flex align-items-center">
<div class="w-100">
Expand Down Expand Up @@ -161,10 +162,17 @@
</abp-row>
}
<abp-column size="_12" class=" m-0 p-3 payment-error-column text-danger" abp-if="@(!ModelState.IsValid)">
@if(ModelState.ContainsKey(nameof(PaymentsApprovalModel.PreviousL1Approver))
@if (ModelState.ContainsKey(nameof(PaymentsApprovalModel.PreviousL1Approver))
&& ModelState[nameof(PaymentsApprovalModel.PreviousL1Approver)]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<span>@L["ApplicationPaymentRequest:Validations:L2ApproverRestrictionBatch"]</span>
var previousL1ApproverState = ModelState[nameof(PaymentsApprovalModel.PreviousL1Approver)];
if (previousL1ApproverState != null)
{
foreach (var error in previousL1ApproverState.Errors)
{
<span>@error.ErrorMessage</span>
}
}
}
</abp-column>
<abp-row class="m-0 p-2 no-payment-msg text-center" id="no-payment-msg" style="display: none;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ private async Task<PaymentsApprovalModel> CreateApprovalModel(PaymentDetailsDto
IsL3ApprovalRequired = isL3ApprovalRequired,
ToStatus = payment.Status,
IsApproval = IsApproval,
HasUserPaymentThreshold = UserPaymentThreshold.HasValue,
PreviousL1Approver = payment.ExpenseApprovals.FirstOrDefault(x => x.Type == ExpenseApprovalType.Level1)?.DecisionUserId
};
}
Expand Down Expand Up @@ -221,7 +222,8 @@ public async Task<IActionResult> OnPostAsync()
.Select(payment => new UpdatePaymentStatusRequestDto
{
PaymentRequestId = payment.Id,
IsApprove = IsApproval
IsApprove = IsApproval,
Note = Note ?? String.Empty
})
.ToList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ $(function () {
text: 'Approve',
className: 'custom-table-btn flex-none btn btn-secondary payment-status',
action: function (e, dt, node, config) {
// Check if user payment threshold is defined and greater than 0
if (parseFloat($("#UserPaymentThreshold").val() || 0) <= 0) {
abp.notify.error(
'Your User has not been configured with an Approved Payment Threshold. Please contact your system administrator.',
'Payment Requests'
);
return;
}
paymentRequestStatusModal.open({
paymentIds: JSON.stringify(selectedPaymentIds),
isApprove: true
Expand Down Expand Up @@ -645,8 +637,14 @@ $(function () {
name: 'accountCodingDisplay',
data: 'accountCodingDisplay',
className: 'data-table-header',
index: columnIndex

index: columnIndex,
render: function (data) {
if (data + "" !== "undefined" && data?.length > 0) {
return data;
} else {
return "";
}
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ public class UpdateApplicantSummaryDto
public bool? IndigenousOrgInd { get; set; }
public string? UnityApplicantId { get; set; }
public string? FiscalDay { get; set; }
public string? FiscalMonth { get; set; }
public string? ElectoralDistrict { get; set; }
public string? FiscalMonth { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public async Task<GrantApplicationDto> UpdatePartialApplicantInfoAsync(Guid appl
}

// Only update the fields we need to update based on the modified
// Automapper mapping ignores the ElectoralDistrict as this is different from the application level electoral district
ObjectMapper.Map<UpdateApplicantInfoDto, Applications.Application>(input.Data, application);

//-- APPLICANT INFO - SUMMARY
Expand Down Expand Up @@ -190,6 +191,14 @@ public async Task<GrantApplicationDto> UpdatePartialApplicantInfoAsync(Guid appl
await CreateOrUpdateApplicantAddress(applicationId, application.ApplicantId, input.Data.MailingAddress);
}

//-- APPLICANT ELECTORAL DISTRICT
if (input.Data.ElectoralDistrict != null
&& await AuthorizationService.IsGrantedAsync(UnitySelector.Applicant.Location.Update))
{
// Update the electoral district at the applicant level
application.Applicant.ElectoralDistrict = input.Data.ElectoralDistrict;
}

//-- APPLICANT INFO CUSTOM FIELDS
if (input.Data.CustomFields?.ValueKind != JsonValueKind.Null && input.Data.WorksheetId != Guid.Empty && input.Data.CorrelationId != Guid.Empty)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public GrantManagerApplicationAutoMapperProfile()
CreateMap<UpdateApplicantInfoDto, Applicant>()
.IgnoreNullAndDefaultValues();
CreateMap<UpdateApplicantInfoDto, Application>()
.IgnoreNullAndDefaultValues();
.IgnoreNullAndDefaultValues()
.ForMember(dest => dest.ElectoralDistrict, opt => opt.Ignore()); // Electoral district is handled separately
CreateMap<SigningAuthorityDto, Application>()
.IgnoreNullAndDefaultValues();
CreateMap<UpdateApplicantSummaryDto, Applicant>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class DetermineElectoralDistrictHandler(IGeocoderApiService geocoderApiSe
: ILocalEventHandler<ApplicationProcessEvent>, ITransientDependency
{
/// <summary>
/// Determines the Electoral Distrct based on the Address.
/// Determines the Electoral District based on the Address.
/// </summary>
/// <param name="eventData"></param>
/// <returns></returns>
Expand All @@ -30,31 +30,39 @@ public async Task HandleEventAsync(ApplicationProcessEvent eventData)

if (eventData.FormVersion == null)
{
logger.LogWarning("Application data is null in DetermineElectoralDistrictHandler.");
logger.LogWarning("Form version data is null in DetermineElectoralDistrictHandler.");
return;
}

// Check if the electoral district is already mapped for the form submission, if so then no work to be done
if (eventData.FormVersion.HasSubmissionHeaderMapping("ApplicantElectoralDistrict"))
{
logger.LogInformation("Electoral district already determined for application {ApplicationId}. No further action required.",
eventData.Application.Id);
return;
}

// Use local variable to avoid modifying the entity property
var addressType = eventData.Application.ApplicationForm.ElectoralDistrictAddressType ?? GrantApplications.AddressType.PhysicalAddress;
logger.LogInformation("Using electoral district address type: {AddressType} for electoral determination", addressType);

var electoralDistrictAddressType = eventData.Application.ApplicationForm.ElectoralDistrictAddressType;
var applicantAddresses = eventData.Application.Applicant.ApplicantAddresses;

if (applicantAddresses == null || applicantAddresses.Count == 0)
{
logger.LogWarning("Application data is null in DetermineElectoralDistrictHandler.");
logger.LogWarning("Applicant addresses are null or empty in DetermineElectoralDistrictHandler for application {ApplicationId}.",
eventData.Application.Id);
return;
}

// Find the related address type
var matchedAddressType = applicantAddresses
.FirstOrDefault(a => a.AddressType == electoralDistrictAddressType);
.FirstOrDefault(a => a.AddressType == addressType);

if (matchedAddressType == null)
{
logger.LogWarning("No address of type {AddressType} found for application {ApplicationId}.",
electoralDistrictAddressType, eventData.Application.Id);
addressType, eventData.Application.Id);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@

namespace Unity.GrantManager.Payments
{
public class AccountCodingAppService :
CrudAppService<
public class AccountCodingAppService(
IRepository<AccountCoding, Guid> repository
) : CrudAppService<
AccountCoding,
AccountCodingDto,
Guid,
PagedAndSortedResultRequestDto,
CreateUpdateAccountCodingDto>, IAccountCodingAppService
CreateUpdateAccountCodingDto>(repository), IAccountCodingAppService
{
public AccountCodingAppService(IRepository<AccountCoding, Guid> repository)
: base(repository)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public async Task<IViewComponentResult> InvokeAsync(Guid applicationId, Guid app
ContactInfo = ObjectMapper.Map<ContactInfoDto, ContactInfoViewModel>(applicantInfoDto.ContactInfo ?? new ContactInfoDto()),
SigningAuthority = ObjectMapper.Map<SigningAuthorityDto, SigningAuthorityViewModel>(applicantInfoDto.SigningAuthority ?? new SigningAuthorityDto()),
ApplicantElectoralAddressType = electoralDistrictAddressType,
// This is fixed at the applicant level, we need solution this wrt the recent address logic added below
ElectoralDistrict = applicantInfoDto.ElectoralDistrict
};

viewModel.ApplicantSummary.ApplicantId = applicantInfoDto.ApplicantId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
try {
assessmentResultObj['correlationId'] = formVersionId;
assessmentResultObj['worksheetId'] = worksheetId;
if(assessmentResultObj['ApprovedAmount'] == '') {
assessmentResultObj['ApprovedAmount'] = null;
}
unity.grantManager.grantApplications.grantApplication
.updateAssessmentResults(applicationId, assessmentResultObj)
.done(function () {
Expand Down