diff --git a/application/CohortManager/src/Functions/ParticipantManagementServices/ManageServiceNowParticipant/ManageServiceNowParticipantFunction.cs b/application/CohortManager/src/Functions/ParticipantManagementServices/ManageServiceNowParticipant/ManageServiceNowParticipantFunction.cs index 5b62dc42d7..d45af7109b 100644 --- a/application/CohortManager/src/Functions/ParticipantManagementServices/ManageServiceNowParticipant/ManageServiceNowParticipantFunction.cs +++ b/application/CohortManager/src/Functions/ParticipantManagementServices/ManageServiceNowParticipant/ManageServiceNowParticipantFunction.cs @@ -17,15 +17,18 @@ public class ManageServiceNowParticipantFunction private readonly IHttpClientFunction _httpClientFunction; private readonly IExceptionHandler _exceptionHandler; private readonly IDataServiceClient _participantManagementClient; + private readonly IDataServiceClient _cohortDistributionClient; public ManageServiceNowParticipantFunction(ILogger logger, IOptions config, - IHttpClientFunction httpClientFunction, IExceptionHandler handleException, IDataServiceClient participantManagementClient) + IHttpClientFunction httpClientFunction, IExceptionHandler handleException, IDataServiceClient participantManagementClient, + IDataServiceClient cohortDistributionClient) { _logger = logger; _config = config.Value; _httpClientFunction = httpClientFunction; _exceptionHandler = handleException; _participantManagementClient = participantManagementClient; + _cohortDistributionClient = cohortDistributionClient; } /// @@ -44,7 +47,7 @@ public async Task Run([ServiceBusTrigger(topicName: "%ServiceNowParticipantManag var participantManagement = await _participantManagementClient.GetSingleByFilter( x => x.NHSNumber == serviceNowParticipant.NhsNumber && x.ScreeningId == serviceNowParticipant.ScreeningId); - var success = await ProcessParticipantRecord(serviceNowParticipant, participantManagement); + var success = await ProcessParticipantRecord(serviceNowParticipant, participantManagement, participantDemographic); if (!success) { await HandleException(new Exception("Participant Management Data Service request failed"), serviceNowParticipant, ServiceNowMessageType.AddRequestInProgress); @@ -111,7 +114,7 @@ private async Task ValidateParticipantData(ServiceNowParticipant serviceNo return true; } - private async Task ProcessParticipantRecord(ServiceNowParticipant serviceNowParticipant, ParticipantManagement? participantManagement) + private async Task ProcessParticipantRecord(ServiceNowParticipant serviceNowParticipant, ParticipantManagement? participantManagement, ParticipantDemographic participantDemographic) { if (participantManagement is null) { @@ -124,7 +127,7 @@ private async Task ProcessParticipantRecord(ServiceNowParticipant serviceN return true; } - return await UpdateExistingParticipant(serviceNowParticipant, participantManagement); + return await UpdateExistingParticipant(serviceNowParticipant, participantManagement, participantDemographic); } private async Task AddNewParticipant(ServiceNowParticipant serviceNowParticipant) @@ -149,10 +152,18 @@ private async Task AddNewParticipant(ServiceNowParticipant serviceNowParti _logger.LogInformation("Participant with NHS Number: {NhsNumber} set as High Risk", serviceNowParticipant.NhsNumber); } - return await _participantManagementClient.Add(participantToAdd); + var participantManagementSuccess = await _participantManagementClient.Add(participantToAdd); + if (!participantManagementSuccess) + { + return false; + } + + await HandleGpCodeForAddParticipant(serviceNowParticipant); + + return true; } - private async Task UpdateExistingParticipant(ServiceNowParticipant serviceNowParticipant, ParticipantManagement participantManagement) + private async Task UpdateExistingParticipant(ServiceNowParticipant serviceNowParticipant, ParticipantManagement participantManagement, ParticipantDemographic participantDemographic) { _logger.LogInformation("Existing participant management record found, updating record {ParticipantId}", participantManagement.ParticipantId); @@ -163,7 +174,15 @@ private async Task UpdateExistingParticipant(ServiceNowParticipant service HandleVhrFlagForExistingParticipant(serviceNowParticipant, participantManagement); - return await _participantManagementClient.Update(participantManagement); + var participantManagementSuccess = await _participantManagementClient.Update(participantManagement); + if (!participantManagementSuccess) + { + return false; + } + + await HandleGpCodeForAmendParticipant(serviceNowParticipant, participantDemographic); + + return true; } private void HandleVhrFlagForExistingParticipant(ServiceNowParticipant serviceNowParticipant, ParticipantManagement participantManagement) @@ -182,6 +201,68 @@ private void HandleVhrFlagForExistingParticipant(ServiceNowParticipant serviceNo } } + private async Task HandleGpCodeForAddParticipant(ServiceNowParticipant serviceNowParticipant) + { + var hasDummyGpCode = CheckIfHasDummyGpCode(serviceNowParticipant); + if (!hasDummyGpCode) + { + return; + } + + _logger.LogInformation("ADD participant with NHS Number: {NhsNumber} has dummy GP code: {GpCode}, updating Cohort Distribution table", + serviceNowParticipant.NhsNumber, serviceNowParticipant.RequiredGpCode); + + await UpdateCohortDistributionGpCode(serviceNowParticipant, serviceNowParticipant.RequiredGpCode!, false); + } + + private async Task HandleGpCodeForAmendParticipant(ServiceNowParticipant serviceNowParticipant, ParticipantDemographic participantDemographic) + { + if (string.IsNullOrEmpty(participantDemographic.PrimaryCareProvider)) return; + + _logger.LogInformation("AMEND participant with NHS Number: {NhsNumber}, overwriting Primary_Care_Provider with PDS data: {UpdatedGpCode}", + serviceNowParticipant.NhsNumber, participantDemographic.PrimaryCareProvider); + + await UpdateCohortDistributionGpCode(serviceNowParticipant, participantDemographic.PrimaryCareProvider, true); + } + + private async Task UpdateCohortDistributionGpCode(ServiceNowParticipant serviceNowParticipant, string gpCode, bool isAmendParticipant) + { + try + { + var cohortDistribution = await _cohortDistributionClient.GetSingleByFilter(x => x.NHSNumber == serviceNowParticipant.NhsNumber); + + if (cohortDistribution == null) + { + _logger.LogError("No Cohort Distribution record found for NHS Number: {NhsNumber}", serviceNowParticipant.NhsNumber); + return; + } + + if (isAmendParticipant && cohortDistribution.PrimaryCareProvider == gpCode) + { + _logger.LogInformation("Primary_Care_Provider for NHS Number: {NhsNumber} is already up to date: {GpCode}", serviceNowParticipant.NhsNumber, gpCode); + return; + } + + cohortDistribution.PrimaryCareProvider = gpCode; + cohortDistribution.RecordUpdateDateTime = DateTime.UtcNow; + + var success = await _cohortDistributionClient.Update(cohortDistribution); + if (success) + { + _logger.LogInformation("Successfully updated Primary Care Provider in Cohort Distribution for NHS Number: {NhsNumber}", serviceNowParticipant.NhsNumber); + } + + if (!success) + { + _logger.LogError("Failed to update Primary Care Provider in Cohort Distribution for NHS Number: {NhsNumber}", serviceNowParticipant.NhsNumber); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating Cohort Distribution GP code for NHS Number: {NhsNumber}", serviceNowParticipant.NhsNumber); + } + } + private async Task HandleException(Exception exception, ServiceNowParticipant serviceNowParticipant, ServiceNowMessageType serviceNowMessageType) { _logger.LogError(exception, "Exception occurred whilst attempting to add participant from ServiceNow"); @@ -214,4 +295,10 @@ private static bool CheckIfVhrParticipant(ServiceNowParticipant serviceNowPartic { return serviceNowParticipant.ReasonForAdding == ServiceNowReasonsForAdding.VeryHighRisk; } + + private static bool CheckIfHasDummyGpCode(ServiceNowParticipant serviceNowParticipant) + { + return !string.IsNullOrEmpty(serviceNowParticipant.RequiredGpCode) && + serviceNowParticipant.RequiredGpCode.StartsWith("ZZZ", StringComparison.OrdinalIgnoreCase); + } } diff --git a/tests/UnitTests/ParticipantManagementServicesTests/ManageServiceNowParticipantTests/ManageServiceNowParticipantFunctionTests.cs b/tests/UnitTests/ParticipantManagementServicesTests/ManageServiceNowParticipantTests/ManageServiceNowParticipantFunctionTests.cs index 7bf951be57..f90d2835ee 100644 --- a/tests/UnitTests/ParticipantManagementServicesTests/ManageServiceNowParticipantTests/ManageServiceNowParticipantFunctionTests.cs +++ b/tests/UnitTests/ParticipantManagementServicesTests/ManageServiceNowParticipantTests/ManageServiceNowParticipantFunctionTests.cs @@ -22,6 +22,7 @@ public class ManageServiceNowParticipantFunctionTests private readonly Mock _httpClientFunctionMock = new(); private readonly Mock _handleExceptionMock = new(); private readonly Mock> _dataServiceClientMock = new(); + private readonly Mock> _cohortDistributionClientMock = new(); private readonly ServiceNowParticipant _serviceNowParticipant; private readonly ManageServiceNowParticipantFunction _function; @@ -55,7 +56,8 @@ public ManageServiceNowParticipantFunctionTests() _configMock.Object, _httpClientFunctionMock.Object, _handleExceptionMock.Object, - _dataServiceClientMock.Object + _dataServiceClientMock.Object, + _cohortDistributionClientMock.Object ); _messageType1Request = JsonSerializer.Serialize(new SendServiceNowMessageRequestBody @@ -653,4 +655,263 @@ public async Task Run_WhenNonVhrParticipantExistsWithNullVhrFlag_LeavesVhrFlagAs _loggerMock.VerifyLogger(LogLevel.Information, "Existing participant management record found, updating record 123"); } + + [TestMethod] + [DataRow("ZZZ123")] + [DataRow("zzz456")] + public async Task Run_WhenAddingNewParticipantWithDummyGpCode_UpdatesCohortDistribution(string requiredGpCode) + { + // Arrange + var participantWithGpCode = new ServiceNowParticipant() + { + ScreeningId = 1, + NhsNumber = 1234567890, + FirstName = "Samantha", + FamilyName = "Bloggs", + DateOfBirth = new DateOnly(1970, 1, 1), + ServiceNowCaseNumber = "CS123", + BsoCode = "ABC", + ReasonForAdding = ServiceNowReasonsForAdding.RequiresCeasing, + RequiredGpCode = requiredGpCode + }; + + SetupPdsResponse(participantWithGpCode, "REAL123"); + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter( + It.Is>>(x => + x.Compile().Invoke(new ParticipantManagement { NHSNumber = participantWithGpCode.NhsNumber, ScreeningId = participantWithGpCode.ScreeningId }) + ))).ReturnsAsync((ParticipantManagement)null!); + + _dataServiceClientMock.Setup(x => x.Add(It.IsAny())).ReturnsAsync(true); + + var existingCohortDistribution = new CohortDistribution + { + NHSNumber = participantWithGpCode.NhsNumber, + PrimaryCareProvider = "OLD123" + }; + + _cohortDistributionClientMock.Setup(x => x.GetSingleByFilter( + It.Is>>(expr => + expr.Compile().Invoke(new CohortDistribution { NHSNumber = participantWithGpCode.NhsNumber }) + ))).ReturnsAsync(existingCohortDistribution); + + _cohortDistributionClientMock.Setup(x => x.Update(It.Is(c => + c.NHSNumber == participantWithGpCode.NhsNumber && + c.PrimaryCareProvider == requiredGpCode && + c.RecordUpdateDateTime != null))).ReturnsAsync(true); + + // Act + await _function.Run(participantWithGpCode); + + // Assert + _cohortDistributionClientMock.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _cohortDistributionClientMock.Verify(x => x.Update(It.IsAny()), Times.Once); + _loggerMock.VerifyLogger(LogLevel.Information, $"ADD participant with NHS Number: {participantWithGpCode.NhsNumber} has dummy GP code: {requiredGpCode}, updating Cohort Distribution table"); + _loggerMock.VerifyLogger(LogLevel.Information, $"Successfully updated Primary Care Provider in Cohort Distribution for NHS Number: {participantWithGpCode.NhsNumber}"); + } + + [TestMethod] + [DataRow("ABC123")] + [DataRow("")] + [DataRow(null)] + public async Task Run_WhenAddingNewParticipantWithoutDummyGpCode_DoesNotUpdateCohortDistribution(string requiredGpCode) + { + // Arrange + var participantWithGpCode = new ServiceNowParticipant() + { + ScreeningId = 1, + NhsNumber = 1234567890, + FirstName = "Samantha", + FamilyName = "Bloggs", + DateOfBirth = new DateOnly(1970, 1, 1), + ServiceNowCaseNumber = "CS123", + BsoCode = "ABC", + ReasonForAdding = ServiceNowReasonsForAdding.RequiresCeasing, + RequiredGpCode = requiredGpCode + }; + + SetupPdsResponse(participantWithGpCode, "REAL123"); + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter( + It.Is>>(x => + x.Compile().Invoke(new ParticipantManagement { NHSNumber = participantWithGpCode.NhsNumber, ScreeningId = participantWithGpCode.ScreeningId }) + ))).ReturnsAsync((ParticipantManagement)null!); + + _dataServiceClientMock.Setup(x => x.Add(It.IsAny())).ReturnsAsync(true); + + // Act + await _function.Run(participantWithGpCode); + + // Assert + _cohortDistributionClientMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public async Task Run_WhenAmendingExistingParticipant_DoesNotUpdateWhenGpCodeIsSame() + { + // Arrange + SetupPdsResponse(_serviceNowParticipant, "REAL123"); + + var existingParticipant = new ParticipantManagement + { + ParticipantId = 123, + ScreeningId = _serviceNowParticipant.ScreeningId, + NHSNumber = _serviceNowParticipant.NhsNumber + }; + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter( + It.Is>>(x => + x.Compile().Invoke(new ParticipantManagement { NHSNumber = _serviceNowParticipant.NhsNumber, ScreeningId = _serviceNowParticipant.ScreeningId }) + ))).ReturnsAsync(existingParticipant); + + _dataServiceClientMock.Setup(x => x.Update(It.IsAny())).ReturnsAsync(true); + + var existingCohortDistribution = new CohortDistribution + { + NHSNumber = _serviceNowParticipant.NhsNumber, + PrimaryCareProvider = "REAL123" + }; + + _cohortDistributionClientMock.Setup(x => x.GetSingleByFilter( + It.Is>>(expr => + expr.Compile().Invoke(new CohortDistribution { NHSNumber = _serviceNowParticipant.NhsNumber }) + ))).ReturnsAsync(existingCohortDistribution); + + // Act + await _function.Run(_serviceNowParticipant); + + // Assert + _cohortDistributionClientMock.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _cohortDistributionClientMock.Verify(x => x.Update(It.IsAny()), Times.Never); + _loggerMock.VerifyLogger(LogLevel.Information, $"Primary_Care_Provider for NHS Number: {_serviceNowParticipant.NhsNumber} is already up to date: REAL123"); + } + + [TestMethod] + [DataRow("ZZZ456", "REAL123")] + [DataRow("OLD789", "NEW123")] + [DataRow("", "NEW123")] + public async Task Run_WhenAmendingExistingParticipant_UpdatesCohortDistributionWhenGpCodeChanges(string currentGpCode, string pdsGpCode) + { + // Arrange + SetupPdsResponse(_serviceNowParticipant, pdsGpCode); + + var existingParticipant = new ParticipantManagement + { + ParticipantId = 123, + ScreeningId = _serviceNowParticipant.ScreeningId, + NHSNumber = _serviceNowParticipant.NhsNumber + }; + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter( + It.Is>>(x => + x.Compile().Invoke(new ParticipantManagement { NHSNumber = _serviceNowParticipant.NhsNumber, ScreeningId = _serviceNowParticipant.ScreeningId }) + ))).ReturnsAsync(existingParticipant); + + _dataServiceClientMock.Setup(x => x.Update(It.IsAny())).ReturnsAsync(true); + + var existingCohortDistribution = new CohortDistribution + { + NHSNumber = _serviceNowParticipant.NhsNumber, + PrimaryCareProvider = currentGpCode + }; + + _cohortDistributionClientMock.Setup(x => x.GetSingleByFilter( + It.Is>>(expr => + expr.Compile().Invoke(new CohortDistribution { NHSNumber = _serviceNowParticipant.NhsNumber }) + ))).ReturnsAsync(existingCohortDistribution); + + _cohortDistributionClientMock.Setup(x => x.Update(It.Is(c => + c.NHSNumber == _serviceNowParticipant.NhsNumber && + c.PrimaryCareProvider == pdsGpCode && + c.RecordUpdateDateTime != null))).ReturnsAsync(true); + + // Act + await _function.Run(_serviceNowParticipant); + + // Assert + _cohortDistributionClientMock.Verify(x => x.GetSingleByFilter(It.IsAny>>()), Times.Once); + _cohortDistributionClientMock.Verify(x => x.Update(It.IsAny()), Times.Once); + _loggerMock.VerifyLogger(LogLevel.Information, $"AMEND participant with NHS Number: {_serviceNowParticipant.NhsNumber}, overwriting Primary_Care_Provider with PDS data: {pdsGpCode}"); + _loggerMock.VerifyLogger(LogLevel.Information, $"Successfully updated Primary Care Provider in Cohort Distribution for NHS Number: {_serviceNowParticipant.NhsNumber}"); + } + + [TestMethod] + public async Task Run_WhenAmendingParticipantButNoPdsGpCode_DoesNotUpdateCohortDistribution() + { + // Arrange + SetupPdsResponse(_serviceNowParticipant, ""); + + var existingParticipant = new ParticipantManagement + { + ParticipantId = 123, + ScreeningId = _serviceNowParticipant.ScreeningId, + NHSNumber = _serviceNowParticipant.NhsNumber + }; + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter( + It.Is>>(x => + x.Compile().Invoke(new ParticipantManagement { NHSNumber = _serviceNowParticipant.NhsNumber, ScreeningId = _serviceNowParticipant.ScreeningId }) + ))).ReturnsAsync(existingParticipant); + + _dataServiceClientMock.Setup(x => x.Update(It.IsAny())).ReturnsAsync(true); + + // Act + await _function.Run(_serviceNowParticipant); + + // Assert + _cohortDistributionClientMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public async Task Run_WhenCohortDistributionUpdateFails_LogsError() + { + // Arrange + var participantWithDummyGp = new ServiceNowParticipant() + { + ScreeningId = 1, + NhsNumber = 1234567890, + FirstName = "Samantha", + FamilyName = "Bloggs", + DateOfBirth = new DateOnly(1970, 1, 1), + ServiceNowCaseNumber = "CS123", + BsoCode = "ABC", + ReasonForAdding = ServiceNowReasonsForAdding.RequiresCeasing, + RequiredGpCode = "ZZZ123" + }; + + SetupPdsResponse(participantWithDummyGp); + + _dataServiceClientMock.Setup(client => client.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync((ParticipantManagement)null!); + _dataServiceClientMock.Setup(x => x.Add(It.IsAny())).ReturnsAsync(true); + + var existingCohortDistribution = new CohortDistribution { NHSNumber = participantWithDummyGp.NhsNumber }; + _cohortDistributionClientMock.Setup(x => x.GetSingleByFilter(It.IsAny>>())) + .ReturnsAsync(existingCohortDistribution); + _cohortDistributionClientMock.Setup(x => x.Update(It.IsAny())).ReturnsAsync(false); + + // Act + await _function.Run(participantWithDummyGp); + + // Assert + _loggerMock.VerifyLogger(LogLevel.Error, $"Failed to update Primary Care Provider in Cohort Distribution for NHS Number: {participantWithDummyGp.NhsNumber}"); + } + + private void SetupPdsResponse(ServiceNowParticipant participant, string? primaryCareProvider = null) + { + var json = JsonSerializer.Serialize(new ParticipantDemographic + { + NhsNumber = participant.NhsNumber, + GivenName = participant.FirstName, + FamilyName = participant.FamilyName, + DateOfBirth = participant.DateOfBirth.ToString("yyyy-MM-dd"), + PrimaryCareProvider = primaryCareProvider + }); + + _httpClientFunctionMock.Setup(x => x.SendGetResponse($"{_configMock.Object.Value.RetrievePdsDemographicURL}?nhsNumber={participant.NhsNumber}")) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); + } }