Skip to content

Commit

Permalink
controlling member invite and management (#1354)
Browse files Browse the repository at this point in the history
# Description

This PR includes the following proposed change(s):

- [spdbt-2679]
 1. controlling member invite 
 2. return controlling member application status and invite status
 3. remove controlling member connection with parentApp
 4. move member management to bizMember out of bizLicApp
  • Loading branch information
peggy-quartech authored Aug 30, 2024
1 parent 33c2a87 commit f91582b
Show file tree
Hide file tree
Showing 32 changed files with 732 additions and 241 deletions.
24 changes: 2 additions & 22 deletions src/Spd.Manager.Licence/BizLicAppContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public interface IBizLicAppManager
public Task<BizLicAppCommandResponse> Handle(BizLicAppReplaceCommand command, CancellationToken ct);
public Task<BizLicAppCommandResponse> Handle(BizLicAppRenewCommand command, CancellationToken ct);
public Task<BizLicAppCommandResponse> Handle(BizLicAppUpdateCommand command, CancellationToken ct);
public Task<Members> Handle(GetBizMembersQuery query, CancellationToken ct);
public Task<Unit> Handle(UpsertBizMembersCommand cmd, CancellationToken ct);
public Task<IEnumerable<LicenceAppListResponse>> Handle(GetBizLicAppListQuery cmd, CancellationToken ct);
public Task<FileResponse> Handle(BrandImageQuery qry, CancellationToken ct);
}
Expand Down Expand Up @@ -99,6 +97,8 @@ public abstract record BizLicenceApp : LicenceAppBase
public record NonSwlContactInfo : ContactInfo
{
public Guid? BizContactId { get; set; }
public ApplicationPortalStatusCode? ControllingMemberAppStatusCode { get; set; }
public ApplicationInviteStatusCode? InviteStatusCode { get; set; }
}

public record PrivateInvestigatorSwlContactInfo : ContactInfo
Expand All @@ -107,23 +107,3 @@ public record PrivateInvestigatorSwlContactInfo : ContactInfo
public Guid? BizContactId { get; set; }
public Guid? LicenceId { get; set; }
}

public record GetBizMembersQuery(Guid BizId, Guid? AppId = null) : IRequest<Members>;

public record Members
{
public IEnumerable<SwlContactInfo> SwlControllingMembers { get; set; }
public IEnumerable<NonSwlContactInfo> NonSwlControllingMembers { get; set; }
public IEnumerable<SwlContactInfo> Employees { get; set; }
};

public record MembersRequest : Members
{
public IEnumerable<Guid> ControllingMemberDocumentKeyCodes { get; set; } = Array.Empty<Guid>();//the document is saved in cache.
}

public record UpsertBizMembersCommand(
Guid BizId,
Guid? ApplicationId,
Members Members,
IEnumerable<LicAppFileInfo> LicAppFileInfos) : IRequest<Unit>;
86 changes: 18 additions & 68 deletions src/Spd.Manager.Licence/BizLicAppManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ internal class BizLicAppManager :
IRequestHandler<BizLicAppReplaceCommand, BizLicAppCommandResponse>,
IRequestHandler<BizLicAppRenewCommand, BizLicAppCommandResponse>,
IRequestHandler<BizLicAppUpdateCommand, BizLicAppCommandResponse>,
IRequestHandler<GetBizMembersQuery, Members>,
IRequestHandler<UpsertBizMembersCommand, Unit>,
IRequestHandler<GetBizLicAppListQuery, IEnumerable<LicenceAppListResponse>>,
IRequestHandler<BrandImageQuery, FileResponse>,
IBizLicAppManager
Expand Down Expand Up @@ -73,10 +71,6 @@ public async Task<BizLicAppResponse> Handle(GetBizLicAppQuery query, Cancellatio
BizLicAppResponse result = _mapper.Map<BizLicAppResponse>(response);
var existingDocs = await _documentRepository.QueryAsync(new DocumentQry(query.LicenceApplicationId), cancellationToken);
result.DocumentInfos = _mapper.Map<Document[]>(existingDocs.Items).Where(d => d.LicenceDocumentTypeCode != null).ToList();

if (result.BizId != null)
result.Members = await Handle(new GetBizMembersQuery((Guid)result.BizId, null), cancellationToken);

return result;
}

Expand Down Expand Up @@ -112,7 +106,6 @@ public async Task<BizLicAppCommandResponse> Handle(BizLicAppUpsertCommand cmd, C
if (cmd.BizLicAppUpsertRequest.Members != null && cmd.BizLicAppUpsertRequest.BizId != null)
await UpdateMembersAsync(cmd.BizLicAppUpsertRequest.Members,
cmd.BizLicAppUpsertRequest.BizId,
(Guid)cmd.BizLicAppUpsertRequest.LicenceAppId,
cancellationToken);

if (cmd.BizLicAppUpsertRequest.DocumentInfos != null && cmd.BizLicAppUpsertRequest.DocumentInfos.Any())
Expand Down Expand Up @@ -162,7 +155,6 @@ public async Task<BizLicAppCommandResponse> Handle(BizLicAppReplaceCommand cmd,
if (cmd.LicenceRequest.Members != null)
await UpdateMembersAsync(cmd.LicenceRequest.Members,
(Guid)originalLic.BizId,
(Guid)originalLic.LicenceAppId,
cancellationToken);

return new BizLicAppCommandResponse { LicenceAppId = response.LicenceAppId, Cost = cost };
Expand Down Expand Up @@ -209,7 +201,6 @@ await ValidateFilesForRenewUpdateAppAsync(cmd.LicenceRequest,
if (cmd.LicenceRequest.Members != null)
await UpdateMembersAsync(cmd.LicenceRequest.Members,
(Guid)originalBizLic.BizId,
(Guid)originalBizLic.LicenceAppId,
cancellationToken);

// Upload new files
Expand Down Expand Up @@ -303,55 +294,11 @@ await _documentRepository.ManageAsync(
if (cmd.LicenceRequest.Members != null)
await UpdateMembersAsync(cmd.LicenceRequest.Members,
(Guid)originalLicApp.BizId,
(Guid)originalLicApp.LicenceAppId,
cancellationToken);

return new BizLicAppCommandResponse { LicenceAppId = response?.LicenceAppId ?? originalLicApp.LicenceAppId, Cost = cost };
}

public async Task<Members> Handle(GetBizMembersQuery qry, CancellationToken ct)
{
var bizMembers = await _bizContactRepository.GetBizAppContactsAsync(new BizContactQry(qry.BizId, null), ct);
Members members = new();
members.SwlControllingMembers = bizMembers.Where(c => c.ContactId != null && c.LicenceId != null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.ControllingMember)
.Select(c => new SwlContactInfo()
{
BizContactId = c.BizContactId,
LicenceId = c.LicenceId,
ContactId = c.ContactId,
});
members.NonSwlControllingMembers = bizMembers.Where(c => c.ContactId == null && c.LicenceId == null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.ControllingMember)
.Select(c => _mapper.Map<NonSwlContactInfo>(c));
members.Employees = bizMembers.Where(c => c.ContactId != null && c.LicenceId != null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.Employee)
.Select(c => _mapper.Map<SwlContactInfo>(c));
return members;
}

public async Task<Unit> Handle(UpsertBizMembersCommand cmd, CancellationToken ct)
{
await UpdateMembersAsync(cmd.Members, cmd.BizId, cmd.ApplicationId, ct);
if (cmd.LicAppFileInfos.Any(f => f.LicenceDocumentTypeCode != LicenceDocumentTypeCode.CorporateRegistryDocument))
throw new ApiException(HttpStatusCode.BadRequest, "Can only Upload Corporate Registry Document for management of controlling member.");

if (cmd.LicAppFileInfos != null && cmd.LicAppFileInfos.Any())
{
foreach (LicAppFileInfo licAppFile in cmd.LicAppFileInfos)
{
SpdTempFile? tempFile = _mapper.Map<SpdTempFile>(licAppFile);
CreateDocumentCmd? fileCmd = _mapper.Map<CreateDocumentCmd>(licAppFile);
fileCmd.AccountId = cmd.BizId;
fileCmd.ApplicationId = cmd.ApplicationId;
fileCmd.TempFile = tempFile;
//create bcgov_documenturl and file
await _documentRepository.ManageAsync(fileCmd, ct);
}
}
return default;
}

public async Task<IEnumerable<LicenceAppListResponse>> Handle(GetBizLicAppListQuery query, CancellationToken cancellationToken)
{
LicenceAppQuery q = new(
Expand Down Expand Up @@ -418,21 +365,6 @@ public async Task<FileResponse> Handle(BrandImageQuery qry, CancellationToken ct
}
}

private async Task<Unit> UpdateMembersAsync(Members members, Guid bizId, Guid? appId, CancellationToken ct)
{
List<BizContactResp> contacts = _mapper.Map<List<BizContactResp>>(members.NonSwlControllingMembers);
contacts.AddRange(_mapper.Map<IList<BizContactResp>>(members.SwlControllingMembers));
IList<BizContactResp> employees = _mapper.Map<IList<BizContactResp>>(members.Employees);
foreach (var e in employees)
{
e.BizContactRoleCode = BizContactRoleEnum.Employee;
}
contacts.AddRange(employees);
BizContactUpsertCmd upsertCmd = new(bizId, appId, contacts);
await _bizContactRepository.ManageBizContactsAsync(upsertCmd, ct);
return default;
}

private async Task ValidateFilesForRenewUpdateAppAsync(BizLicAppSubmitRequest request,
IList<LicAppFileInfo> newFileInfos,
CancellationToken ct)
Expand Down Expand Up @@ -612,6 +544,24 @@ private async Task<CreateBizLicApplicationCmd> SetBizManagerInfo(
return createApp;
}


//todo: if fe can make seperate call to members and application, then we can give all members related info to BizMemberManager, then this function
//can be removed.
private async Task<Unit> UpdateMembersAsync(Members members, Guid bizId, CancellationToken ct)
{
List<BizContactResp> contacts = _mapper.Map<List<BizContactResp>>(members.NonSwlControllingMembers);
contacts.AddRange(_mapper.Map<IList<BizContactResp>>(members.SwlControllingMembers));
IList<BizContactResp> employees = _mapper.Map<IList<BizContactResp>>(members.Employees);
foreach (var e in employees)
{
e.BizContactRoleCode = BizContactRoleEnum.Employee;
}
contacts.AddRange(employees);
BizContactUpsertCmd upsertCmd = new(bizId, contacts);
await _bizContactRepository.ManageBizContactsAsync(upsertCmd, ct);
return default;
}

private sealed record ChangeSpec
{
public bool CategoriesChanged { get; set; }
Expand Down
37 changes: 37 additions & 0 deletions src/Spd.Manager.Licence/BizMemberContract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using MediatR;

namespace Spd.Manager.Licence;

public interface IBizMemberManager
{
public Task<Members> Handle(GetBizMembersQuery query, CancellationToken ct);
public Task<Unit> Handle(UpsertBizMembersCommand cmd, CancellationToken ct);
public Task<ControllingMemberInvitesCreateResponse> Handle(BizControllingMemberNewInviteCommand command, CancellationToken ct);
}

public record BizControllingMemberNewInviteCommand(Guid BizContactId, Guid UserId, string HostUrl) : IRequest<ControllingMemberInvitesCreateResponse>;

public record GetBizMembersQuery(Guid BizId, Guid? AppId = null) : IRequest<Members>;

public record Members
{
public IEnumerable<SwlContactInfo> SwlControllingMembers { get; set; }
public IEnumerable<NonSwlContactInfo> NonSwlControllingMembers { get; set; }
public IEnumerable<SwlContactInfo> Employees { get; set; }
};

public record MembersRequest : Members
{
public IEnumerable<Guid> ControllingMemberDocumentKeyCodes { get; set; } = Array.Empty<Guid>();//the document is saved in cache.
}

public record UpsertBizMembersCommand(
Guid BizId,
Guid? ApplicationId,
Members Members,
IEnumerable<LicAppFileInfo> LicAppFileInfos) : IRequest<Unit>;

public record ControllingMemberInvitesCreateResponse(Guid BizContactId)
{
public bool CreateSuccess { get; set; }
}
137 changes: 137 additions & 0 deletions src/Spd.Manager.Licence/BizMemberManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using AutoMapper;
using MediatR;
using Spd.Resource.Repository.Application;
using Spd.Resource.Repository.Biz;
using Spd.Resource.Repository.BizContact;
using Spd.Resource.Repository.BizLicApplication;
using Spd.Resource.Repository.ControllingMemberInvite;
using Spd.Resource.Repository.Document;
using Spd.Resource.Repository.LicApp;
using Spd.Resource.Repository.Licence;
using Spd.Resource.Repository.LicenceFee;
using Spd.Resource.Repository.PersonLicApplication;
using Spd.Resource.Repository.Tasks;
using Spd.Utilities.FileStorage;
using Spd.Utilities.Shared.Exceptions;
using System.Net;

namespace Spd.Manager.Licence;
internal class BizMemberManager :
LicenceAppManagerBase,
IRequestHandler<GetBizMembersQuery, Members>,
IRequestHandler<UpsertBizMembersCommand, Unit>,
IRequestHandler<BizControllingMemberNewInviteCommand, ControllingMemberInvitesCreateResponse>,
IBizMemberManager
{
private readonly IBizLicApplicationRepository _bizLicApplicationRepository;
private readonly IBizContactRepository _bizContactRepository;
private readonly ITaskRepository _taskRepository;
private readonly IBizRepository _bizRepository;
private readonly IPersonLicApplicationRepository _personLicApplicationRepository;
private readonly IControllingMemberInviteRepository _cmInviteRepository;

public BizMemberManager(
ILicenceRepository licenceRepository,
ILicAppRepository licAppRepository,
IMapper mapper,
IDocumentRepository documentUrlRepository,
ILicenceFeeRepository feeRepository,
IMainFileStorageService mainFileStorageService,
ITransientFileStorageService transientFileStorageService,
IBizContactRepository bizContactRepository,
IControllingMemberInviteRepository cmInviteRepository)
: base(mapper,
documentUrlRepository,
feeRepository,
licenceRepository,
mainFileStorageService,
transientFileStorageService,
licAppRepository)
{
_bizContactRepository = bizContactRepository;
_cmInviteRepository = cmInviteRepository;
}
public async Task<ControllingMemberInvitesCreateResponse> Handle(BizControllingMemberNewInviteCommand cmd, CancellationToken cancellationToken)
{
//check if bizContact already has invitation
//todo: probably we do not need to check this. it should allow user to send out invite multiple times.
//var existingInvites = await _cmInviteRepository.QueryAsync(new ControllingMemberInviteQuery(cmd.BizContactId), cancellationToken);
//if (existingInvites.Any(i => i.Status == Resource.Repository.ApplicationInviteStatusEnum.Sent))
// throw new ApiException(HttpStatusCode.BadRequest, "There is already an invite sent out.");

//get info from bizContactId
BizContactResp contactResp = await _bizContactRepository.GetBizContactAsync(cmd.BizContactId, cancellationToken);
if (contactResp.BizContactRoleCode != BizContactRoleEnum.ControllingMember)
throw new ApiException(HttpStatusCode.BadRequest, "Cannot send out invitation for non-controlling member.");
if (string.IsNullOrWhiteSpace(contactResp.EmailAddress))
throw new ApiException(HttpStatusCode.BadRequest, "Cannot send out invitation when there is no email address provided.");
if (contactResp.LatestControllingMemberCrcAppPortalStatusEnum != null)
throw new ApiException(HttpStatusCode.BadRequest, "This business contact already has a CRC application");
//todo : how can we check if the CRC approved but it has been expired.

var createCmd = _mapper.Map<ControllingMemberInviteCreateCmd>(contactResp);
createCmd.CreatedByUserId = cmd.UserId;
createCmd.HostUrl = cmd.HostUrl;
await _cmInviteRepository.ManageAsync(createCmd, cancellationToken);

return new ControllingMemberInvitesCreateResponse(cmd.BizContactId) { CreateSuccess = true };
}

public async Task<Members> Handle(GetBizMembersQuery qry, CancellationToken ct)
{
var bizMembers = await _bizContactRepository.QueryBizContactsAsync(new BizContactQry(qry.BizId, null), ct);
Members members = new();
members.SwlControllingMembers = bizMembers.Where(c => c.ContactId != null && c.LicenceId != null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.ControllingMember)
.Select(c => new SwlContactInfo()
{
BizContactId = c.BizContactId,
LicenceId = c.LicenceId,
ContactId = c.ContactId,
});
members.NonSwlControllingMembers = bizMembers.Where(c => c.ContactId == null && c.LicenceId == null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.ControllingMember)
.Select(c => _mapper.Map<NonSwlContactInfo>(c));
members.Employees = bizMembers.Where(c => c.ContactId != null && c.LicenceId != null)
.Where(c => c.BizContactRoleCode == BizContactRoleEnum.Employee)
.Select(c => _mapper.Map<SwlContactInfo>(c));
return members;
}

public async Task<Unit> Handle(UpsertBizMembersCommand cmd, CancellationToken ct)
{
await UpdateMembersAsync(cmd.Members, cmd.BizId, ct);
if (cmd.LicAppFileInfos.Any(f => f.LicenceDocumentTypeCode != LicenceDocumentTypeCode.CorporateRegistryDocument))
throw new ApiException(HttpStatusCode.BadRequest, "Can only Upload Corporate Registry Document for management of controlling member.");

if (cmd.LicAppFileInfos != null && cmd.LicAppFileInfos.Any())
{
foreach (LicAppFileInfo licAppFile in cmd.LicAppFileInfos)
{
SpdTempFile? tempFile = _mapper.Map<SpdTempFile>(licAppFile);
CreateDocumentCmd? fileCmd = _mapper.Map<CreateDocumentCmd>(licAppFile);
fileCmd.AccountId = cmd.BizId;
fileCmd.ApplicationId = cmd.ApplicationId;
fileCmd.TempFile = tempFile;
//create bcgov_documenturl and file
await _documentRepository.ManageAsync(fileCmd, ct);
}
}
return default;
}

private async Task<Unit> UpdateMembersAsync(Members members, Guid bizId, CancellationToken ct)
{
List<BizContactResp> contacts = _mapper.Map<List<BizContactResp>>(members.NonSwlControllingMembers);
contacts.AddRange(_mapper.Map<IList<BizContactResp>>(members.SwlControllingMembers));
IList<BizContactResp> employees = _mapper.Map<IList<BizContactResp>>(members.Employees);
foreach (var e in employees)
{
e.BizContactRoleCode = BizContactRoleEnum.Employee;
}
contacts.AddRange(employees);
BizContactUpsertCmd upsertCmd = new(bizId, contacts);
await _bizContactRepository.ManageBizContactsAsync(upsertCmd, ct);
return default;
}
}
2 changes: 2 additions & 0 deletions src/Spd.Manager.Licence/BizMemberValidation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
namespace Spd.Manager.Licence;

2 changes: 1 addition & 1 deletion src/Spd.Manager.Licence/LicenceAppManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ protected async Task<Guid> GetLatestApplicationId(Guid? contactId, Guid? bizId,
.OrderByDescending(a => a.SubmittedOn)
.FirstOrDefault();
if (app == null)
throw new ApiException(HttpStatusCode.BadRequest, $"there is no {licenceTypeEnum}.");
throw new ApiException(HttpStatusCode.InternalServerError, $"there is no completed {licenceTypeEnum} application.");
return app.LicenceAppId;
}

Expand Down
Loading

0 comments on commit f91582b

Please sign in to comment.