diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java index dd8d398a15..faeb4f48fe 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java @@ -6,6 +6,7 @@ private FeatureList() { public static final String CMS_DISABLED = "cms-disabled"; public static final String DEMOGRAPHIC_CHANGE_REQUEST = "demographic-change-request"; + public static final String HTI_4_2028_01_01 = "2028-01-01"; public static final String HTI_5_ERD = "hti-5-erd"; public static final String INSIGHTS_DISPLAY = "insights-display"; public static final String SERVICE_BASE_URL_LIST_CHANGE_REQUEST = "sbul-change-request"; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewer.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewer.java index 19ff31f237..a9f4f01e21 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewer.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewer.java @@ -7,9 +7,11 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.ff4j.FF4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; @@ -27,15 +29,18 @@ public class RequiredAndRelatedCriteriaReviewer extends PermissionBasedReviewer private ErrorMessageUtil msgUtil; private CertificationCriterionService criterionService; private ValidationUtils validationUtils; + private FF4j ff4j; @Autowired public RequiredAndRelatedCriteriaReviewer(CertificationCriterionService criterionService, ErrorMessageUtil msgUtil, ValidationUtils validationUtils, - ResourcePermissionsFactory resourcePermissionsFactory) { + ResourcePermissionsFactory resourcePermissionsFactory, + FF4j ff4j) { super(msgUtil, resourcePermissionsFactory); this.criterionService = criterionService; this.msgUtil = msgUtil; this.validationUtils = validationUtils; + this.ff4j = ff4j; } @Override @@ -47,6 +52,7 @@ public void review(CertifiedProductSearchDetails listing) { checkA4A9CriteriaHaveRequiredDependencies(listing, attestedCriteria); checkBCriteriaHaveRequiredDependencies(listing, attestedCriteria); checkB11HasRequiredDependencies(listing, attestedCriteria); + checkB3B4RequiredDependency(listing, attestedCriteria); checkCCriteriaHaveRequiredDependencies(listing, attestedCriteria); checkE1CriterionHasRequiredDependencies(listing, attestedCriteria); checkE3CriterionHasRequiredDependencies(listing, attestedCriteria); @@ -208,6 +214,24 @@ private void checkB11HasRequiredDependencies(CertifiedProductSearchDetails listi } } + private void checkB3B4RequiredDependency(CertifiedProductSearchDetails listing, List attestedCriteria) { + if (!ff4j.check(FeatureList.HTI_4_2028_01_01)) { + return; + } + + CertificationCriterion b3 = criterionService.get(Criteria2015.B_3_CURES); + CertificationCriterion b4 = criterionService.get(Criteria2015.B_4_HTI4); + boolean attestsB3 = isInList(b3, attestedCriteria); + boolean attestsB4 = isInList(b4, attestedCriteria); + + if (attestsB3 && !attestsB4) { + listing.addBusinessErrorMessage( + msgUtil.getMessage("listing.criteria.complementaryCriteriaRequired", + Util.formatCriteriaNumber(b3), + Util.formatCriteriaNumber(b4))); + } + } + private void checkCCriteriaHaveRequiredDependencies(CertifiedProductSearchDetails listing, List attestedCriteria) { List cCriteria = Stream.of( criterionService.get(Criteria2015.C_1), diff --git a/chpl/chpl-service/src/test/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewerTest.java b/chpl/chpl-service/src/test/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewerTest.java index 18e3f6d7f1..854cddd331 100644 --- a/chpl/chpl-service/src/test/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewerTest.java +++ b/chpl/chpl-service/src/test/java/gov/healthit/chpl/validation/listing/reviewer/edition2015/RequiredAndRelatedCriteriaReviewerTest.java @@ -7,11 +7,13 @@ import java.time.LocalDate; import java.util.List; +import org.ff4j.FF4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; @@ -36,6 +38,7 @@ public class RequiredAndRelatedCriteriaReviewerTest { private ValidationUtils validationUtil; private ErrorMessageUtil errorMessageUtil; private ResourcePermissions resourcePermissions; + private FF4j ff4j; private RequiredAndRelatedCriteriaReviewer reviewer; private CertificationCriterion a1, a2, a3, a4, a5, a6, a9, a10, a12, a13, a14, a15, @@ -207,6 +210,8 @@ public void before() throws ParseException { resourcePermissions = Mockito.mock(CognitoResourcePermissions.class); Mockito.when(resourcePermissions.doesUserHaveRole(ArgumentMatchers.any(List.class))).thenReturn(true); + ff4j = Mockito.mock(FF4j.class); + Mockito.when(ff4j.check(ArgumentMatchers.eq(FeatureList.HTI_4_2028_01_01))).thenReturn(false); validationUtil = new ValidationUtils(certificationCriterionService); errorMessageUtil = Mockito.mock(ErrorMessageUtil.class); Mockito.when(errorMessageUtil.getMessage(ArgumentMatchers.eq(CRITERIA_REQUIRED_ERROR_KEY), ArgumentMatchers.anyString())) @@ -221,7 +226,7 @@ public void before() throws ParseException { Mockito.when(resourcePermissionsFactory.get()).thenReturn(resourcePermissions); reviewer = new RequiredAndRelatedCriteriaReviewer(certificationCriterionService, errorMessageUtil, - validationUtil, resourcePermissionsFactory); + validationUtil, resourcePermissionsFactory, ff4j); } @Test @@ -851,6 +856,109 @@ public void review_b11CriteriaAttestedWithAllDependencies_hasNoErrors() { assertEquals(0, listing.getErrorMessages().size()); } + @Test + public void review_b3AndB4CriteriaAttestedFlagIsOn_hasNoErrors() { + Mockito.when(ff4j.check(ArgumentMatchers.eq(FeatureList.HTI_4_2028_01_01))).thenReturn(true); + CertifiedProductSearchDetails listing = CertifiedProductSearchDetails.builder() + .certificationResult(CertificationResult.builder() + .criterion(g4) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(g5) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(b3Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(b4) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d1) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d2Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d3Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d5) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d6) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d7) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d8) + .success(Boolean.TRUE) + .build()) + .build(); + reviewer.review(listing); + assertEquals(0, listing.getErrorMessages().size()); + } + + @Test + public void review_b3CriteriaAttestedWithoutB4FlagIsOn_hasError() { + Mockito.when(ff4j.check(ArgumentMatchers.eq(FeatureList.HTI_4_2028_01_01))).thenReturn(true); + CertifiedProductSearchDetails listing = CertifiedProductSearchDetails.builder() + .certificationResult(CertificationResult.builder() + .criterion(g4) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(g5) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(b3Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d1) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d2Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d3Cures) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d5) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d6) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d7) + .success(Boolean.TRUE) + .build()) + .certificationResult(CertificationResult.builder() + .criterion(d8) + .success(Boolean.TRUE) + .build()) + .build(); + reviewer.review(listing); + assertEquals(1, listing.getErrorMessages().size()); + assertTrue(listing.getErrorMessages().contains(String.format(CRITERIA_COMPLEMENT_NOT_FOUND, Util.formatCriteriaNumber(b3Cures), Util.formatCriteriaNumber(b4)))); + } + @Test public void review_cCriteriaNotPresent_noError() { CertifiedProductSearchDetails listing = CertifiedProductSearchDetails.builder()