diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/IgnorableResponseFieldAnnotationIntrospector.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/IgnorableResponseFieldAnnotationIntrospector.java index 31ecbd3a18..8c8ef30cb0 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/IgnorableResponseFieldAnnotationIntrospector.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/IgnorableResponseFieldAnnotationIntrospector.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField; +import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData; import gov.healthit.chpl.sed.DeprecatedSedSummaryData; import gov.healthit.chpl.sed.DeprecatedSedTestTaskData; import lombok.extern.log4j.Log4j2; @@ -29,7 +30,7 @@ public boolean hasIgnoreMarker(MapperConfig config, AnnotatedMember m) { if (super.hasIgnoreMarker(config, m)) { return true; } else { - return isDeprecatedAndIgnorable(m) || isSedAndIgnorable(m); + return isDeprecatedAndIgnorable(m) || isSedAndIgnorable(m) || isRwtAndIgnorable(m); } } @@ -45,4 +46,10 @@ private boolean isSedAndIgnorable(AnnotatedMember m) { boolean isSedTestTask = _findAnnotation(m, DeprecatedSedTestTaskData.class) != null; return isHti5Erd && (isSedSummary || isSedTestTask); } + + private boolean isRwtAndIgnorable(AnnotatedMember m) { + boolean isHti5Erd = ff4j.check(FeatureList.HTI_5_ERD); + boolean isRwtPlans = _findAnnotation(m, DeprecatedRwtPlansData.class) != null; + return isHti5Erd && isRwtPlans; + } } diff --git a/chpl/chpl-resources/src/main/resources/environment.properties b/chpl/chpl-resources/src/main/resources/environment.properties index ed9615b5bc..906087a0d6 100644 --- a/chpl/chpl-resources/src/main/resources/environment.properties +++ b/chpl/chpl-resources/src/main/resources/environment.properties @@ -366,10 +366,10 @@ realWorldTestingCriteriaKeys={2021: 'criterion.170_315_b_1_old,\ criterion.170_315_g_7,\ criterion.170_315_g_8,\ criterion.170_315_g_9_cures,\ + criterion.170_315_g_10,\ criterion.170_315_g_31,\ criterion.170_315_g_32,\ criterion.170_315_g_33,\ - criterion.170_315_g_10,\ criterion.170_315_h_1,\ criterion.170_315_h_2,\ criterion.170_315_j_20,\ diff --git a/chpl/chpl-resources/src/main/resources/errors.properties b/chpl/chpl-resources/src/main/resources/errors.properties index 04016c54b7..c2def18062 100644 --- a/chpl/chpl-resources/src/main/resources/errors.properties +++ b/chpl/chpl-resources/src/main/resources/errors.properties @@ -990,6 +990,7 @@ changeRequest.listingUrl.serviceBaseUrlList.sameUrl=No change to the Service Bas changeRequest.listingUrl.serviceBaseUrlList.missing=The Service Base URL List may not be blank for %s. changeRequest.missingDetails=Details about what is being changed are required. changeRequest.listingUrl.rwtUrl.featureDisabled=Creating a Change Request to update a Real World Testing URL is not enabled. +changeRequest.listingUrl.rwtPlans.notAllowed=Creating a change request for an RWT Plans URL is not allowed. changeRequest.listingUrl.rwtUrl.missing=The Real World Testing URL may not be blank for %s. changeRequest.listingUrl.checkDateRequired=The Check Date is required when accepting a Real World Testing URL change request. changeRequest.listingUrl.checkDateFuture=The Check Date may not be in the future when accepting a Real World Testing URL change request. diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvDataWriter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvDataWriter.java index a5379107e8..1057c403a9 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvDataWriter.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvDataWriter.java @@ -73,19 +73,25 @@ public class ListingCsvDataWriter { private static final int DEVELOPER_CONTACT_COL_START = 18; private static final int SVAP_NOTICE_URL_COL = 21; private static final int RWT_COL_START = 22; - private static final int TARGETED_USERS_COL = 26; - private static final int QMS_STD_START_COL = 27; - private static final int ICS_START_COL = 30; - private static final int ACCESSIBILITY_STANDARDS_START_COL = 32; - private static final int K1_URL_COL = 34; - private static final int CQM_START_COL = 35; + private static final int TARGETED_USERS_COL = 24; + private static final int TARGETED_USERS_COL_PRE_HTI5 = 26; + private static final int QMS_STD_START_COL = 25; + private static final int QMS_STD_START_COL_PRE_HTI5 = 27; + private static final int ICS_START_COL = 28; + private static final int ICS_START_COL_PRE_HTI5 = 30; + private static final int ACCESSIBILITY_STANDARDS_START_COL = 30; + private static final int ACCESSIBILITY_STANDARDS_START_COL_PRE_HTI5 = 32; + private static final int K1_URL_COL = 32; + private static final int K1_URL_COL_PRE_HTI5 = 34; + private static final int CQM_START_COL = 33; + private static final int CQM_START_COL_PRE_HTI5 = 35; private static final int SED_REPORT_URL_COL = 38; private static final int SED_INTENDED_USERS_COL = 39; private static final int SED_TESTING_DATE_COL = 40; private static final int PARTICIPANT_START_COL = 41; private static final int TASK_START_COL = 50; private static final int CRITERIA_START_COL_PRE_HTI5 = 65; - private static final int CRITERIA_START_COL = 38; + private static final int CRITERIA_START_COL = 36; private static final int ADDITIONAL_SOFTWARE_COL_COUNT = 5; private static final int UCD_PROCESS_COL_COUNT = 2; private static final int SED_TESTING_COL_COUNT = 2; @@ -125,7 +131,7 @@ public List> getCsvData(CertifiedProductSearchDetails listing, int addQmsStandards(csvDataMatrix, listing.getQmsStandards()); addIcs(csvDataMatrix, listing.getIcs()); addAccessibilityStandards(csvDataMatrix, listing.getAccessibilityStandards()); - csvDataMatrix[0][K1_URL_COL] = listing.getMandatoryDisclosures(); + csvDataMatrix[0][(!ff4j.check(FeatureList.HTI_5_ERD) ? K1_URL_COL_PRE_HTI5 : K1_URL_COL)] = listing.getMandatoryDisclosures(); addCqms(csvDataMatrix, listing.getCqmResults()); if (!ff4j.check(FeatureList.HTI_5_ERD)) { csvDataMatrix[0][SED_REPORT_URL_COL] = listing.getSedReportFileLocation(); @@ -333,47 +339,50 @@ private void addDeveloperContact(String[][] csvDataMatrix, Developer developer) private void addRwtData(String[][] csvDataMatrix, CertifiedProductSearchDetails listing) { int col = RWT_COL_START; - csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtPlansUrl()) ? listing.getRwtPlansUrl() : ""; - csvDataMatrix[0][col++] = listing.getRwtPlansCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtPlansCheckDate())) : ""; + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtPlansUrl()) ? listing.getRwtPlansUrl() : ""; + csvDataMatrix[0][col++] = listing.getRwtPlansCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtPlansCheckDate())) : ""; + } csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtResultsUrl()) ? listing.getRwtResultsUrl() : ""; csvDataMatrix[0][col++] = listing.getRwtResultsCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtResultsCheckDate())) : ""; } private void addTargetedUsers(String[][] csvDataMatrix, List targetedUsers) { + int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? TARGETED_USERS_COL_PRE_HTI5 : TARGETED_USERS_COL; for (int i = 0; i < targetedUsers.size(); i++) { CertifiedProductTargetedUser targetedUser = targetedUsers.get(i); - csvDataMatrix[i][TARGETED_USERS_COL] = targetedUser.getTargetedUserName(); + csvDataMatrix[i][currCol] = targetedUser.getTargetedUserName(); } } private void addQmsStandards(String[][] csvDataMatrix, List qmsStandards) { for (int i = 0; i < qmsStandards.size(); i++) { - int col = QMS_STD_START_COL; + int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? QMS_STD_START_COL_PRE_HTI5 : QMS_STD_START_COL; CertifiedProductQmsStandard qmsStandard = qmsStandards.get(i); - csvDataMatrix[i][col++] = qmsStandard.getQmsStandardName(); - csvDataMatrix[i][col++] = qmsStandard.getApplicableCriteria(); - csvDataMatrix[i][col++] = qmsStandard.getQmsModification(); + csvDataMatrix[i][currCol++] = qmsStandard.getQmsStandardName(); + csvDataMatrix[i][currCol++] = qmsStandard.getApplicableCriteria(); + csvDataMatrix[i][currCol++] = qmsStandard.getQmsModification(); } } private void addIcs(String[][] csvDataMatrix, InheritedCertificationStatus ics) { if (ics != null && !CollectionUtils.isEmpty(ics.getParents())) { - int col = ICS_START_COL; - csvDataMatrix[0][col++] = "1"; + int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? ICS_START_COL_PRE_HTI5 : ICS_START_COL; + csvDataMatrix[0][currCol++] = "1"; for (int i = 0; i < ics.getParents().size(); i++) { CertifiedProduct parent = ics.getParents().get(i); - csvDataMatrix[i][col++] = parent.getChplProductNumber(); + csvDataMatrix[i][currCol++] = parent.getChplProductNumber(); } } } private void addAccessibilityStandards(String[][] csvDataMatrix, List accStds) { if (!CollectionUtils.isEmpty(accStds)) { - int col = ACCESSIBILITY_STANDARDS_START_COL; - csvDataMatrix[0][col++] = "1"; + int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? ACCESSIBILITY_STANDARDS_START_COL_PRE_HTI5 : ACCESSIBILITY_STANDARDS_START_COL; + csvDataMatrix[0][currCol++] = "1"; for (int i = 0; i < accStds.size(); i++) { CertifiedProductAccessibilityStandard accStd = accStds.get(i); - csvDataMatrix[i][col] = accStd.getAccessibilityStandardName(); + csvDataMatrix[i][currCol] = accStd.getAccessibilityStandardName(); } } } @@ -384,11 +393,11 @@ private void addCqms(String[][] csvDataMatrix, List cqmResults .collect(Collectors.toList()); for (int i = 0; i < attestedCqms.size(); i++) { CQMResultDetails cqm = attestedCqms.get(i); - int col = CQM_START_COL; - csvDataMatrix[i][col++] = cqm.getCmsId(); - csvDataMatrix[i][col++] = + int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? CQM_START_COL_PRE_HTI5 : CQM_START_COL; + csvDataMatrix[i][currCol++] = cqm.getCmsId(); + csvDataMatrix[i][currCol++] = cqm.getSuccessVersions().stream().collect(Collectors.joining(";")); - csvDataMatrix[i][col++] = + csvDataMatrix[i][currCol++] = cqm.getCriteria().stream() .map(crit -> Util.formatCriteriaNumber(crit.getCriterion())) .collect(Collectors.joining(";")); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvHeadingWriter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvHeadingWriter.java index 1b807cb26b..77c753e99a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvHeadingWriter.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/csv/ListingCsvHeadingWriter.java @@ -65,9 +65,17 @@ public List getCsvHeadings(CertifiedProductSearchDetails listing) { HeadingPostHti5.DEVELOPER_EMAIL.getHeading(), HeadingPostHti5.DEVELOPER_PHONE.getHeading(), HeadingPostHti5.DEVELOPER_CONTACT_NAME.getHeading(), - HeadingPostHti5.SVAP_NOTICE_URL.getHeading(), - HeadingPostHti5.RWT_PLANS_URL.getHeading(), - HeadingPostHti5.RWT_PLANS_CHECK_DATE.getHeading(), + HeadingPostHti5.SVAP_NOTICE_URL.getHeading()) + .collect(Collectors.toList()); + + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + headings.addAll(Stream.of( + HeadingPreHti5.RWT_PLANS_URL.getHeading(), + HeadingPreHti5.RWT_PLANS_CHECK_DATE.getHeading()) + .collect(Collectors.toList())); + } + + headings.addAll(Stream.of( HeadingPostHti5.RWT_RESULTS_URL.getHeading(), HeadingPostHti5.RWT_RESULTS_CHECK_DATE.getHeading(), HeadingPostHti5.TARGETED_USERS.getHeading(), @@ -81,8 +89,8 @@ public List getCsvHeadings(CertifiedProductSearchDetails listing) { HeadingPostHti5.K_1_URL.getHeading(), HeadingPostHti5.CQM_NUMBER.getHeading(), HeadingPostHti5.CQM_VERSION.getHeading(), - HeadingPostHti5.CQM_CRITERIA.getHeading() - ).collect(Collectors.toList()); + HeadingPostHti5.CQM_CRITERIA.getHeading()) + .collect(Collectors.toList())); if (!ff4j.check(FeatureList.HTI_5_ERD)) { headings.addAll(Stream.of( diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/manager/ChangeRequestManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/manager/ChangeRequestManager.java index ed5074a5f9..52c91604db 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/manager/ChangeRequestManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/manager/ChangeRequestManager.java @@ -165,7 +165,8 @@ public List getChangeRequestTypes() { || (type.getName().equals(ChangeRequestType.SBUL_TYPE) && ff4j.check(FeatureList.SERVICE_BASE_URL_LIST_CHANGE_REQUEST)) || (type.getName().equals(ChangeRequestType.RWT_PLANS_TYPE) - && ff4j.check(FeatureList.RWT_CHANGE_REQUEST)) + && ff4j.check(FeatureList.RWT_CHANGE_REQUEST) + && !ff4j.check(FeatureList.HTI_5_ERD)) || (type.getName().equals(ChangeRequestType.RWT_RESULTS_TYPE) && ff4j.check(FeatureList.RWT_CHANGE_REQUEST)) ) @@ -190,6 +191,10 @@ public ChangeRequest createChangeRequest(ChangeRequest changeRequest) if (!ff4j.check(FeatureList.SERVICE_BASE_URL_LIST_CHANGE_REQUEST) && changeRequest.getChangeRequestType().isSbul()) { throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.serviceBaseUrlList.featureDisabled")); + } else if (ff4j.check(FeatureList.RWT_CHANGE_REQUEST) + && ff4j.check(FeatureList.HTI_5_ERD) + && changeRequest.getChangeRequestType().isRwtPlans()) { + throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.rwtPlans.notAllowed")); } else if (!ff4j.check(FeatureList.RWT_CHANGE_REQUEST) && isRwtChangeRequestType(changeRequest.getChangeRequestType())) { throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.rwtUrl.featureDisabled")); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CertifiedProductDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CertifiedProductDAO.java index 09cb8cdd22..ce3ee3a85a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CertifiedProductDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CertifiedProductDAO.java @@ -80,8 +80,10 @@ public Long create(CertifiedProductSearchDetails listing) throws EntityCreationE entity.setIcs(listing.getIcs() == null || listing.getIcs().getInherits() == null ? Boolean.FALSE : listing.getIcs().getInherits()); entity.setAccessibilityCertified(listing.getAccessibilityCertified()); entity.setSvapNoticeUrl(listing.getSvapNoticeUrl()); - entity.setRwtPlansUrl(listing.getRwtPlansUrl()); - entity.setRwtPlansCheckDate(listing.getRwtPlansCheckDate()); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + entity.setRwtPlansUrl(listing.getRwtPlansUrl()); + entity.setRwtPlansCheckDate(listing.getRwtPlansCheckDate()); + } entity.setRwtResultsUrl(listing.getRwtResultsUrl()); entity.setRwtResultsCheckDate(listing.getRwtResultsCheckDate()); entity.setChplProductNumber(null); @@ -122,8 +124,10 @@ public CertifiedProductDTO update(CertifiedProductDTO dto) throws EntityRetrieva entity.setCertificationBodyId(dto.getCertificationBodyId()); entity.setCertificationEditionId(dto.getCertificationEditionId()); entity.setProductVersionId(dto.getProductVersionId()); - entity.setRwtPlansUrl(dto.getRwtPlansUrl()); - entity.setRwtPlansCheckDate(dto.getRwtPlansCheckDate()); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + entity.setRwtPlansUrl(dto.getRwtPlansUrl()); + entity.setRwtPlansCheckDate(dto.getRwtPlansCheckDate()); + } entity.setRwtResultsUrl(dto.getRwtResultsUrl()); entity.setRwtResultsCheckDate(dto.getRwtResultsCheckDate()); entity.setSvapNoticeUrl(dto.getSvapNoticeUrl()); @@ -623,7 +627,9 @@ public List getSummaryByUrl(final String url, final queryStr += " AND cp.sedReportFileLocation = :url "; break; case REAL_WORLD_TESTING_PLANS: - queryStr += " AND cp.rwtPlansUrl = :url "; + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + queryStr += " AND cp.rwtPlansUrl = :url "; + } break; case REAL_WORLD_TESTING_RESULTS: queryStr += " AND cp.rwtResultsUrl = :url "; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchBasicDetails.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchBasicDetails.java index d3d6c1b842..8671838c18 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchBasicDetails.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchBasicDetails.java @@ -12,11 +12,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; import gov.healthit.chpl.domain.compliance.DirectReview; import gov.healthit.chpl.domain.surveillance.Surveillance; +import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData; import gov.healthit.chpl.sed.CertifiedProductSed; import gov.healthit.chpl.targeteduser.CertifiedProductTargetedUser; import gov.healthit.chpl.util.DateUtil; @@ -26,6 +25,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; @Data @Builder @@ -192,9 +193,11 @@ public class CertifiedProductSearchBasicDetails implements Serializable { @Schema(description = "Indicates whether the direct reviews were available when the call was made") private boolean directReviewsAvailable; + @DeprecatedRwtPlansData @Schema(description = "URL where the listing's Real World Testing Plan is located") private String rwtPlansUrl; + @DeprecatedRwtPlansData @Schema(description = "Date the listing's Real World Testing Plan was submitted") @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java index 0e3b508d56..c148981ecf 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java @@ -27,6 +27,7 @@ import gov.healthit.chpl.domain.compliance.DirectReview; import gov.healthit.chpl.domain.surveillance.Surveillance; import gov.healthit.chpl.entity.CertificationStatusType; +import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData; import gov.healthit.chpl.sed.CertifiedProductSed; import gov.healthit.chpl.sed.DeprecatedSedSummaryData; import gov.healthit.chpl.targeteduser.CertifiedProductTargetedUser; @@ -250,9 +251,11 @@ public class CertifiedProductSearchDetails implements Serializable { @Schema(description = "All data related to safety-enhanced design for this listing.") private CertifiedProductSed sed = new CertifiedProductSed(); + @DeprecatedRwtPlansData @Schema(description = "URL where the listings Real World Testing Plan is located") private String rwtPlansUrl; + @DeprecatedRwtPlansData @Schema(description = "Date the listings Real World Testing Plan was submitted") @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/service/ListingQuestionableActivityService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/service/ListingQuestionableActivityService.java index b9bf8e9d81..ea1a684736 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/service/ListingQuestionableActivityService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/service/ListingQuestionableActivityService.java @@ -4,10 +4,12 @@ import java.util.List; import java.util.Optional; +import org.ff4j.FF4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.dao.CertifiedProductDAO; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import gov.healthit.chpl.dto.ActivityDTO; @@ -49,16 +51,19 @@ public class ListingQuestionableActivityService { private List listingActivities; private CertifiedProductDAO certifiedProductDAO; private Environment env; + private FF4j ff4j; private List triggerTypes; @Autowired ListingQuestionableActivityService(QuestionableActivityDAO questionableActivityDao, List listingActivities, - CertifiedProductDAO certifiedProductDAO, Environment env) { + CertifiedProductDAO certifiedProductDAO, Environment env, + FF4j ff4j) { this.questionableActivityDao = questionableActivityDao; this.listingActivities = listingActivities; this.certifiedProductDAO = certifiedProductDAO; this.env = env; + this.ff4j = ff4j; triggerTypes = questionableActivityDao.getAllTriggers(); } @@ -73,14 +78,20 @@ public void processQuestionableActivity(CertifiedProductSearchDetails origListin processListingActivity(UpdatedCertificationStatusHistoryActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(FutureCertificationStatusAddedActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(UpdatedTestingLabActivity.class.getName(), origListing, newListing, activity, activityReason); - processListingActivity(DeletedRwtPlanActivity.class.getName(), origListing, newListing, activity, activityReason); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + processListingActivity(DeletedRwtPlanActivity.class.getName(), origListing, newListing, activity, activityReason); + } processListingActivity(DeletedRwtResultsActivity.class.getName(), origListing, newListing, activity, activityReason); - processListingActivity(AddedRwtPlanNonEligibleListingActivity.class.getName(), origListing, newListing, activity, activityReason); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + processListingActivity(AddedRwtPlanNonEligibleListingActivity.class.getName(), origListing, newListing, activity, activityReason); + } processListingActivity(AddedRwtResultsNonEligibleListingActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(UpdatedPromotingInteroperabilityActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(DeletedMeasuresActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(RwtResultsUpdatedOutsideNormalPeriod.class.getName(), origListing, newListing, activity, activityReason); - processListingActivity(RwtPlansUpdatedOutsideNormalPeriod.class.getName(), origListing, newListing, activity, activityReason); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + processListingActivity(RwtPlansUpdatedOutsideNormalPeriod.class.getName(), origListing, newListing, activity, activityReason); + } processListingActivity(AttestRemovedCriteriaActivity.class.getName(), origListing, newListing, activity, activityReason); processListingActivity(NonActiveCertificateEdited.class.getName(), origListing, newListing, activity, activityReason); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/DeprecatedRwtPlansData.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/DeprecatedRwtPlansData.java new file mode 100644 index 0000000000..3d9aa2365f --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/DeprecatedRwtPlansData.java @@ -0,0 +1,12 @@ +package gov.healthit.chpl.realworldtesting; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DeprecatedRwtPlansData { + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java index 5072e08ee1..475f3c0fe2 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java @@ -1,17 +1,13 @@ package gov.healthit.chpl.realworldtesting.domain; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; - -import gov.healthit.chpl.util.DateUtil; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data @Builder @@ -34,57 +30,16 @@ public class RealWorldTestingReport { private LocalDate rwtResultsCheckDate; private String rwtPlansMessage; private String rwtResultsMessage; - private Boolean attestsG7; - private Boolean attestsG9; - private Boolean attestsG10; - - public List toListOfStrings() { - List results = new ArrayList(); - results.add(acbName); - results.add(chplProductNumber); - results.add(currentStatus); - results.add(DateUtil.format(certificationDate)); - results.add(productName); - results.add(productId == null ? null : productId.toString()); - results.add(developerName); - results.add(developerId == null ? null : developerId.toString()); - results.add(!CollectionUtils.isEmpty(developerUsers) ? developerUsers.stream().collect(Collectors.joining("; ")) : ""); - results.add(rwtEligibilityYear == null ? null : rwtEligibilityYear.toString()); - results.add(BooleanUtils.isTrue(ics) ? "Yes" : ""); - results.add(rwtPlansUrl); - results.add(rwtPlansCheckDate == null ? null : rwtPlansCheckDate.toString()); - results.add(rwtResultsUrl); - results.add(rwtResultsCheckDate == null ? null : rwtResultsCheckDate.toString()); - results.add(rwtPlansMessage); - results.add(rwtResultsMessage); - results.add(BooleanUtils.isTrue(attestsG7) ? "TRUE" : "FALSE"); - results.add(BooleanUtils.isTrue(attestsG9) ? "TRUE" : "FALSE"); - results.add(BooleanUtils.isTrue(attestsG10) ? "TRUE" : "FALSE"); - return results; - } + private List criterionAndSvapData; - public static List getHeaders() { - List results = new ArrayList(); - results.add("ONC-ACB Name"); - results.add("CHPL ID"); - results.add("Current Status"); - results.add("Certification Date"); - results.add("Product Name"); - results.add("Product DBID"); - results.add("Developer Name"); - results.add("Developer DBID"); - results.add("Developer Users"); - results.add("Initial RWT Year"); - results.add("ICS"); - results.add("RWT Plans URL"); - results.add("RWT Plans Submission Confirmed"); - results.add("RWT Results URL"); - results.add("RWT Results Submission Confirmed"); - results.add("RWT Plans Message"); - results.add("RWT Results Message"); - results.add("170.315 (g)(7)"); - results.add("170.315 (g)(9)"); - results.add("170.315 (g)(10)"); - return results; + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class CriterionAndSvapData { + private CertificationCriterion criterion; + private boolean isAttested; + private boolean isGCriterion; + private boolean usesSvap; } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManager.java index 780a34a517..01a168142f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManager.java @@ -14,6 +14,7 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.StringUtils; +import org.ff4j.FF4j; import org.quartz.JobDataMap; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.auth.user.JWTAuthenticatedUser; import gov.healthit.chpl.domain.schedule.ChplJob; import gov.healthit.chpl.domain.schedule.ChplOneTimeTrigger; @@ -51,15 +53,18 @@ public class RealWorldTestingManager { private RealWorldTestingByDeveloperDao rwtByDeveloperDao; private SchedulerManager schedulerManager; private ErrorMessageUtil errorMessageUtil; + private FF4j ff4j; @Autowired public RealWorldTestingManager(RealWorldTestingByDeveloperDao rwtByDeveloperDao, SchedulerManager schedulerManager, - ErrorMessageUtil errorMessageUtil) { + ErrorMessageUtil errorMessageUtil, + FF4j ff4j) { this.rwtByDeveloperDao = rwtByDeveloperDao; this.schedulerManager = schedulerManager; this.errorMessageUtil = errorMessageUtil; + this.ff4j = ff4j; } public List getPlansUrls(Long developerId) { @@ -160,6 +165,13 @@ private List parseCsvFile(MultipartFile file) throws Val .map(rec -> createRwtUploadFromCsvRecord(rec)) .collect(Collectors.toList()); + //only keep RESULTS upload records after HTI-5 + if (ff4j.check(FeatureList.HTI_5_ERD)) { + rwts = rwts.stream() + .filter(rwt -> rwt.getType().equals(RealWorldTestingType.RESULTS)) + .collect(Collectors.toList()); + } + return rwts; } catch (final IOException ioEx) { throw new ValidationException("Could not get input stream for uploaded file " + file.getName()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java index f56c77c553..a10d3cec91 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java @@ -14,9 +14,9 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; -import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.certifiedproduct.service.CertificationStatusEventsService; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertificationStatusEvent; import gov.healthit.chpl.domain.Developer; import gov.healthit.chpl.domain.auth.User; @@ -25,8 +25,8 @@ import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.permissions.ResourcePermissionsFactory; import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport.CriterionAndSvapData; import gov.healthit.chpl.service.CertificationCriterionService; -import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligibility; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligiblityReason; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligiblityService; @@ -41,23 +41,23 @@ public class RealWorldTestingReportService { private ErrorMessageUtil errorMsg; private Environment env; private CertificationStatusEventsService certificationStatusEventsService; + private CertificationCriterionService criteriaService; private RealWorldTestingEligiblityServiceFactory rwtEligServiceFactory; private ResourcePermissionsFactory resourcePermissionsFactory; private List withdrawnStatuses; - private CertificationCriterion g7, g9, g10; @Autowired public RealWorldTestingReportService(CertifiedProductDAO certifiedProductDAO, ErrorMessageUtil errorMsg, Environment env, CertificationStatusEventsService certificationStatusEventsService, CertificationCriterionService criteriaService, - CertificationCriterionService certificationCriterionService, RealWorldTestingEligiblityServiceFactory rwtEligServiceFactory, ResourcePermissionsFactory resourcePermissionsFactory) { this.certifiedProductDAO = certifiedProductDAO; this.errorMsg = errorMsg; this.env = env; + this.criteriaService = criteriaService; this.certificationStatusEventsService = certificationStatusEventsService; this.rwtEligServiceFactory = rwtEligServiceFactory; this.resourcePermissionsFactory = resourcePermissionsFactory; @@ -67,9 +67,6 @@ public RealWorldTestingReportService(CertifiedProductDAO certifiedProductDAO, CertificationStatusType.WithdrawnByDeveloperUnderReview, CertificationStatusType.Retired, CertificationStatusType.TerminatedByOnc); - g7 = criteriaService.get(Criteria2015.G_7); - g9 = criteriaService.get(Criteria2015.G_9_CURES); - g10 = criteriaService.get(Criteria2015.G_10); } public List getRealWorldTestingReports(List acbIds, Logger logger) { @@ -146,9 +143,7 @@ private RealWorldTestingReport getRealWorldTestingReport(CertifiedProductDetails .rwtPlansCheckDate(listing.getRwtPlansCheckDate()) .rwtResultsUrl(listing.getRwtResultsUrl()) .rwtResultsCheckDate(listing.getRwtResultsCheckDate()) - .attestsG7(attestsCriteria(rwtElig, g7)) - .attestsG9(attestsCriteria(rwtElig, g9)) - .attestsG10(attestsCriteria(rwtElig, g10)) + .criterionAndSvapData(buildCriterionAndSvapData(rwtElig.getAttestedCertificationResults())) .build(); if (rwtElig.getEligibilityYear() != null) { @@ -158,11 +153,15 @@ private RealWorldTestingReport getRealWorldTestingReport(CertifiedProductDetails } } - private boolean attestsCriteria(RealWorldTestingEligibility eligibility, CertificationCriterion criterion) { - return eligibility.getAttestedCriteria().stream() - .filter(crit -> crit.getId().equals(criterion.getId())) - .findAny() - .isPresent(); + private List buildCriterionAndSvapData(List certResults) { + return certResults.stream() + .map(certResult -> CriterionAndSvapData.builder() + .criterion(certResult.getCriterion()) + .isAttested(certResult.getSuccess()) + .isGCriterion(criteriaService.isGCriterion(certResult.getCriterion())) + .usesSvap(!CollectionUtils.isEmpty(certResult.getSvaps())) + .build()) + .collect(Collectors.toList()); } private List getDeveloperUsers(Developer developer) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableurl/QuestionableUrlReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableurl/QuestionableUrlReportDao.java index 3fc9ca4a0d..4d9caffbe3 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableurl/QuestionableUrlReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableurl/QuestionableUrlReportDao.java @@ -71,7 +71,7 @@ private boolean isForListingUrl(QuestionableUrlDetailReport report) { || report.getUrlType().equals(UrlType.EXPORT_DOCUMENTATION.getName()) || (!ff4j.check(FeatureList.HTI_5_ERD) && report.getUrlType().equals(UrlType.FULL_USABILITY_REPORT.getName())) || report.getUrlType().equals(UrlType.MANDATORY_DISCLOSURE.getName()) - || report.getUrlType().equals(UrlType.REAL_WORLD_TESTING_PLANS.getName()) + || (!ff4j.check(FeatureList.HTI_5_ERD) && report.getUrlType().equals(UrlType.REAL_WORLD_TESTING_PLANS.getName())) || report.getUrlType().equals(UrlType.REAL_WORLD_TESTING_RESULTS.getName()) || report.getUrlType().equals(UrlType.RISK_MANAGEMENT_SUMMARY_INFORMATION.getName()) || report.getUrlType().equals(UrlType.SERVICE_BASE_URL_LIST.getName()) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java index a82c19067e..7df789c70f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java @@ -1,10 +1,7 @@ package gov.healthit.chpl.scheduler.job; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -12,8 +9,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; +import org.ff4j.FF4j; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @@ -21,6 +17,7 @@ import org.springframework.core.env.Environment; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.dao.CertificationBodyDAO; import gov.healthit.chpl.email.ChplEmailFactory; import gov.healthit.chpl.email.ChplHtmlEmailBuilder; @@ -32,6 +29,7 @@ import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReportSummary; import gov.healthit.chpl.realworldtesting.manager.RealWorldTestingReportService; import gov.healthit.chpl.scheduler.job.realworldtesting.RealWorldTestingReportSummaryCalculator; +import gov.healthit.chpl.scheduler.presenter.RealWorldTestingCsvPresenter; import gov.healthit.chpl.util.NullSafeEvaluator; import lombok.AllArgsConstructor; import lombok.Getter; @@ -50,12 +48,18 @@ public class RealWorldTestingReportEmailJob implements Job { @Autowired private Environment env; + @Autowired + private RealWorldTestingCsvPresenter csvPresenter; + @Autowired private ChplHtmlEmailBuilder chplHtmlEmailBuilder; @Autowired private ChplEmailFactory chplEmailFactory; + @Autowired + private FF4j ff4j; + private List acbIds = new ArrayList(); @Override @@ -103,7 +107,7 @@ private String createHtmlMessage(JobExecutionContext context, List rows) { LOGGER.info("Generating CSV attachment"); File outputFile = getOutputFile(env.getProperty("rwt.report.filename") + LocalDate.now().toString()); - outputFile = writeToFile(rows, outputFile); + writeToFile(rows, outputFile); LOGGER.info("Completed Generating CSV attachment"); return outputFile; } @@ -120,25 +124,8 @@ private File getOutputFile(String reportFilename) { return temp; } - private File writeToFile(List rows, File outputFile) { - try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outputFile), - Charset.forName("UTF-8").newEncoder()); - CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.EXCEL)) { - writer.write('\ufeff'); - csvPrinter.printRecord(RealWorldTestingReport.getHeaders()); - rows.stream() - .forEach(row -> { - try { - csvPrinter.printRecord(row.toListOfStrings()); - } catch (Exception e) { - LOGGER.error(e); - } - - }); - } catch (Exception e) { - LOGGER.error(e); - } - return outputFile; + private void writeToFile(List rows, File outputFile) { + csvPresenter.presentAsFile(rows, outputFile); } private String getAcbNamesAsBrSeparatedList(JobExecutionContext jobContext) { @@ -169,13 +156,15 @@ private String getEmailSummaryParagraph(List rows) { RealWorldTestingReportSummary summary = RealWorldTestingReportSummaryCalculator.calculateSummariesByEligibityYear(rows, year); paragraph.append("Real World Testing for ").append(summary.getRwtEligibilityYear()).append(":
") .append("
    ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalListings(), 0)).append(" Total listing for this period
  • ") + .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalListings(), 0)).append(" Total listing(s) for this period
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithdrawn(), 0)).append(" Withdrawn, no longer eligible
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalActive(), 0)).append(" Active
  • ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalEligibleViaIcs(), 0)).append(" Eligible via ICS
  • ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithPlansUrl(), 0)).append(" Have RWT plans URL
  • ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithPlansUrlValidated(), 0)).append(" RWT plans validated
  • ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithResultsUrl(), 0)).append(" Have RWT results URL
  • ") + .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalEligibleViaIcs(), 0)).append(" Eligible via ICS
  • "); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + paragraph.append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithPlansUrl(), 0)).append(" Have RWT plans URL
  • ") + .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithPlansUrlValidated(), 0)).append(" RWT plans validated
  • "); + } + paragraph.append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithResultsUrl(), 0)).append(" Have RWT results URL
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithResultsUrlValidated(), 0)).append(" RWT results validated
  • ") .append("
").append("
"); }); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java index 60be011a8f..5e0ae137d0 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java @@ -8,6 +8,7 @@ import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; +import org.ff4j.FF4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,8 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import gov.healthit.chpl.FeatureList; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.developer.search.ActiveListingSearchOptions; import gov.healthit.chpl.developer.search.DeveloperSearchRequest; import gov.healthit.chpl.developer.search.DeveloperSearchResult; @@ -30,6 +33,8 @@ import gov.healthit.chpl.report.realworldtesting.RealWorldTestingResultsSummaryReportDao; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryByAcbReport; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryByDeveloperReport; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; @@ -54,10 +59,22 @@ public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { @Autowired private PlatformTransactionManager transactionManager; + @Autowired + private FF4j ff4j; + + @Autowired + private CertificationCriterionService criteriaService; + + private CertificationCriterion g7, g9, g10; + @Override public void execute(JobExecutionContext context) throws JobExecutionException { SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); LOGGER.info("********* Starting the Real World Testing Summary Report Creator job *********"); + g7 = criteriaService.get(Criteria2015.G_7); + g9 = criteriaService.get(Criteria2015.G_9_CURES); + g10 = criteriaService.get(Criteria2015.G_10); + try { List activeAcbIds = certificationBodyManager.getAllActive().stream() .map(acb -> acb.getId()) @@ -66,9 +83,8 @@ public void execute(JobExecutionContext context) throws JobExecutionException { List rwtPlansReports = rwtReportService.getRealWorldTestingReports(activeAcbIds, LOGGER).stream() .collect(Collectors.toList()); - //RWT Results requirement is only enforced for listings with g7, g9, or g10 so we only want to report on those List rwtResultReports = rwtPlansReports.stream() - .filter(report -> report.getAttestsG7() || report.getAttestsG9() || report.getAttestsG10()) + .filter(report -> isRwtResultsRequired(report)) .collect(Collectors.toList()); TransactionOperations transactionOperations = new TransactionTemplate(transactionManager, @@ -86,6 +102,24 @@ public void execute(JobExecutionContext context) throws JobExecutionException { } + private boolean isRwtResultsRequired(RealWorldTestingReport rwtReport) { + //RWT Results requirement is only enforced for listings with g7, g9, or g10 pre HTI-5. + //Post HTI-5 listings with any g-criteria or non-g-criteria using svap have the RWT Results requirement. + if (ff4j.check(FeatureList.HTI_5_ERD)) { + return rwtReport.getCriterionAndSvapData().stream() + .filter(item -> item.isAttested() && (item.isGCriterion() || item.isUsesSvap())) + .findAny() + .isPresent(); + } else { + return rwtReport.getCriterionAndSvapData().stream() + .filter(item -> item.getCriterion().getId().equals(g7.getId()) + || item.getCriterion().getId().equals(g9.getId()) + || item.getCriterion().getId().equals(g10.getId())) + .findAny() + .isPresent(); + } + } + private void processRwtResultsCountsByAcb(List reportRows) { Integer rwtEligibilityYear = LocalDate.now().getYear() - 1; @@ -176,6 +210,9 @@ && isListingValidAsOfDate(row.getCertificationDate(), reportDate)) } private void processRwtPlanCountsByAcb(List reportRows) { + if (ff4j.check(FeatureList.HTI_5_ERD)) { + return; + } Integer rwtEligibilityYear = LocalDate.now().getYear() + 1; if (!isDateInPlansSubmissionWindow(LocalDate.now(), rwtEligibilityYear)) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingUploadJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingUploadJob.java index 2e9081a409..01f4c48f50 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingUploadJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingUploadJob.java @@ -14,12 +14,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.ff4j.FF4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.auth.user.JWTAuthenticatedUser; import gov.healthit.chpl.certifiedproduct.CertifiedProductDetailsManager; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; @@ -51,6 +53,9 @@ public class RealWorldTestingUploadJob extends QuartzJob { @Autowired private Environment env; + @Autowired + private FF4j ff4j; + @Autowired private ChplEmailFactory chplEmailFactory; @@ -68,19 +73,23 @@ public void execute(JobExecutionContext context) throws JobExecutionException { // Run some basic validation on upload records that do not have any errors // yet... - rwts.stream().filter(rwt -> rwt.getValidationErrors().size() == 0) - .forEach(rwt -> rwt.getValidationErrors().addAll(validateRwtUpload(rwt))); + rwts.stream() + .filter(rwt -> rwt.getType().equals(RealWorldTestingType.RESULTS) || (!ff4j.check(FeatureList.HTI_5_ERD) && rwt.getType().equals(RealWorldTestingType.PLANS))) + .filter(rwt -> rwt.getValidationErrors().size() == 0) + .forEach(rwt -> rwt.getValidationErrors().addAll(validateRwtUpload(rwt))); // Determine if there multiple Plans or Results for the same listing. These will // not get // get processed, and will have an error added to the upload record. rwts = markMultipleChangesForSamePlanTypeAndListing(rwts); - // Process the plans - List rwtPlans = rwts.stream().filter( - rwt -> rwt.getValidationErrors().size() == 0 && rwt.getType().equals(RealWorldTestingType.PLANS)) - .collect(Collectors.toList()); - saveRealWorldTestingUploads(rwtPlans); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + // Process the plans + List rwtPlans = rwts.stream().filter( + rwt -> rwt.getValidationErrors().size() == 0 && rwt.getType().equals(RealWorldTestingType.PLANS)) + .collect(Collectors.toList()); + saveRealWorldTestingUploads(rwtPlans); + } // Process the results List rwtResults = rwts.stream().filter( @@ -218,13 +227,15 @@ private Integer getThreadCountForJob() throws NumberFormatException { private List markMultipleChangesForSamePlanTypeAndListing( List rwts) { - // Find any listings that have duplicate updates - multiple Plans - rwts.stream() + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + // Find any listings that have duplicate updates - multiple Plans + rwts.stream() .filter(rwt -> rwt.getValidationErrors().size() == 0 && rwt.getType().equals(RealWorldTestingType.PLANS)) .collect(Collectors.groupingBy(RealWorldTestingUpload::getChplProductNumber)).entrySet().stream() .filter(lst -> lst.getValue().size() > 1).forEach(lst -> lst.getValue().stream().forEach( rwt -> rwt.getValidationErrors().add("Multiple Plans found for this CHPL Product Number"))); + } rwts.stream() .filter(rwt -> rwt.getValidationErrors().size() == 0 diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/changerequest/ChangeRequestReportEmailJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/changerequest/ChangeRequestReportEmailJob.java index c8b7fc49c9..d3b95f531e 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/changerequest/ChangeRequestReportEmailJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/changerequest/ChangeRequestReportEmailJob.java @@ -231,7 +231,9 @@ private List getAttachments() { attachments.add(tempSbulFile); } if (ff4j.check(FeatureList.RWT_CHANGE_REQUEST)) { - attachments.add(tempRwtPlansFile); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + attachments.add(tempRwtPlansFile); + } attachments.add(tempRwtResultsFile); } return attachments; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/QuestionableUrlReportGenerator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/QuestionableUrlReportGenerator.java index e710eaab95..c2628b2ea1 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/QuestionableUrlReportGenerator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/QuestionableUrlReportGenerator.java @@ -126,7 +126,17 @@ public void execute(JobExecutionContext jobContext) throws JobExecutionException } break; case MANDATORY_DISCLOSURE: + LOGGER.info("[" + i + "] Getting Listings with bad " + questionableUrlResult.getUrlType().getName() + + " website " + questionableUrlResult.getUrl()); + questionableUrls.addAll(urlLookupDao.getListingsWithUrl(questionableUrlResult)); + break; case REAL_WORLD_TESTING_PLANS: + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + LOGGER.info("[" + i + "] Getting Listings with bad " + questionableUrlResult.getUrlType().getName() + + " website " + questionableUrlResult.getUrl()); + questionableUrls.addAll(urlLookupDao.getListingsWithUrl(questionableUrlResult)); + } + break; case REAL_WORLD_TESTING_RESULTS: case STANDARDS_VERSION_ADVANCEMENT_PROCESS_NOTICE: LOGGER.info("[" + i + "] Getting Listings with bad " + questionableUrlResult.getUrlType().getName() @@ -343,7 +353,9 @@ private String createHtmlEmailBody(List urlResults, JobExecutio brokenUrlSummaryHtml += "
  • " + UrlType.DOCUMENTATION.getName() + ": " + brokenDocumentationUrlUrls + "
  • "; brokenUrlSummaryHtml += "
  • " + UrlType.USE_CASES.getName() + ": " + brokenUseCasesUrls + "
  • "; brokenUrlSummaryHtml += "
  • " + UrlType.RISK_MANAGEMENT_SUMMARY_INFORMATION.getName() + ": " + brokenRiskManagementSummaryInfoUrls + "
  • "; - brokenUrlSummaryHtml += "
  • " + UrlType.REAL_WORLD_TESTING_PLANS.getName() + ": " + brokenRwtPlansUrls + "
  • "; + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + brokenUrlSummaryHtml += "
  • " + UrlType.REAL_WORLD_TESTING_PLANS.getName() + ": " + brokenRwtPlansUrls + "
  • "; + } brokenUrlSummaryHtml += "
  • " + UrlType.REAL_WORLD_TESTING_RESULTS.getName() + ": " + brokenRwtResultsUrls + "
  • "; brokenUrlSummaryHtml += "
  • " + UrlType.STANDARDS_VERSION_ADVANCEMENT_PROCESS_NOTICE.getName() + ": " + brokenSvapNoticeUrls + "
  • "; brokenUrlSummaryHtml += ""; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/UrlStatusDataCollector.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/UrlStatusDataCollector.java index fe8bd66732..c949f24636 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/UrlStatusDataCollector.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urlStatus/UrlStatusDataCollector.java @@ -114,6 +114,7 @@ private List getUrlTypesToExclude() { List exclusions = Stream.of(UrlType.SERVICE_BASE_URL_LIST).collect(Collectors.toList()); if (ff4j.check(FeatureList.HTI_5_ERD)) { exclusions.add(UrlType.FULL_USABILITY_REPORT); + exclusions.add(UrlType.REAL_WORLD_TESTING_PLANS); } return exclusions; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java new file mode 100644 index 0000000000..409dbc3ce4 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java @@ -0,0 +1,150 @@ +package gov.healthit.chpl.scheduler.presenter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.lang3.BooleanUtils; +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.certificationCriteria.CertificationCriterionComparator; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport.CriterionAndSvapData; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; +import gov.healthit.chpl.service.realworldtesting.RealWorldTestingCriteriaService; +import gov.healthit.chpl.util.DateUtil; +import gov.healthit.chpl.util.Util; +import lombok.extern.log4j.Log4j2; + +@Component +@Log4j2(topic = "realWorldTestingReportEmailJobLogger") +public class RealWorldTestingCsvPresenter { + private CertificationCriterionService criteriaService; + private List rwtResultsRequiredCriteria; + private FF4j ff4j; + + @Autowired + public RealWorldTestingCsvPresenter(RealWorldTestingCriteriaService rwtCriteriaService, + CertificationCriterionService criteriaService, + CertificationCriterionComparator criteriaComparator, + FF4j ff4j) { + this.criteriaService = criteriaService; + this.ff4j = ff4j; + Integer currentYear = Calendar.getInstance().get(Calendar.YEAR); + if (ff4j.check(FeatureList.HTI_5_ERD)) { + rwtResultsRequiredCriteria = rwtCriteriaService.getEligibleCriteria(currentYear); + } else { + rwtResultsRequiredCriteria = Stream.of(criteriaService.get(Criteria2015.G_7), + criteriaService.get(Criteria2015.G_9_CURES), + criteriaService.get(Criteria2015.G_10)).collect(Collectors.toList()); + } + rwtResultsRequiredCriteria.stream().sorted(criteriaComparator); + } + + public void presentAsFile(List rwtReports, File outputFile) { + try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outputFile), + Charset.forName("UTF-8").newEncoder()); + CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.EXCEL)) { + writer.write('\ufeff'); + csvPrinter.printRecord(generateHeaderValues()); + for (RealWorldTestingReport rwtReport : rwtReports) { + csvPrinter.printRecord(generateRowValue(rwtReport)); + } + } catch (IOException ex) { + LOGGER.error("Could not write file " + outputFile.getName(), ex); + } + } + + protected List generateHeaderValues() { + List results = new ArrayList(); + results.add("ONC-ACB Name"); + results.add("CHPL ID"); + results.add("Current Status"); + results.add("Certification Date"); + results.add("Product Name"); + results.add("Product DBID"); + results.add("Developer Name"); + results.add("Developer DBID"); + results.add("Developer Users"); + results.add("Initial RWT Year"); + results.add("ICS"); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + results.add("RWT Plans URL"); + results.add("RWT Plans Submission Confirmed"); + } + results.add("RWT Results URL"); + results.add("RWT Results Submission Confirmed"); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + results.add("RWT Plans Message"); + } + results.add("RWT Results Message"); + rwtResultsRequiredCriteria.stream() + .forEach(criterion -> { + if (criteriaService.isGCriterion(criterion)) { + results.add(Util.formatCriteriaNumber(criterion)); + } else { + results.add(Util.formatCriteriaNumber(criterion) + "SVAP"); + } + }); + return results; + } + + protected List generateRowValue(RealWorldTestingReport rwtReport) { + List results = new ArrayList(); + results.add(rwtReport.getAcbName()); + results.add(rwtReport.getChplProductNumber()); + results.add(rwtReport.getCurrentStatus()); + results.add(DateUtil.format(rwtReport.getCertificationDate())); + results.add(rwtReport.getProductName()); + results.add(rwtReport.getProductId() == null ? null : rwtReport.getProductId().toString()); + results.add(rwtReport.getDeveloperName()); + results.add(rwtReport.getDeveloperId() == null ? null : rwtReport.getDeveloperId().toString()); + results.add(!CollectionUtils.isEmpty(rwtReport.getDeveloperUsers()) + ? rwtReport.getDeveloperUsers().stream().collect(Collectors.joining("; ")) + : ""); + results.add(rwtReport.getRwtEligibilityYear() == null ? null : rwtReport.getRwtEligibilityYear().toString()); + results.add(BooleanUtils.isTrue(rwtReport.getIcs()) ? "Yes" : ""); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + results.add(rwtReport.getRwtPlansUrl()); + results.add(rwtReport.getRwtPlansCheckDate() == null ? null : rwtReport.getRwtPlansCheckDate().toString()); + } + results.add(rwtReport.getRwtResultsUrl()); + results.add(rwtReport.getRwtResultsCheckDate() == null ? null : rwtReport.getRwtResultsCheckDate().toString()); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + results.add(rwtReport.getRwtPlansMessage()); + } + results.add(rwtReport.getRwtResultsMessage()); + rwtResultsRequiredCriteria.stream() + .forEach(criterion -> results.add(determineCriteriaReportValue(rwtReport.getCriterionAndSvapData(), criterion))); + return results; + } + + private String determineCriteriaReportValue(List criteriaAndSvapData, CertificationCriterion criterion) { + CriterionAndSvapData criterionAndSvapData = criteriaAndSvapData.stream() + .filter(item -> item.getCriterion().getId().equals(criterion.getId())) + .findAny() + .orElse(null); + if (criterionAndSvapData == null) { + return "FALSE"; + } else if (criterionAndSvapData.isGCriterion()) { + return criterionAndSvapData.isAttested() ? "TRUE" : "FALSE"; + } else { + return criterionAndSvapData.isAttested() && criterionAndSvapData.isUsesSvap() ? "TRUE" : "FALSE"; + } + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java index 49c2a274e5..897cf8711f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java @@ -15,6 +15,7 @@ import gov.healthit.chpl.domain.CertificationEdition; import gov.healthit.chpl.domain.IdNamePair; +import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData; import gov.healthit.chpl.util.CertificationStatusUtil; import gov.healthit.chpl.util.LocalDateDeserializer; import gov.healthit.chpl.util.LocalDateSerializer; @@ -80,6 +81,7 @@ public class ListingSearchResult implements Serializable { private CertificationCriterionSearchResultWithStringField serviceBaseUrlList; private CertificationCriterionSearchResultWithStringField riskManagementSummaryInformation; private Set svaps; + @DeprecatedRwtPlansData private String rwtPlansUrl; private String rwtResultsUrl; private String svapNoticeUrl; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java index abf0a30c15..e564f90f13 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java @@ -331,6 +331,27 @@ private List getReferenceSortingCriteriaList() { .collect(Collectors.toList()); } + public boolean isGCriterion(CertificationCriterion criterion) { + if (criterion.getId().equals(get(Criteria2015.G_1).getId()) + || criterion.getId().equals(get(Criteria2015.G_2).getId()) + || criterion.getId().equals(get(Criteria2015.G_3).getId()) + || criterion.getId().equals(get(Criteria2015.G_4).getId()) + || criterion.getId().equals(get(Criteria2015.G_5).getId()) + || criterion.getId().equals(get(Criteria2015.G_6_OLD).getId()) + || criterion.getId().equals(get(Criteria2015.G_6_CURES).getId()) + || criterion.getId().equals(get(Criteria2015.G_7).getId()) + || criterion.getId().equals(get(Criteria2015.G_8).getId()) + || criterion.getId().equals(get(Criteria2015.G_9_OLD).getId()) + || criterion.getId().equals(get(Criteria2015.G_9_CURES).getId()) + || criterion.getId().equals(get(Criteria2015.G_10).getId()) + || criterion.getId().equals(get(Criteria2015.G_31).getId()) + || criterion.getId().equals(get(Criteria2015.G_32).getId()) + || criterion.getId().equals(get(Criteria2015.G_33).getId())) { + return true; + } + return false; + } + public static class Criteria2015 { public static final String A_1 = "criterion.170_315_a_1"; public static final String A_2 = "criterion.170_315_a_2"; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java index 5d1557bc26..ac4ffcac0c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java @@ -50,6 +50,7 @@ private Integer getYearOrMostRecentPastYear(Integer requestedYear) throws Invali private List getRwtEligibleCriteria(List eligibleCriteriaKeys) { return eligibleCriteriaKeys.stream() .map(key -> certificationCriterionService.get(key)) + .filter(criterion -> !criterion.isRemoved()) .collect(Collectors.toList()); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java index 339ec019ec..619e23f03d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java @@ -3,7 +3,7 @@ import java.io.Serializable; import java.util.List; -import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.domain.CertificationResult; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -18,5 +18,5 @@ public class RealWorldTestingEligibility implements Serializable { private RealWorldTestingEligiblityReason reason; private Integer eligibilityYear; - private List attestedCriteria; + private List attestedCertificationResults; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java index 0781940b55..de5e83f9ff 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java @@ -11,20 +11,25 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.logging.log4j.Logger; +import org.ff4j.FF4j; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.activity.history.ListingActivityUtil; import gov.healthit.chpl.activity.history.explorer.RealWorldTestingEligibilityActivityExplorer; import gov.healthit.chpl.activity.history.query.RealWorldTestingEligibilityQuery; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertificationStatusEvent; import gov.healthit.chpl.domain.CertifiedProduct; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import gov.healthit.chpl.dto.ActivityDTO; import gov.healthit.chpl.dto.CertifiedProductDTO; import gov.healthit.chpl.exception.EntityRetrievalException; +import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.util.CertificationStatusUtil; import gov.healthit.chpl.util.DateUtil; import lombok.AllArgsConstructor; @@ -41,19 +46,27 @@ public class RealWorldTestingEligiblityService { private RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer; private ListingActivityUtil listingActivityUtil; private CertifiedProductDAO certifiedProductDAO; + private CertificationCriterionService criteriaService; + private FF4j ff4j; private Map memo = new HashMap(); public RealWorldTestingEligiblityService(RealWorldTestingCriteriaService realWorldTestingCriteriaService, RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer, ListingActivityUtil listingActivityUtil, - CertifiedProductDAO certifiedProductDAO, LocalDate rwtProgramStartDate, Integer rwtProgramFirstEligibilityYear) { + CertifiedProductDAO certifiedProductDAO, + LocalDate rwtProgramStartDate, + Integer rwtProgramFirstEligibilityYear, + CertificationCriterionService criteriaService, + FF4j ff4j) { this.realWorldTestingCriteriaService = realWorldTestingCriteriaService; this.realWorldTestingEligibilityActivityExplorer = realWorldTestingEligibilityActivityExplorer; this.listingActivityUtil = listingActivityUtil; this.certifiedProductDAO = certifiedProductDAO; this.rwtProgramStartDate = rwtProgramStartDate; this.rwtProgramFirstEligibilityYear = rwtProgramFirstEligibilityYear; + this.criteriaService = criteriaService; + this.ff4j = ff4j; } public RealWorldTestingEligibility getRwtEligibilityYearForListing(Long listingId, Logger logger) { @@ -113,9 +126,9 @@ private Optional getRwtEligBasedOnStandardRequireme private RealWorldTestingEligibility getRwtEligibility(Optional listing, RealWorldTestingEligiblityReason reason, Integer currentRwtEligYear) { - List attestedCriteria = new ArrayList(); + List attestedCertificationResults = new ArrayList(); if (listing != null && listing.isPresent()) { - attestedCriteria = listing.get().getCertificationResults().stream() + attestedCertificationResults = listing.get().getCertificationResults().stream() //We might be getting this listing in it's original state from saved JSON. //For most of CHPL before mid-2023, we saved a certification result on the listing for each criteria //and used the "success" field to determine if that listing attested to that criterion. @@ -123,13 +136,12 @@ private RealWorldTestingEligibility getRwtEligibility(Optional BooleanUtils.isTrue(certResult.getSuccess())) - .map(certResult -> certResult.getCriterion()) .collect(Collectors.toList()); } return RealWorldTestingEligibility.builder() .reason(reason) .eligibilityYear(currentRwtEligYear) - .attestedCriteria(attestedCriteria) + .attestedCertificationResults(attestedCertificationResults) .build(); } @@ -207,7 +219,6 @@ private boolean isListingRwtEligible(CertifiedProductSearchDetails listing, Loca return isListingStatusActiveAsOfEligibilityDate(listing, asOfDate) && isCertificationDateBeforeEligibilityDate(listing, asOfDate) && doesListingAttestToEligibleCriteria(listing, asOfDate.getYear()); - } private boolean doesListingAttestToEligibleCriteria(CertifiedProductSearchDetails listing, Integer year) { @@ -215,13 +226,20 @@ private boolean doesListingAttestToEligibleCriteria(CertifiedProductSearchDetail return listing.getCertificationResults().stream() .filter(result -> result.getSuccess() && eligibleCriteria.stream() - .filter(crit -> crit.getId().equals(result.getCriterion().getId())) + .filter(crit -> crit.getId().equals(result.getCriterion().getId()) && isGCriteriaOrUsesSvap(result)) .findAny() .isPresent()) .findAny() .isPresent(); } + private boolean isGCriteriaOrUsesSvap(CertificationResult certResult) { + if (ff4j.check(FeatureList.HTI_5_ERD)) { + return criteriaService.isGCriterion(certResult.getCriterion()) || !CollectionUtils.isEmpty(certResult.getSvaps()); + } + return true; + } + private boolean isCertificationDateBeforeEligibilityDate(CertifiedProductSearchDetails listing, LocalDate eligibilityDate) { if (Objects.isNull(listing) || Objects.isNull(listing.getCertificationDate())) { return false; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java index 28a9160c99..dc40a67988 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java @@ -2,6 +2,7 @@ import java.time.LocalDate; +import org.ff4j.FF4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -9,6 +10,7 @@ import gov.healthit.chpl.activity.history.ListingActivityUtil; import gov.healthit.chpl.activity.history.explorer.RealWorldTestingEligibilityActivityExplorer; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.service.CertificationCriterionService; @Component public class RealWorldTestingEligiblityServiceFactory { @@ -19,24 +21,31 @@ public class RealWorldTestingEligiblityServiceFactory { @Value("#{T(java.time.LocalDate).parse('${rwtProgramStartDate}')}") private LocalDate rwtProgramStartDate; + private CertificationCriterionService criteriaService; private RealWorldTestingCriteriaService realWorldTestingCriteriaService; private RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer; private ListingActivityUtil listingActivityUtil; private CertifiedProductDAO certifiedProductDAO; + private FF4j ff4j; @Autowired - public RealWorldTestingEligiblityServiceFactory(RealWorldTestingCriteriaService realWorldTestingCriteriaService, + public RealWorldTestingEligiblityServiceFactory(CertificationCriterionService criteriaService, + RealWorldTestingCriteriaService realWorldTestingCriteriaService, RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer, ListingActivityUtil listingActivityUtil, - CertifiedProductDAO certifiedProductDAO) { + CertifiedProductDAO certifiedProductDAO, + FF4j ff4j) { + this.criteriaService = criteriaService; this.realWorldTestingCriteriaService = realWorldTestingCriteriaService; this.realWorldTestingEligibilityActivityExplorer = realWorldTestingEligibilityActivityExplorer; this.listingActivityUtil = listingActivityUtil; this.certifiedProductDAO = certifiedProductDAO; + this.ff4j = ff4j; } public RealWorldTestingEligiblityService getInstance() { return new RealWorldTestingEligiblityService(realWorldTestingCriteriaService, realWorldTestingEligibilityActivityExplorer, - listingActivityUtil, certifiedProductDAO, rwtProgramStartDate, rwtProgramFirstEligibilityYear); + listingActivityUtil, certifiedProductDAO, rwtProgramStartDate, rwtProgramFirstEligibilityYear, + criteriaService, ff4j); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/SubscriptionObserver.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/SubscriptionObserver.java index 1fee8372bf..872664f389 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/SubscriptionObserver.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/SubscriptionObserver.java @@ -5,9 +5,11 @@ import java.util.Optional; import org.apache.commons.collections4.CollectionUtils; +import org.ff4j.FF4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.dto.ActivityDTO; import gov.healthit.chpl.subscription.dao.SubscriptionDao; import gov.healthit.chpl.subscription.dao.SubscriptionObservationDao; @@ -28,6 +30,7 @@ public class SubscriptionObserver { private SubscriptionDao subscriptionDao; private SubscriptionObservationDao observationDao; private SubscriptionLookupUtil lookupUtil; + private FF4j ff4j; private List processors; private List allSubjects; @@ -35,10 +38,12 @@ public class SubscriptionObserver { @Autowired public SubscriptionObserver(SubscriptionDao subscriptionDao, SubscriptionObservationDao observationDao, - SubscriptionLookupUtil lookupUtil) { + SubscriptionLookupUtil lookupUtil, + FF4j ff4j) { this.subscriptionDao = subscriptionDao; this.observationDao = observationDao; this.lookupUtil = lookupUtil; + this.ff4j = ff4j; this.allSubjects = subscriptionDao.getAllSubjects(); createSubscriptionSubjectProcessors(); @@ -73,7 +78,9 @@ private void createSubscriptionSubjectProcessors() { this.processors.add(new CertificationStatusChangedActivityProcessor(getSubject(lookupUtil.getCertificationStatusChangedSubjectId()))); this.processors.add(new CertificationCriteriaAddedActivityProcessor(getSubject(lookupUtil.getCertificationCriteriaAddedSubjectId()))); this.processors.add(new CertificationCriteriaRemovedActivityProcessor(getSubject(lookupUtil.getCertificationCriteriaRemovedSubjectId()))); - this.processors.add(new RwtPlansUrlChangedActivityProcessor(getSubject(lookupUtil.getRwtPlansUrlChangedSubjectId()))); + if (!ff4j.check(FeatureList.HTI_5_ERD)) { + this.processors.add(new RwtPlansUrlChangedActivityProcessor(getSubject(lookupUtil.getRwtPlansUrlChangedSubjectId()))); + } this.processors.add(new RwtResultsUrlChangedActivityProcessor(getSubject(lookupUtil.getRwtResultsUrlChangedSubjectId()))); this.processors.add(new ServiceBaseUrlListChangedActivityProcessor(getSubject(lookupUtil.getServiceBaseUrlListChangedSubjectId()))); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/dao/SubscriptionDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/dao/SubscriptionDao.java index 0eef9a79d5..bf462cf2f0 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/dao/SubscriptionDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/subscription/dao/SubscriptionDao.java @@ -5,8 +5,10 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; +import org.ff4j.FF4j; import org.springframework.stereotype.Repository; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.dao.impl.BaseDAOImpl; import gov.healthit.chpl.subscription.domain.Subscription; import gov.healthit.chpl.subscription.domain.SubscriptionConsolidationMethod; @@ -37,9 +39,12 @@ public class SubscriptionDao extends BaseDAOImpl { + "AND subscriber.deleted = false "; private SubscriptionLookupUtil lookupUtil; + private FF4j ff4j; - public SubscriptionDao(SubscriptionLookupUtil lookupUtil) { + public SubscriptionDao(SubscriptionLookupUtil lookupUtil, + FF4j ff4j) { this.lookupUtil = lookupUtil; + this.ff4j = ff4j; } public List getAllSubscriptionObjectTypes() { @@ -103,6 +108,8 @@ public void createSubscription(UUID subscriberId, Long subscribedObjectTypeId, L List subjectsForObjectType = getAllSubjectsForObjectType(subscribedObjectTypeId); subjectsForObjectType.stream() + .filter(subject -> !lookupUtil.getRwtPlansUrlChangedSubjectId().equals(subject.getId()) + || (!ff4j.check(FeatureList.HTI_5_ERD) && lookupUtil.getRwtPlansUrlChangedSubjectId().equals(subject.getId()))) .forEach(subject -> createSubscriptionIfNotExists( subscriberId, subscribedObjectId, subject.getId(), lookupUtil.getDailyConsolidationMethodId())); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/HeadingPostHti5.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/HeadingPostHti5.java index d36f33cb9a..fbc634839d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/HeadingPostHti5.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/HeadingPostHti5.java @@ -36,8 +36,6 @@ public enum HeadingPostHti5 implements Heading { ACCESSIBILITY_STANDARD("Accessibility Standard"), K_1_URL("170.523(k)(1) URL", "170.523 (k)(1) URL"), SVAP_NOTICE_URL("SVAP Notice URL"), - RWT_PLANS_URL("RWT Plans URL", "RWT_Plans_URL"), - RWT_PLANS_CHECK_DATE("RWT Plans Check Date"), RWT_RESULTS_URL("RWT Results URL", "RWT_Results_URL"), RWT_RESULTS_CHECK_DATE("RWT Results Check Date"), CQM_NUMBER("CQM Number", "CQM_NUMBER"), diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/handler/ListingDetailsUploadHandler.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/handler/ListingDetailsUploadHandler.java index a1191776b0..359f3620b3 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/handler/ListingDetailsUploadHandler.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/handler/ListingDetailsUploadHandler.java @@ -235,11 +235,11 @@ private String parseSvapNoticeUrl(CSVRecord headingRecord, List listi } private String parseRwtPlansUrl(CSVRecord headingRecord, List listingRecords) { - return uploadUtil.parseSingleRowField(HeadingPostHti5.RWT_PLANS_URL, headingRecord, listingRecords); + return uploadUtil.parseSingleRowField(HeadingPreHti5.RWT_PLANS_URL, headingRecord, listingRecords); } private String parseRwtPlansCheckDate(CSVRecord headingRecord, List listingRecords) { - return uploadUtil.parseSingleRowField(HeadingPostHti5.RWT_PLANS_CHECK_DATE, headingRecord, listingRecords); + return uploadUtil.parseSingleRowField(HeadingPreHti5.RWT_PLANS_CHECK_DATE, headingRecord, listingRecords); } private String parseRwtResultsUrl(CSVRecord headingRecord, List listingRecords) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/JSONUtils.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/JSONUtils.java index e534941286..7c539bf6f4 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/JSONUtils.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/JSONUtils.java @@ -10,6 +10,7 @@ import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.activity.ActivityExclude; +import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData; import gov.healthit.chpl.sed.DeprecatedSedSummaryData; import gov.healthit.chpl.sed.DeprecatedSedTestTaskData; import lombok.extern.log4j.Log4j2; @@ -44,7 +45,8 @@ public boolean hasIgnoreMarker(MapperConfig config, AnnotatedMember m) { return super.hasIgnoreMarker(config, m) || m.hasAnnotation(Deprecated.class) || m.hasAnnotation(ActivityExclude.class) - || isSedAndIgnorable(m); + || isSedAndIgnorable(m) + || isRwtAndIgnorable(m); } private boolean isSedAndIgnorable(AnnotatedMember m) { @@ -53,6 +55,12 @@ private boolean isSedAndIgnorable(AnnotatedMember m) { boolean isSedTestTask = _findAnnotation(m, DeprecatedSedTestTaskData.class) != null; return isHti5Erd && (isSedSummary || isSedTestTask); } + + private boolean isRwtAndIgnorable(AnnotatedMember m) { + boolean isHti5Erd = ff4j.check(FeatureList.HTI_5_ERD); + boolean isRwtPlans = _findAnnotation(m, DeprecatedRwtPlansData.class) != null; + return isHti5Erd && isRwtPlans; + } }) .build(); this.writerExcludingIgnoredFields = mapperExcludingIgnoredFields.writer(); diff --git a/chpl/chpl-service/src/test/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManagerTest.java b/chpl/chpl-service/src/test/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManagerTest.java index 2f787fe23f..aa45bf9b77 100644 --- a/chpl/chpl-service/src/test/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManagerTest.java +++ b/chpl/chpl-service/src/test/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingManagerTest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.UUID; +import org.ff4j.FF4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; @@ -48,7 +49,9 @@ public void setup() throws SchedulerException, ValidationException, UserRetrieva .thenReturn("This is an error message."); realWorldTestingManager = new RealWorldTestingManager(Mockito.mock(RealWorldTestingByDeveloperDao.class), - schedulerManager, errorMessageUtil); + schedulerManager, + errorMessageUtil, + Mockito.mock(FF4j.class)); } @Test