diff --git a/src/Spd.Manager.Licence/BizMemberContract.cs b/src/Spd.Manager.Licence/BizMemberContract.cs index 7be569e5b..0c0766a83 100644 --- a/src/Spd.Manager.Licence/BizMemberContract.cs +++ b/src/Spd.Manager.Licence/BizMemberContract.cs @@ -5,6 +5,11 @@ namespace Spd.Manager.Licence; public interface IBizMemberManager { public Task Handle(GetBizMembersQuery query, CancellationToken ct); + public Task Handle(CreateBizEmployeeCommand cmd, CancellationToken ct); + public Task Handle(CreateBizSwlControllingMemberCommand cmd, CancellationToken ct); + public Task Handle(CreateBizNonSwlControllingMemberCommand cmd, CancellationToken ct); + public Task Handle(UpdateBizNonSwlControllingMemberCommand cmd, CancellationToken ct); + public Task Handle(DeleteBizMemberCommand cmd, CancellationToken ct); public Task Handle(UpsertBizMembersCommand cmd, CancellationToken ct); public Task Handle(BizControllingMemberNewInviteCommand command, CancellationToken ct); public Task Handle(VerifyBizControllingMemberInviteCommand command, CancellationToken ct); @@ -12,9 +17,18 @@ public interface IBizMemberManager public record BizControllingMemberNewInviteCommand(Guid BizContactId, Guid UserId, string HostUrl) : IRequest; public record VerifyBizControllingMemberInviteCommand(string InviteEncryptedCode) : IRequest; - public record GetBizMembersQuery(Guid BizId, Guid? AppId = null) : IRequest; - +public record UpsertBizMembersCommand( + Guid BizId, + Guid? ApplicationId, + Members Members, + IEnumerable LicAppFileInfos) : IRequest; //deprecated +public record CreateBizEmployeeCommand(Guid BizId, SwlContactInfo Employee) : IRequest; +public record CreateBizSwlControllingMemberCommand(Guid BizId, SwlContactInfo SwlControllingMember) : IRequest; +public record CreateBizNonSwlControllingMemberCommand(Guid BizId, NonSwlContactInfo NonSwlControllingMember) : IRequest; +public record UpdateBizNonSwlControllingMemberCommand(Guid BizId, Guid BizContactId, NonSwlContactInfo NonSwlControllingMember) : IRequest; +public record DeleteBizMemberCommand(Guid BizId, Guid BizContactId) : IRequest; +public record BizMemberResponse(Guid? bizContactId); public record Members { public IEnumerable SwlControllingMembers { get; set; } @@ -27,12 +41,6 @@ public record MembersRequest : Members public IEnumerable ControllingMemberDocumentKeyCodes { get; set; } = Array.Empty();//the document is saved in cache. } -public record UpsertBizMembersCommand( - Guid BizId, - Guid? ApplicationId, - Members Members, - IEnumerable LicAppFileInfos) : IRequest; - public record ControllingMemberInvitesCreateResponse(Guid BizContactId) { public bool CreateSuccess { get; set; } diff --git a/src/Spd.Manager.Licence/BizMemberManager.cs b/src/Spd.Manager.Licence/BizMemberManager.cs index beee20b26..427c68641 100644 --- a/src/Spd.Manager.Licence/BizMemberManager.cs +++ b/src/Spd.Manager.Licence/BizMemberManager.cs @@ -23,6 +23,11 @@ internal class BizMemberManager : IRequestHandler, IRequestHandler, IRequestHandler, + IRequestHandler, + IRequestHandler, + IRequestHandler, + IRequestHandler, + IRequestHandler, IBizMemberManager { private readonly IBizLicApplicationRepository _bizLicApplicationRepository; @@ -106,7 +111,6 @@ public async Task Handle(BizControllingM 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(contactResp); createCmd.CreatedByUserId = cmd.UserId; @@ -137,6 +141,43 @@ public async Task Handle(GetBizMembersQuery qry, CancellationToken ct) return members; } + public async Task Handle(CreateBizEmployeeCommand cmd, CancellationToken ct) + { + BizContact bizContact = _mapper.Map(cmd.Employee); + bizContact.BizContactRoleCode = BizContactRoleEnum.Employee; + bizContact.BizId = cmd.BizId; + Guid? bizContactId = await _bizContactRepository.ManageBizContactsAsync(new BizContactCreateCmd(bizContact), ct); + return new BizMemberResponse(bizContactId); + } + public async Task Handle(CreateBizSwlControllingMemberCommand cmd, CancellationToken ct) + { + BizContact bizContact = _mapper.Map(cmd.SwlControllingMember); + bizContact.BizContactRoleCode = BizContactRoleEnum.ControllingMember; + bizContact.BizId = cmd.BizId; + Guid? bizContactId = await _bizContactRepository.ManageBizContactsAsync(new BizContactCreateCmd(bizContact), ct); + return new BizMemberResponse(bizContactId); + } + public async Task Handle(CreateBizNonSwlControllingMemberCommand cmd, CancellationToken ct) + { + BizContact bizContact = _mapper.Map(cmd.NonSwlControllingMember); + bizContact.BizContactRoleCode = BizContactRoleEnum.ControllingMember; + bizContact.BizId = cmd.BizId; + Guid? bizContactId = await _bizContactRepository.ManageBizContactsAsync(new BizContactCreateCmd(bizContact), ct); + return new BizMemberResponse(bizContactId); + } + public async Task Handle(DeleteBizMemberCommand cmd, CancellationToken ct) + { + await _bizContactRepository.ManageBizContactsAsync(new BizContactDeleteCmd(cmd.BizContactId), ct); + return default; + } + public async Task Handle(UpdateBizNonSwlControllingMemberCommand cmd, CancellationToken ct) + { + BizContact bizContact = _mapper.Map(cmd.NonSwlControllingMember); + bizContact.BizContactRoleCode = BizContactRoleEnum.ControllingMember; + bizContact.BizId = cmd.BizId; + Guid? bizContactId = await _bizContactRepository.ManageBizContactsAsync(new BizContactUpdateCmd(cmd.BizContactId, bizContact), ct); + return new BizMemberResponse(bizContactId); + } public async Task Handle(UpsertBizMembersCommand cmd, CancellationToken ct) { await UpdateMembersAsync(cmd.Members, cmd.BizId, ct); diff --git a/src/Spd.Manager.Licence/Mappings.cs b/src/Spd.Manager.Licence/Mappings.cs index b72a321d7..887c59302 100644 --- a/src/Spd.Manager.Licence/Mappings.cs +++ b/src/Spd.Manager.Licence/Mappings.cs @@ -352,7 +352,8 @@ public Mappings() CreateMap() .IncludeBase() .ForMember(d => d.HostUrl, opt => opt.Ignore()); - + CreateMap(); + CreateMap(); CreateMap() .ForMember(d => d.ResidentialAddress, opt => opt.MapFrom(s => s.ResidentialAddressData)) .ForPath(d => d.ResidentialAddress.AddressLine1, opt => opt.MapFrom(s => s.ResidentialAddressData.AddressLine1)) @@ -647,7 +648,6 @@ private static List GetBranchAddr(IEnumerable branchInfo {LicenceDocumentTypeCode.CorporateRegistryDocument, DocumentTypeEnum.CorporateRegistryDocument } }.ToImmutableDictionary(); - private static readonly ImmutableDictionary LicenceDocumentType2Dictionary = new Dictionary() { {LicenceDocumentTypeCode.BcServicesCard, DocumentTypeEnum.AdditionalGovIdDocument}, diff --git a/src/Spd.Presentation.Licensing/Controllers/BizMembersController.cs b/src/Spd.Presentation.Licensing/Controllers/BizMembersController.cs index efbad5ee4..cb09f6152 100644 --- a/src/Spd.Presentation.Licensing/Controllers/BizMembersController.cs +++ b/src/Spd.Presentation.Licensing/Controllers/BizMembersController.cs @@ -39,7 +39,7 @@ public BizMembersController(IPrincipal currentUser, /// /// /// - [Route("api/business-licence-application/{bizId}/members")] + [Route("api/business/{bizId}/members")] [HttpGet] [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] public async Task GetMembers([FromRoute] Guid bizId, CancellationToken ct) @@ -48,13 +48,13 @@ public async Task GetMembers([FromRoute] Guid bizId, CancellationToken } /// - /// Upsert Biz Application controlling members and employees, controlling members include swl and non-swl + /// Deprecated. Upsert Biz Application controlling members and employees, controlling members include swl and non-swl /// /// /// /// /// - [Route("api/business-licence-application/{bizId}/members")] + [Route("api/business/{bizId}/members")] [HttpPost] [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] public async Task UpsertMembers([FromRoute] Guid bizId, [FromBody] MembersRequest members, CancellationToken ct) @@ -68,6 +68,82 @@ public async Task UpsertMembers([FromRoute] Guid bizId, [FromBody] return Ok(); } + /// + /// Create Biz employee + /// + /// + /// + /// + /// + [Route("api/business/{bizId}/employees")] + [HttpPost] + [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] + public async Task CreateEmployee([FromRoute] Guid bizId, [FromBody] SwlContactInfo employee, CancellationToken ct) + { + return await _mediator.Send(new CreateBizEmployeeCommand(bizId, employee), ct); + } + + /// + /// Create Biz swl controlling member + /// + /// + /// + /// + /// + [Route("api/business/{bizId}/swl-controlling-members")] + [HttpPost] + [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] + public async Task CreateSwlControllingMember([FromRoute] Guid bizId, [FromBody] SwlContactInfo controllingMember, CancellationToken ct) + { + return await _mediator.Send(new CreateBizSwlControllingMemberCommand(bizId, controllingMember), ct); + } + + /// + /// Create Biz swl controlling member + /// + /// + /// + /// + /// + [Route("api/business/{bizId}/non-swl-controlling-members")] + [HttpPost] + [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] + public async Task CreateNonSwlControllingMember([FromRoute] Guid bizId, [FromBody] NonSwlContactInfo controllingMember, CancellationToken ct) + { + return await _mediator.Send(new CreateBizNonSwlControllingMemberCommand(bizId, controllingMember), ct); + } + + /// + /// Update Non swl biz controlling member + /// + /// + /// + /// + /// + [Route("api/business/{bizId}/non-swl-controlling-members/{bizContactId}")] + [HttpPut] + [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] + public async Task UpdateNonSwlControllingMember([FromRoute] Guid bizId, [FromRoute] Guid bizContactId, NonSwlContactInfo controllingMember, CancellationToken ct) + { + return await _mediator.Send(new UpdateBizNonSwlControllingMemberCommand(bizId, bizContactId, controllingMember), ct); + } + + /// + /// Delete Biz swl controlling member + /// + /// + /// + /// + /// + [Route("api/business/{bizId}/members/{bizContactId}")] + [HttpDelete] + [Authorize(Policy = "OnlyBceid", Roles = "PrimaryBusinessManager,BusinessManager")] + public async Task DeleteBizMember([FromRoute] Guid bizId, [FromRoute] Guid bizContactId, CancellationToken ct) + { + await _mediator.Send(new DeleteBizMemberCommand(bizId, bizContactId), ct); + return Ok(); + } + /// /// Create controlling member crc invitation for this biz contact /// diff --git a/src/Spd.Resource.Repository/BizContact/BizContactRepository.cs b/src/Spd.Resource.Repository/BizContact/BizContactRepository.cs index 849dec0f0..a3200c06b 100644 --- a/src/Spd.Resource.Repository/BizContact/BizContactRepository.cs +++ b/src/Spd.Resource.Repository/BizContact/BizContactRepository.cs @@ -1,5 +1,4 @@ using AutoMapper; -using MediatR; using Microsoft.Dynamics.CRM; using Microsoft.Extensions.Logging; using Spd.Utilities.Dynamics; @@ -52,7 +51,71 @@ public async Task> QueryBizContactsAsync(BizContactQ return _mapper.Map>(bizContacts.ToList()); } - public async Task ManageBizContactsAsync(BizContactUpsertCmd cmd, CancellationToken ct) + public async Task ManageBizContactsAsync(BizContactCmd cmd, CancellationToken ct) + { + return cmd switch + { + BizContactCreateCmd c => await CreateBizContactAsync(c, ct), + BizContactUpdateCmd c => await UpdateBizContactAsync(c, ct), + BizContactDeleteCmd c => await DeleteBizContactAsync(c, ct), + _ => throw new NotSupportedException($"{cmd.GetType().Name} is not supported") + }; + } + + private async Task CreateBizContactAsync(BizContactCreateCmd cmd, CancellationToken ct) + { + account? biz = await _context.GetOrgById(cmd.BizContact.BizId, ct); + spd_businesscontact bizContact = _mapper.Map(cmd.BizContact); + bizContact.spd_businesscontactid = Guid.NewGuid(); + contact? c = null; + if (cmd.BizContact.ContactId != null) + { + c = await _context.GetContactById((Guid)cmd.BizContact.ContactId, ct); + if (c == null) + throw new ApiException(HttpStatusCode.BadRequest, $"invalid contact {cmd.BizContact.ContactId.Value}"); + bizContact.spd_fullname = $"{c.lastname},{c.firstname}"; + } + _context.AddTospd_businesscontacts(bizContact); + if (c != null) + _context.SetLink(bizContact, nameof(bizContact.spd_ContactId), c); + if (cmd.BizContact.LicenceId != null) + { + spd_licence? swlLic = _context.spd_licences.Where(l => l.spd_licenceid == cmd.BizContact.LicenceId && l.statecode == DynamicsConstants.StateCode_Active).FirstOrDefault(); + Guid swlServiceTypeId = DynamicsContextLookupHelpers.ServiceTypeGuidDictionary[ServiceTypeEnum.SecurityWorkerLicence.ToString()]; + //only swl can be linked to. + if (swlLic == null || swlLic._spd_licencetype_value != swlServiceTypeId) + throw new ApiException(System.Net.HttpStatusCode.BadRequest, $"invalid licence {cmd.BizContact.LicenceId}"); + _context.SetLink(bizContact, nameof(bizContact.spd_SWLNumber), swlLic); + } + _context.SetLink(bizContact, nameof(bizContact.spd_OrganizationId), biz); + await _context.SaveChangesAsync(ct); + return bizContact.spd_businesscontactid; + } + + private async Task DeleteBizContactAsync(BizContactDeleteCmd cmd, CancellationToken ct) + { + spd_businesscontact? bizContact = await _context.GetBizContactById(cmd.BizContactId, ct); + bizContact.statecode = DynamicsConstants.StateCode_Inactive; + _context.UpdateObject(bizContact); + await _context.SaveChangesAsync(ct); + return null; + } + + private async Task UpdateBizContactAsync(BizContactUpdateCmd cmd, CancellationToken ct) + { + spd_businesscontact? bizContact = await _context.GetBizContactById(cmd.BizContactId, ct); + if (bizContact == null) throw new ApiException(HttpStatusCode.BadRequest, "Cannot find the member."); + if (bizContact.spd_role != (int)BizContactRoleOptionSet.ControllingMember) + throw new ApiException(HttpStatusCode.BadRequest, "Cannot update non-controlling member."); + if (bizContact._spd_swlnumber_value != null) + throw new ApiException(HttpStatusCode.BadRequest, "Cannot update controlling member with secure worker licence."); + _mapper.Map(cmd.BizContact, bizContact); + _context.UpdateObject(bizContact); + await _context.SaveChangesAsync(ct); + return cmd.BizContactId; + } + + private async Task UpsertBizContacts(BizContactUpsertCmd cmd, CancellationToken ct) { IQueryable bizContacts = _context.spd_businesscontacts .Expand(b => b.spd_businesscontact_spd_application) @@ -116,7 +179,7 @@ public async Task ManageBizContactsAsync(BizContactUpsertCmd cmd, Cancella } } await _context.SaveChangesAsync(ct); - return default; + return null; } } diff --git a/src/Spd.Resource.Repository/BizContact/Contract.cs b/src/Spd.Resource.Repository/BizContact/Contract.cs index f38875ba5..abaca3c96 100644 --- a/src/Spd.Resource.Repository/BizContact/Contract.cs +++ b/src/Spd.Resource.Repository/BizContact/Contract.cs @@ -1,5 +1,4 @@ -using MediatR; -using Spd.Resource.Repository.Application; +using Spd.Resource.Repository.Application; namespace Spd.Resource.Repository.BizContact { @@ -7,18 +6,30 @@ public interface IBizContactRepository { Task GetBizContactAsync(Guid bizContactId, CancellationToken ct); Task> QueryBizContactsAsync(BizContactQry qry, CancellationToken ct); - Task ManageBizContactsAsync(BizContactUpsertCmd cmd, CancellationToken ct); + Task ManageBizContactsAsync(BizContactCmd cmd, CancellationToken ct); } //command - public record BizContactUpsertCmd(Guid BizId, List Data); + public interface BizContactCmd; + public record BizContactUpsertCmd(Guid BizId, List Data) : BizContactCmd; //deprecated + public record BizContactCreateCmd(BizContact BizContact) : BizContactCmd; + public record BizContactUpdateCmd(Guid BizContactId, BizContact BizContact) : BizContactCmd; + public record BizContactDeleteCmd(Guid BizContactId) : BizContactCmd; //query public record BizContactQry(Guid? BizId, Guid? AppId, BizContactRoleEnum? RoleCode = null, bool IncludeInactive = false); - //shared content - public record BizContactResp + public record BizContactResp : BizContact { public Guid? BizContactId { get; set; } + public Guid? LatestControllingMemberCrcAppId { get; set; } + public ApplicationPortalStatusEnum? LatestControllingMemberCrcAppPortalStatusEnum { get; set; } + public Guid? LatestControllingMemberInvitationId { get; set; } + public ApplicationInviteStatusEnum? LatestControllingMemberInvitationStatusEnum { get; set; } + } + + //shared content + public record BizContact + { public string? EmailAddress { get; set; } public string? GivenName { get; set; } public string? MiddleName1 { get; set; } @@ -28,10 +39,6 @@ public record BizContactResp public Guid? LicenceId { get; set; } public BizContactRoleEnum BizContactRoleCode { get; set; } = BizContactRoleEnum.ControllingMember; public Guid BizId { get; set; } - public Guid? LatestControllingMemberCrcAppId { get; set; } - public ApplicationPortalStatusEnum? LatestControllingMemberCrcAppPortalStatusEnum { get; set; } - public Guid? LatestControllingMemberInvitationId { get; set; } - public ApplicationInviteStatusEnum? LatestControllingMemberInvitationStatusEnum { get; set; } } public enum BizContactRoleEnum diff --git a/src/Spd.Resource.Repository/BizContact/Mappings.cs b/src/Spd.Resource.Repository/BizContact/Mappings.cs index c7200a20f..6911e0040 100644 --- a/src/Spd.Resource.Repository/BizContact/Mappings.cs +++ b/src/Spd.Resource.Repository/BizContact/Mappings.cs @@ -9,24 +9,27 @@ public class Mappings : Profile { public Mappings() { + _ = CreateMap() + .ForMember(d => d.spd_role, opt => opt.MapFrom(s => SharedMappingFuncs.GetOptionset(s.BizContactRoleCode))) + .ForMember(d => d.spd_middlename1, opt => opt.MapFrom(s => s.MiddleName1)) + .ForMember(d => d.spd_firstname, opt => opt.MapFrom(s => s.GivenName)) + .ForMember(d => d.spd_surname, opt => opt.MapFrom(s => s.Surname)) + .ForMember(d => d.spd_middlename2, opt => opt.MapFrom(s => s.MiddleName2)) + .ForMember(d => d.spd_email, opt => opt.MapFrom(s => s.EmailAddress)) + .ForMember(d => d.spd_fullname, opt => opt.MapFrom(s => $"{s.Surname}, {s.GivenName}")) + .ReverseMap(); + _ = CreateMap() - .ForMember(d => d.BizContactId, opt => opt.MapFrom(s => s.spd_businesscontactid)) - .ForMember(d => d.EmailAddress, opt => opt.MapFrom(s => s.spd_email)) - .ForMember(d => d.BizContactRoleCode, opt => opt.MapFrom(s => SharedMappingFuncs.GetEnum(s.spd_role))) - .ForMember(d => d.Surname, opt => opt.MapFrom(s => s.spd_surname)) - .ForMember(d => d.MiddleName1, opt => opt.MapFrom(s => s.spd_middlename1)) - .ForMember(d => d.GivenName, opt => opt.MapFrom(s => s.spd_firstname)) - .ForMember(d => d.MiddleName2, opt => opt.MapFrom(s => s.spd_middlename2)) - .ForMember(d => d.ContactId, opt => opt.MapFrom(s => s._spd_contactid_value)) - .ForMember(d => d.LicenceId, opt => opt.MapFrom(s => s._spd_swlnumber_value)) - .ForMember(d => d.BizId, opt => opt.MapFrom(s => s._spd_organizationid_value)) - .ForMember(d => d.LatestControllingMemberInvitationId, opt => opt.MapFrom(s => GetLastestControllingMemberInvite(s.spd_businesscontact_spd_portalinvitation.ToList()).InviteId)) - .ForMember(d => d.LatestControllingMemberInvitationStatusEnum, opt => opt.MapFrom(s => GetLastestControllingMemberInvite(s.spd_businesscontact_spd_portalinvitation.ToList()).InviteStatus)) - .ForMember(d => d.LatestControllingMemberCrcAppId, opt => opt.MapFrom(s => GetLastestControllingMemberCrcApp(s.spd_businesscontact_spd_application.ToList()).AppId)) - .ForMember(d => d.LatestControllingMemberCrcAppPortalStatusEnum, opt => opt.MapFrom(s => GetLastestControllingMemberCrcApp(s.spd_businesscontact_spd_application.ToList()).PortalStatus)) - .ReverseMap() - .ForMember(d => d.spd_role, opt => opt.MapFrom(s => SharedMappingFuncs.GetOptionset(s.BizContactRoleCode))) - .ForMember(d => d.spd_fullname, opt => opt.MapFrom(s => $"{s.Surname}, {s.GivenName}")); + .IncludeBase() + .ForMember(d => d.BizContactId, opt => opt.MapFrom(s => s.spd_businesscontactid)) + .ForMember(d => d.BizContactRoleCode, opt => opt.MapFrom(s => SharedMappingFuncs.GetEnum(s.spd_role))) + .ForMember(d => d.ContactId, opt => opt.MapFrom(s => s._spd_contactid_value)) + .ForMember(d => d.LicenceId, opt => opt.MapFrom(s => s._spd_swlnumber_value)) + .ForMember(d => d.BizId, opt => opt.MapFrom(s => s._spd_organizationid_value)) + .ForMember(d => d.LatestControllingMemberInvitationId, opt => opt.MapFrom(s => GetLastestControllingMemberInvite(s.spd_businesscontact_spd_portalinvitation.ToList()).InviteId)) + .ForMember(d => d.LatestControllingMemberInvitationStatusEnum, opt => opt.MapFrom(s => GetLastestControllingMemberInvite(s.spd_businesscontact_spd_portalinvitation.ToList()).InviteStatus)) + .ForMember(d => d.LatestControllingMemberCrcAppId, opt => opt.MapFrom(s => GetLastestControllingMemberCrcApp(s.spd_businesscontact_spd_application.ToList()).AppId)) + .ForMember(d => d.LatestControllingMemberCrcAppPortalStatusEnum, opt => opt.MapFrom(s => GetLastestControllingMemberCrcApp(s.spd_businesscontact_spd_application.ToList()).PortalStatus)); } private (Guid? AppId, ApplicationPortalStatusEnum? PortalStatus) GetLastestControllingMemberCrcApp(IEnumerable apps)