Skip to content
Merged

Dev #1487

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
07e755a
29103:Restructuring the Tagging data model in the Application & Payme…
don-aot Jul 7, 2025
3faa0d6
29103:Restructuring the Tagging data model in the Payment List
don-aot Jul 8, 2025
ce7c719
Merge branch 'dev' into feature/AB#29103-Restructuring-the-Tagging-da…
don-aot Jul 8, 2025
970d345
feature/AB#29310-AddPaymentGroupToSite
jimmyPasta Jul 8, 2025
071f636
29103:Restructuring the Tagging data model in the Payment List
don-aot Jul 9, 2025
1804606
29103:Restructuring the Tagging data model in the Payment List
don-aot Jul 9, 2025
a2fa7d3
Sonarqube Issue fixes
don-aot Jul 9, 2025
1373664
feature/AB#29310-AddPaymentGroupToSite
jimmyPasta Jul 9, 2025
e1db7d7
Sonarqube fixes
don-aot Jul 9, 2025
af7af0b
feature/AB#29310-AddPaymentGroupToSite
jimmyPasta Jul 9, 2025
6c28043
Merge pull request #1476 from bcgov/feature/AB#29103-Restructuring-th…
JamesPasta Jul 9, 2025
b4bc88b
AB#29096 - Add SettingManagement.Tags.Create Permissions
plavoie-BC Jul 9, 2025
53a18a3
Merge remote-tracking branch 'origin/dev' into feature/AB#29096-tag-m…
plavoie-BC Jul 9, 2025
a6f4061
feature/AB#29310-AddPaymentGroupToSite-SonarFixes
jimmyPasta Jul 9, 2025
cad671a
Merge pull request #1483 from bcgov/feature/AB#29310-AddPaygroupToSite
JamesPasta Jul 9, 2025
25fd6c9
AB#29451 add fix endpoint to recover missing worksheet instances
AndreGAot Jul 9, 2025
5455af5
feature/AB#29464 Add ApplicantName to Applicant Info Tab
samsaravillo Jul 9, 2025
cce7983
AB#29451 some updates based on codeQL suggestions
AndreGAot Jul 9, 2025
c67a615
feature/AB#29103-AddInDrop-DropFK for app tage
jimmyPasta Jul 9, 2025
21f520b
Merge pull request #1486 from bcgov/bugfix/AB#29103-AddInDropForForei…
JamesPasta Jul 9, 2025
d3168a1
AB#29451 more codeQL suggestions
AndreGAot Jul 9, 2025
849bdc4
AB#29103 - Global Tags - Add Tag Normalization Migration Scripts
plavoie-BC Jul 9, 2025
76df73c
AB#29103 - Global Tags - Add Tag Normalization Migration Scripts - Sc…
plavoie-BC Jul 10, 2025
74576d8
Merge pull request #1488 from bcgov/bugfix/AB#29103-tag-model-normali…
don-aot Jul 10, 2025
e97c28c
AB#29451 secure behind admin with tenant specify capability
AndreGAot Jul 10, 2025
8310bb5
Merge remote-tracking branch 'origin/dev' into feature/AB#29096-tag-m…
plavoie-BC Jul 10, 2025
0d96a24
AB#29096 - Tag Management - Configure 'Add New Tag' Permission on Int…
plavoie-BC Jul 10, 2025
7357936
AB#29096 - Tag Management - Remove Legacy Tag Management Logic
plavoie-BC Jul 10, 2025
33f4f02
AB#29096 - Tag Management - Resolve SonarQube issue and fix RenameTag…
plavoie-BC Jul 10, 2025
0a0cc48
AB#29096 - Tag Management - Bugfix for RenameTagAsync
plavoie-BC Jul 10, 2025
16fd0f5
Merge pull request #1484 from bcgov/feature/AB#29464-Add-Applicant-Na…
JamesPasta Jul 10, 2025
03168be
Merge pull request #1489 from bcgov/feature/AB#29096-tag-management-p…
JamesPasta Jul 10, 2025
6088f47
Merge pull request #1485 from bcgov/bugfix/AB#29451-missing-worksheet…
JamesPasta Jul 10, 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 @@ -179,9 +179,13 @@ private async Task<WorksheetInstance> CreateNewWorksheetInstanceAsync(IWorksheet

if (worksheet != null)
{
var worksheetLink = await worksheetLinkRepository.GetExistingLinkAsync(worksheet.Id, eventData.SheetCorrelationId, eventData.SheetCorrelationProvider);
var worksheetLink = await worksheetLinkRepository.GetExistingLinkAsync(worksheet.Id,
eventData.SheetCorrelationId,
eventData.SheetCorrelationProvider);

if (worksheetLink != null)
// Make sure we do not create an instance if it already exists
if (worksheetLink != null
&& !(await WorksheetInstanceAlreadyExists(worksheet.Id, eventData, worksheetLink.UiAnchor)))
{
var newInstance = new WorksheetInstance(Guid.NewGuid(),
worksheet.Id,
Expand Down Expand Up @@ -210,6 +214,18 @@ private async Task<WorksheetInstance> CreateNewWorksheetInstanceAsync(IWorksheet
return newWorksheetInstances;
}

private async Task<bool> WorksheetInstanceAlreadyExists(Guid worksheetId,
CreateWorksheetInstanceByFieldValuesEto eventData,
string uiAnchor)
{
return await worksheetInstanceRepository.ExistsAsync(worksheetId,
eventData.InstanceCorrelationId,
eventData.InstanceCorrelationProvider,
eventData.SheetCorrelationId,
eventData.SheetCorrelationProvider,
uiAnchor);
}

public async Task<Worksheet> CloneWorksheetAsync(Guid id)
{
var worksheet = await worksheetRepository.GetAsync(id, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public interface IWorksheetInstanceRepository : IBasicRepository<WorksheetInstan
Task<WorksheetInstance?> GetByCorrelationAnchorAsync(Guid correlationId, string correlationProvider, string uiAnchor, bool includeDetails);
Task<List<WorksheetInstance>> GetByWorksheetCorrelationAsync(Guid worksheetId, string uiAnchor, Guid worksheetCorrelationId, string worksheetCorrelationProvider);
Task<WorksheetInstance?> GetWithValuesAsync(Guid worksheetInstanceId);
Task<bool> ExistsAsync(Guid worksheetId, Guid instanceCorrelationId, string instanceCorrelationProvider, Guid sheetCorrelationId, string sheetCorrelationProvider, string? uiAnchor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,23 @@ public async Task<List<WorksheetInstance>> GetByWorksheetCorrelationAsync(Guid w
.Include(wi => wi.Values)
.FirstOrDefaultAsync(wi => wi.Id == worksheetInstanceId);
}

public async Task<bool> ExistsAsync(Guid worksheetId,
Guid instanceCorrelationId,
string instanceCorrelationProvider,
Guid sheetCorrelationId,
string sheetCorrelationProvider,
string? uiAnchor)
{
var dbSet = await GetDbSetAsync();

return await dbSet
.AnyAsync(s => s.WorksheetId == worksheetId
&& s.CorrelationId == instanceCorrelationId
&& s.CorrelationProvider == instanceCorrelationProvider
&& s.WorksheetCorrelationId == sheetCorrelationId
&& s.WorksheetCorrelationProvider == sheetCorrelationProvider
&& s.UiAnchor == uiAnchor);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,44 @@
using Unity.Flex.Worksheets.Collectors;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.MultiTenancy;

namespace Unity.Flex.Handlers
{
public class CreateWorksheetInstanceByFieldValuesHandler(WorksheetsManager worksheetsManager, IServiceProvider serviceProvider) : ILocalEventHandler<CreateWorksheetInstanceByFieldValuesEto>, ITransientDependency
/// <summary>
/// Handles creating worksheet instances from field values.
///
/// Tenant context handling:
/// - If the request comes through a route with {__tenant} parameter, ABP automatically sets the tenant context
/// - If the request is processed via an ABP background job, tenant context is preserved automatically
/// - If TenantId is explicitly set on the CreateWorksheetInstanceByFieldValuesEto, it will be used when current tenant is null
/// </summary>
public class CreateWorksheetInstanceByFieldValuesHandler(WorksheetsManager worksheetsManager,
IServiceProvider serviceProvider,
ICurrentTenant currentTenant)
: ILocalEventHandler<CreateWorksheetInstanceByFieldValuesEto>, ITransientDependency
{
public async Task HandleEventAsync(CreateWorksheetInstanceByFieldValuesEto eventData)
{
List<(Worksheet worksheet, WorksheetInstance worksheetIntance)> workSheetInstancePairs = await worksheetsManager.CreateWorksheetDataByFields(eventData);
// If current tenant is null but eventData provides a TenantId, use it
if (currentTenant.Id == null && eventData.TenantId.HasValue)
{
using (currentTenant.Change(eventData.TenantId))
{
await ProcessWorksheetInstancesAsync(eventData);
}
}
else
{
// Continue with current tenant context (could be null/host or already set)
await ProcessWorksheetInstancesAsync(eventData);
}
}

private async Task ProcessWorksheetInstancesAsync(CreateWorksheetInstanceByFieldValuesEto eventData)
{
List<(Worksheet worksheet, WorksheetInstance worksheetIntance)> workSheetInstancePairs =
await worksheetsManager.CreateWorksheetDataByFields(eventData);

foreach (var (worksheet, worksheetIntance) in workSheetInstancePairs.Where(s => s.worksheet.RequiresCollection()))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public class CreateWorksheetInstanceByFieldValuesEto
public List<(string fieldName, string chefsPropertyName, object? value)> CustomFields { get; set; } = [];
public Guid? VersionId { get; set; }
public string? VersionData { get; set; }
public Guid? TenantId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
public enum PaymentGroup
{
EFT = 1,
Cheque = 2,
GLP = 3
Cheque = 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;

namespace Unity.Payments.PaymentTags;

[Serializable]
public class AssignPaymentTagDto : AuditedEntityDto<Guid>
{
public Guid PaymentRequestId { get; set; }
public List<GlobalTagDto>? Tags { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using Volo.Abp.Application.Dtos;

namespace Unity.Payments.PaymentTags
{
[Serializable]
public class GlobalTagDto : EntityDto<Guid>
{
public string Name { get; set; } = string.Empty;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public interface IPaymentTagAppService : IApplicationService
{
Task<IList<PaymentTagDto>> GetListAsync();
Task<IList<PaymentTagDto>> GetListWithPaymentRequestIdsAsync(List<Guid> ids);
Task<PaymentTagDto> CreateorUpdateTagsAsync(Guid id, PaymentTagDto input);
Task<List<PaymentTagDto>> AssignTagsAsync(AssignPaymentTagDto input);
Task<PaymentTagDto?> GetPaymentTagsAsync(Guid id);

Task<PagedResultDto<TagSummaryCountDto>> GetTagSummaryAsync();
Task<int> GetMaxRenameLengthAsync(string originalTag);
Task<List<Guid>> RenameTagAsync(string originalTag, string replacementTag);
Task DeleteTagAsync(string deleteTag);
Task DeleteTagAsync(Guid id);
Task DeleteTagWithTagIdAsync(Guid tagId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ namespace Unity.Payments.PaymentTags;
public class PaymentTagDto : AuditedEntityDto<Guid>
{
public Guid PaymentRequestId { get; set; }
public string Text { get; set; } = string.Empty;
public GlobalTagDto? Tag { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Unity.Payments.PaymentTags;
public class TagSummaryCountDto
{
public required string Text { get; set; }
public required GlobalTagDto Tag { get; set; }
public required int Count { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public virtual Site Site
public virtual string RequesterName { get; private set; } = string.Empty;
public virtual string BatchName { get; private set; } = string.Empty;
public virtual decimal BatchNumber { get; private set; } = 0;
public virtual Collection<PaymentTag>? PaymentTags { get; set; }
public virtual Collection<PaymentTag>? PaymentTags { get; set; }
public virtual Collection<ExpenseApproval> ExpenseApprovals { get; private set; }
public virtual bool IsApproved { get => ExpenseApprovals.All(s => s.Status == ExpenseApprovalStatus.Approved); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Unity.Payments.Domain.PaymentTags
public interface IPaymentTagRepository : IRepository<PaymentTag, Guid>
{
Task<List<PaymentTag>> GetTagsByPaymentRequestIdAsync(Guid paymentRequestId);
Task<List<TagSummaryCount>> GetTagSummary();
Task<int> GetMaxRenameLengthAsync(string originalTag);
Task<List<PaymentTagSummaryCount>> GetTagSummary();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Unity.GrantManager.GlobalTag;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;

Expand All @@ -10,18 +11,28 @@ public class PaymentTag : AuditedAggregateRoot<Guid>, IMultiTenant
public Guid PaymentRequestId { get; set; }
public string Text { get; set; } = string.Empty;

protected PaymentTag()
public Guid TagId { get; set; }

public virtual Tag Tag
{
set => _tag = value;
get => _tag
?? throw new InvalidOperationException("Uninitialized property: " + nameof(Tag));
}
private Tag? _tag;

public PaymentTag()
{
/* This constructor is for ORMs to be used while getting the entity from the database. */
}

public PaymentTag(Guid id,
Guid paymentRequestId,
string text)
Guid tagId)
: base(id)
{
PaymentRequestId = paymentRequestId;
Text = text;
TagId = tagId;
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Unity.Payments.Domain.PaymentTags;
public class TagSummaryCount(string name, int count)
using Unity.GrantManager.GlobalTag;

namespace Unity.Payments.Domain.PaymentTags;
public class PaymentTagSummaryCount(Tag tag, int count)
{
public string Text { get; set; } = name;
public Tag Tag { get; set; } = tag;
public int Count { get; set; } = count;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
using Unity.Payments.Domain.Suppliers;
using Unity.Payments.Domain.PaymentConfigurations;
using Unity.Payments.Domain.PaymentTags;

using Unity.GrantManager;
using Unity.GrantManager.GlobalTag;
namespace Unity.Payments.EntityFrameworkCore;

public static class PaymentsDbContextModelCreatingExtensions
Expand Down Expand Up @@ -79,9 +80,16 @@ public static void ConfigurePayments(
PaymentsDbProperties.DbSchema);

b.ConfigureByConvention();
b.Property(x => x.Text)
b.HasOne(x => x.Tag)
.WithMany()
.HasForeignKey(x => x.TagId)
.IsRequired()
.HasMaxLength(250);
.OnDelete(DeleteBehavior.NoAction);
});
modelBuilder.Entity<Tag>(b =>
{
b.ToTable(GrantManagerConsts.TenantTablePrefix + "Tags", GrantManagerConsts.DbSchema);
b.ConfigureByConvention();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +21,20 @@ public async Task<List<PaymentTag>> GetTagsByPaymentRequestIdAsync(Guid paymentR
return await dbSet.Where(p => p.PaymentRequestId.Equals(paymentRequestId)).ToListAsync();
}

public virtual async Task<List<TagSummaryCount>> GetTagSummary()
public virtual async Task<List<PaymentTagSummaryCount>> GetTagSummary()
{
var dbSet = await GetDbSetAsync();
var results = dbSet
.AsNoTracking()
.AsEnumerable() // Forces client-side evaluation
.SelectMany(tag => tag.Text.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.Trim()))
.GroupBy(tag => tag)
.Select(group => new TagSummaryCount(
group.Key,
group.Count()
)).ToList();
var results = await dbSet
.AsNoTracking()
.Include(x => x.Tag) // Ensure Tag is loaded
.GroupBy(x => x.Tag)
.Select(group => new PaymentTagSummaryCount(
group.Key,
group.Count()
))
.ToListAsync();

return results;
}

/// <summary>
/// For a given Tag, finds the maximum length available for renaming.
/// </summary>
/// <param name="originalTag">The tag to be replaced.</param>
/// <returns>The maximum length available for renaming</returns>
public virtual async Task<int> GetMaxRenameLengthAsync(string originalTag)
{
var dbContext = await GetDbContextAsync();
var entityType = dbContext.Model.FindEntityType(typeof(PaymentTag));
var property = entityType?.FindProperty(nameof(PaymentTag.Text));

int maxColumnLength = property?.GetMaxLength() ?? 0;

var dbSet = await GetDbSetAsync();
int? maxTagSetLength = await dbSet
.AsNoTracking()
.Where(t => t.Text.Contains(originalTag))
.Select(t => t.Text.Length)
.OrderByDescending(len => len)
.FirstOrDefaultAsync();

if (maxTagSetLength == null || maxTagSetLength == 0)
{
return maxColumnLength;
}

return maxColumnLength + originalTag.Length - maxTagSetLength.Value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace Unity.Payments.Events;
[Serializable]
public class DeleteTagEto
{
public required string TagName { get; set; }
public required Guid TagId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Unity.Payments.Events;

[Serializable]
public class TagDeletedEto
{
public required Guid TagId { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class DeleteTagHandler(PaymentTagAppService paymentTagAppService) :
{
public async Task HandleEventAsync(DeleteTagEto eventData)
{
await paymentTagAppService.DeleteTagAsync(eventData.TagName);
await paymentTagAppService.DeleteTagWithTagIdAsync(eventData.TagId);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ await _paymentRequestsRepository
var paymentsQueryable = await _paymentRequestsRepository.GetQueryableAsync();
var paymentsWithTags = await paymentsQueryable
.Include(pr => pr.PaymentTags)
.ThenInclude(pt => pt.Tag)
.ToListAsync();

var mappedPayments = await MapToDtoAndLoadDetailsAsync(paymentsWithTags);
Expand Down
Loading