diff --git a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt index 136a6dfff4..0bc4ef3015 100644 --- a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt +++ b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt @@ -21,6 +21,7 @@ import org.dhis2.data.forms.dataentry.ValueStore import org.dhis2.data.forms.dataentry.ValueStoreImpl import org.dhis2.form.data.EnrollmentRepository import org.dhis2.form.data.RulesRepository +import org.dhis2.form.data.metadata.EnrollmentConfiguration import org.dhis2.form.data.metadata.FileResourceConfiguration import org.dhis2.form.data.metadata.OptionSetConfiguration import org.dhis2.form.data.metadata.OrgUnitConfiguration @@ -90,8 +91,7 @@ class EnrollmentModule( ): EnrollmentRepository { return EnrollmentRepository( fieldFactory = modelFactory, - enrollmentUid = enrollmentUid, - d2 = d2, + conf = EnrollmentConfiguration(d2, enrollmentUid), enrollmentMode = EnrollmentMode.valueOf(enrollmentMode.name), enrollmentFormLabelsProvider = enrollmentFormLabelsProvider, ) diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt index 894d753402..6f7b795d21 100644 --- a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt @@ -1,19 +1,21 @@ package org.dhis2.form.data -import org.dhis2.commons.bindings.disableCollapsableSectionsInProgram +import org.dhis2.form.data.metadata.FormBaseConfiguration import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.SectionUiModelImpl import org.dhis2.form.ui.FieldViewModelFactory -import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.imports.TrackerImportConflict import org.hisp.dhis.android.core.program.SectionRenderingType abstract class DataEntryBaseRepository( - private val d2: D2, + private val conf: FormBaseConfiguration, private val fieldFactory: FieldViewModelFactory, ) : DataEntryRepository { abstract val programUid: String? + override fun firstSectionToOpen(): String? { + return sectionUids().blockingFirst().firstOrNull() + } override fun updateSection( sectionToUpdate: FieldUiModel, @@ -64,10 +66,7 @@ abstract class DataEntryBaseRepository( private fun optionsFromGroups(optionGroupUids: List): List { if (optionGroupUids.isEmpty()) return emptyList() val optionsFromGroups = arrayListOf() - val optionGroups = d2.optionModule().optionGroups() - .withOptions() - .byUid().`in`(optionGroupUids) - .blockingGet() + val optionGroups = conf.optionGroups(optionGroupUids) for (optionGroup in optionGroups) { for (option in optionGroup.options()!!) { if (!optionsFromGroups.contains(option.uid())) { @@ -106,6 +105,6 @@ abstract class DataEntryBaseRepository( } override fun disableCollapsableSections(): Boolean? { - return programUid?.let { d2.disableCollapsableSectionsInProgram(programUid = it) } + return programUid?.let { conf.disableCollapsableSectionsInProgram(programUid = it) } } } diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt index 9da98dc173..41a3ee0213 100644 --- a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt @@ -5,6 +5,7 @@ import org.dhis2.form.model.FieldUiModel interface DataEntryRepository { fun list(): Flowable> + fun firstSectionToOpen(): String? fun sectionUids(): Flowable> fun updateSection( sectionToUpdate: FieldUiModel, diff --git a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt index 03e3dd0b91..5dc3066aa0 100644 --- a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt @@ -2,97 +2,78 @@ package org.dhis2.form.data import io.reactivex.Flowable import io.reactivex.Single -import org.dhis2.bindings.userFriendlyValue import org.dhis2.commons.date.DateUtils import org.dhis2.commons.orgunitselector.OrgUnitSelectorScope +import org.dhis2.form.data.metadata.EnrollmentConfiguration import org.dhis2.form.model.EnrollmentMode import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.OptionSetConfiguration import org.dhis2.form.model.SectionUiModelImpl.Companion.SINGLE_SECTION_UID import org.dhis2.form.ui.FieldViewModelFactory import org.dhis2.form.ui.provider.EnrollmentFormLabelsProvider -import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.arch.helpers.UidsHelper.getUidsList -import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope import org.hisp.dhis.android.core.common.FeatureType import org.hisp.dhis.android.core.common.ObjectStyle import org.hisp.dhis.android.core.common.ValueType -import org.hisp.dhis.android.core.enrollment.EnrollmentObjectRepository import org.hisp.dhis.android.core.imports.ImportStatus -import org.hisp.dhis.android.core.organisationunit.OrganisationUnit -import org.hisp.dhis.android.core.program.Program import org.hisp.dhis.android.core.program.ProgramSection import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute import org.hisp.dhis.android.core.program.SectionRenderingType import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute -import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeValueObjectRepository import timber.log.Timber class EnrollmentRepository( private val fieldFactory: FieldViewModelFactory, - private val enrollmentUid: String, - private val d2: D2, + private val conf: EnrollmentConfiguration, private val enrollmentMode: EnrollmentMode, private val enrollmentFormLabelsProvider: EnrollmentFormLabelsProvider, -) : DataEntryBaseRepository(d2, fieldFactory) { +) : DataEntryBaseRepository(conf, fieldFactory) { - private val enrollmentRepository: EnrollmentObjectRepository = - d2.enrollmentModule().enrollments().uid(enrollmentUid) - - private val program by lazy { - d2.programModule().programs().uid(enrollmentRepository.blockingGet()?.program()).get() + override val programUid by lazy { + conf.program()?.uid() } - override val programUid by lazy { - program.blockingGet()?.uid() + private val programSections by lazy { + conf.sections() } private fun canBeEdited(): Boolean { - val selectedProgram = d2.programModule().programs().uid( - d2.enrollmentModule().enrollments().uid(enrollmentUid).blockingGet()?.program(), - ).blockingGet() + val selectedProgram = conf.program() val programAccess = selectedProgram?.access()?.data()?.write() == true - val teTypeAccess = d2.trackedEntityModule().trackedEntityTypes().uid( - selectedProgram?.trackedEntityType()?.uid(), - ).blockingGet()?.access()?.data()?.write() == true + val teTypeAccess = conf.trackedEntityType()?.access()?.data()?.write() == true return programAccess && teTypeAccess } - private val programSections by lazy { - d2.programModule().programSections().withAttributes() - .byProgramUid().eq(enrollmentRepository.blockingGet()?.program()) - .blockingGet() - } - override fun sectionUids(): Flowable> { val sectionUids = mutableListOf(ENROLLMENT_DATA_SECTION_UID) - sectionUids.addAll(programSections.map { it.uid() }) + if (programSections.isEmpty()) { + sectionUids.add(SINGLE_SECTION_UID) + } else { + sectionUids.addAll(programSections.map { it.uid() }) + } return Flowable.just(sectionUids) } override fun list(): Flowable> { - return program - .flatMap { program -> - d2.programModule().programSections().byProgramUid().eq(program.uid()) - .withAttributes().get() - .flatMap { programSections -> - if (programSections.isEmpty()) { - getFieldsForSingleSection() - .map { singleSectionList -> - val list = getSingleSectionList() - list.addAll(singleSectionList) - list - } - } else { - getFieldsForMultipleSections() + return Single.just(conf.sections()) + .flatMap { programSections -> + if (programSections.isEmpty()) { + getFieldsForSingleSection() + .map { singleSectionList -> + val list = getSingleSectionList() + list.addAll(singleSectionList) + list } - }.map { list -> - val fields = getEnrollmentData(program) - fields.addAll(list) - fields.add(fieldFactory.createClosingSection()) - fields.toList() - } - }.toFlowable() + } else { + getFieldsForMultipleSections() + } + }.map { list -> + val fields = getEnrollmentData() + fields.addAll(list) + fields.add(fieldFactory.createClosingSection()) + fields.toList() + } + .toFlowable() } override fun isEvent(): Boolean { @@ -100,11 +81,7 @@ class EnrollmentRepository( } private fun getSingleSectionList(): MutableList { - val tei = d2.trackedEntityModule().trackedEntityInstances() - .uid(enrollmentRepository.blockingGet()?.trackedEntityInstance()) - .blockingGet() - val teiType = d2.trackedEntityModule().trackedEntityTypes() - .uid(tei?.trackedEntityType()).blockingGet() + val teiType = conf.trackedEntityType() return mutableListOf( fieldFactory.createSingleSection( String.format( @@ -117,13 +94,7 @@ class EnrollmentRepository( private fun getFieldsForSingleSection(): Single> { return Single.fromCallable { - val programAttributes = - d2.programModule().programTrackedEntityAttributes().withRenderType() - .byProgram().eq(program.blockingGet()?.uid()) - .orderBySortOrder(RepositoryScope.OrderByDirection.ASC) - .blockingGet() - - programAttributes.map { programTrackedEntityAttribute -> + conf.programAttributes().map { programTrackedEntityAttribute -> transform(programTrackedEntityAttribute) } } @@ -137,12 +108,9 @@ class EnrollmentRepository( transformSection(section.uid(), section.displayName(), section.description()), ) section.attributes()?.forEachIndexed { _, attribute -> - d2.programModule().programTrackedEntityAttributes().withRenderType() - .byProgram().eq(program.blockingGet()?.uid()) - .byTrackedEntityAttribute().eq(attribute.uid()) - .one().blockingGet()?.let { programTrackedEntityAttribute -> - fields.add(transform(programTrackedEntityAttribute, section.uid())) - } + conf.programAttribute(attribute.uid())?.let { programTrackedEntityAttribute -> + fields.add(transform(programTrackedEntityAttribute, section.uid())) + } } } return@fromCallable fields @@ -153,33 +121,28 @@ class EnrollmentRepository( programTrackedEntityAttribute: ProgramTrackedEntityAttribute, sectionUid: String? = SINGLE_SECTION_UID, ): FieldUiModel { - val attribute = d2.trackedEntityModule().trackedEntityAttributes() - .uid(programTrackedEntityAttribute.trackedEntityAttribute()!!.uid()) - .blockingGet() - val attrValueRepository = d2.trackedEntityModule().trackedEntityAttributeValues() - .value( - attribute!!.uid(), - enrollmentRepository.blockingGet()!!.trackedEntityInstance()!!, - ) + val attribute = programTrackedEntityAttribute.trackedEntityAttribute()?.uid()?.let { + conf.trackedEntityAttribute(it) + } ?: throw IllegalStateException( + "Attribute %s does not exist".format( + programTrackedEntityAttribute.trackedEntityAttribute()?.uid(), + ), + ) val valueType = attribute.valueType() - var mandatory = programTrackedEntityAttribute.mandatory()!! + var mandatory = programTrackedEntityAttribute.mandatory() ?: false val optionSet = attribute.optionSet()?.uid() - val generated = attribute.generated()!! + val generated = attribute.generated() ?: false - val orgUnitUid = enrollmentRepository.blockingGet()!!.organisationUnit() + val orgUnitUid = conf.enrollment() + ?.organisationUnit() - var dataValue: String? = getAttributeValue(attrValueRepository) + var dataValue: String? = attribute.uid() + ?.let { conf.attributeValue(it) } var optionSetConfig: OptionSetConfiguration? = null if (!optionSet.isNullOrEmpty()) { - val optionCount = - d2.optionModule().options().byOptionSetUid().eq(optionSet).blockingCount() - optionSetConfig = OptionSetConfiguration.config(optionCount) { - d2.optionModule().options().byOptionSetUid().eq(optionSet) - .orderBySortOrder(RepositoryScope.OrderByDirection.ASC) - .blockingGet() - } + optionSetConfig = conf.optionSetConfig(optionSet) } var (error, warning) = getConflictErrorsAndWarnings(attribute.uid(), dataValue) @@ -190,14 +153,14 @@ class EnrollmentRepository( dataValue = result.first warning = result.second if (!dataValue.isNullOrEmpty()) { - attrValueRepository.blockingSet(dataValue) + conf.setValue(attribute.uid(), dataValue) } } if ((valueType == ValueType.ORGANISATION_UNIT || valueType?.isDate == true) && !dataValue.isNullOrEmpty() ) { - dataValue = attrValueRepository.blockingGet()?.value() + dataValue = conf.getValue(attribute.uid())?.value() } var programSection: ProgramSection? = null @@ -247,9 +210,7 @@ class EnrollmentRepository( var error: String? = null var warning: String? = null - val conflicts = d2.importModule().trackerImportConflicts() - .byEnrollmentUid().eq(enrollmentUid) - .blockingGet() + val conflicts = conf.conflicts() val conflict = conflicts .find { it.trackedEntityAttribute() == attributeUid } @@ -268,14 +229,6 @@ class EnrollmentRepository( private fun getSectionRenderingType(programSection: ProgramSection?) = programSection?.renderType()?.mobile()?.type() - private fun getAttributeValue( - attrValueRepository: TrackedEntityAttributeValueObjectRepository, - ) = if (attrValueRepository.blockingExists()) { - attrValueRepository.blockingGet()?.userFriendlyValue(d2) - } else { - null - } - private fun handleAutogeneratedValue( attr: TrackedEntityAttribute, orgUnitUid: String, @@ -283,12 +236,11 @@ class EnrollmentRepository( var warning: String? = null var dataValue: String? = null try { - val teiUid = enrollmentRepository.blockingGet()!!.trackedEntityInstance() + val teiUid = conf.tei() if (teiUid != null) { try { - dataValue = d2.trackedEntityModule().reservedValueManager() - .blockingGetValue(attr.uid(), orgUnitUid) + dataValue = conf.fetchAutogeneratedValue(attr.uid(), orgUnitUid) } catch (e: Exception) { dataValue = null warning = enrollmentFormLabelsProvider.provideReservedValueWarning() @@ -296,8 +248,7 @@ class EnrollmentRepository( if (attr.valueType() == ValueType.NUMBER) { while (dataValue!!.startsWith("0")) { - dataValue = d2.trackedEntityModule().reservedValueManager() - .blockingGetValue(attr.uid(), orgUnitUid) + dataValue = conf.fetchAutogeneratedValue(attr.uid(), orgUnitUid) } } } @@ -309,47 +260,43 @@ class EnrollmentRepository( return Pair(dataValue, warning) } - private fun getEnrollmentData(program: Program): MutableList { + private fun getEnrollmentData(): MutableList { val enrollmentDataList = ArrayList() - enrollmentDataList.add(getEnrollmentDataSection(program.description())) + enrollmentDataList.add(getEnrollmentDataSection(conf.program()?.description())) enrollmentDataList.add( getEnrollmentDateField( - program.enrollmentDateLabel() + conf.program()?.enrollmentDateLabel() ?: enrollmentFormLabelsProvider.provideEnrollmentDateDefaultLabel(), - program.selectEnrollmentDatesInFuture(), + conf.program()?.selectEnrollmentDatesInFuture(), ), ) - if (program.displayIncidentDate()!!) { + if (conf.program()?.displayIncidentDate()!!) { enrollmentDataList.add( getIncidentDateField( - program.incidentDateLabel() + conf.program()?.incidentDateLabel() ?: enrollmentFormLabelsProvider.provideIncidentDateDefaultLabel(), - program.selectIncidentDatesInFuture(), + conf.program()?.selectIncidentDatesInFuture(), ), ) } - val programUids = - enrollmentRepository.blockingGet()?.program()?.let { listOf(it) } ?: emptyList() - val orgUnits = d2.organisationUnitModule().organisationUnits() - .byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) - .byProgramUids(programUids).blockingCount() + + val orgUnits = conf.captureOrgUnitsCount() enrollmentDataList.add( getOrgUnitField(enrollmentMode == EnrollmentMode.NEW && orgUnits > 1), ) - val teiType = - d2.trackedEntityModule().trackedEntityTypes() - .uid(program.trackedEntityType()!!.uid()) - .blockingGet() + val teiType = conf.trackedEntityType() if (teiType!!.featureType() != null && teiType.featureType() != FeatureType.NONE) { enrollmentDataList.add(getTeiCoordinatesField(teiType.featureType())) } - if (program.featureType() != null && program.featureType() != FeatureType.NONE) { + if (conf.program()?.featureType() != null && conf.program() + ?.featureType() != FeatureType.NONE + ) { enrollmentDataList.add( getEnrollmentCoordinatesField( - program.featureType(), + conf.program()?.featureType(), ), ) } @@ -379,7 +326,7 @@ class EnrollmentRepository( ValueType.DATE, true, // check in constructor of dateviewmodel null, - when (val date = enrollmentRepository.blockingGet()!!.enrollmentDate()) { + when (val date = conf.enrollment()?.enrollmentDate()) { null -> null else -> DateUtils.oldUiDateFormat().format(date) }, @@ -406,7 +353,7 @@ class EnrollmentRepository( ValueType.DATE, true, null, - when (val date = enrollmentRepository.blockingGet()!!.incidentDate()) { + when (val date = conf.enrollment()?.incidentDate()) { null -> null else -> DateUtils.oldUiDateFormat().format(date) }, @@ -430,7 +377,7 @@ class EnrollmentRepository( ValueType.ORGANISATION_UNIT, true, null, - enrollmentRepository.blockingGet()?.organisationUnit(), + conf.enrollment()?.organisationUnit(), ENROLLMENT_DATA_SECTION_UID, null, editable, @@ -446,12 +393,8 @@ class EnrollmentRepository( } private fun getTeiCoordinatesField(featureType: FeatureType?): FieldUiModel { - val tei = d2.trackedEntityModule().trackedEntityInstances() - .uid( - enrollmentRepository.blockingGet()!!.trackedEntityInstance(), - ).blockingGet() - val teiType = d2.trackedEntityModule().trackedEntityTypes() - .uid(tei?.trackedEntityType()).blockingGet() + val tei = conf.tei() + val teiType = conf.trackedEntityType() val teiCoordinatesLabel = enrollmentFormLabelsProvider.provideTeiCoordinatesLabel() return fieldFactory.create( TEI_COORDINATES_UID, @@ -459,7 +402,7 @@ class EnrollmentRepository( ValueType.COORDINATE, false, null, - if (tei!!.geometry() != null) tei.geometry()!!.coordinates() else null, + tei?.geometry()?.coordinates(), ENROLLMENT_DATA_SECTION_UID, null, canBeEdited(), @@ -480,11 +423,7 @@ class EnrollmentRepository( ValueType.COORDINATE, false, null, - if (enrollmentRepository.blockingGet()!!.geometry() != null) { - enrollmentRepository.blockingGet()!!.geometry()!!.coordinates() - } else { - null - }, + conf.enrollment()?.geometry()?.coordinates(), ENROLLMENT_DATA_SECTION_UID, null, canBeEdited(), @@ -499,41 +438,47 @@ class EnrollmentRepository( } fun hasEventsGeneratedByEnrollmentDate(): Boolean { - val enrollment = enrollmentRepository.blockingGet() ?: return false - - val stagesWithReportDateToUse = d2.programModule().programStages() - .byProgramUid().eq(enrollment.program()) - .byOpenAfterEnrollment().isTrue - .byReportDateToUse().eq("enrollmentDate") - .blockingGetUids() - val stagesWithGeneratedBy = d2.programModule().programStages() - .byProgramUid().eq(enrollment.program()) - .byAutoGenerateEvent().isTrue - .byGeneratedByEnrollmentDate().isTrue - .blockingGetUids() - return !d2.eventModule().events() - .byEnrollmentUid().eq(enrollmentUid) - .byProgramStageUid().`in`(stagesWithReportDateToUse.union(stagesWithGeneratedBy)) - .blockingIsEmpty() + return conf.hasEventsGeneratedByEnrollmentDate() } fun hasEventsGeneratedByIncidentDate(): Boolean { - val enrollment = enrollmentRepository.blockingGet() ?: return false - - val stagesWithReportDateToUse = d2.programModule().programStages() - .byProgramUid().eq(enrollment.program()) - .byOpenAfterEnrollment().isTrue - .byReportDateToUse().eq("incidentDate") - .blockingGetUids() - val stagesWithGeneratedBy = d2.programModule().programStages() - .byProgramUid().eq(enrollment.program()) - .byAutoGenerateEvent().isTrue - .byGeneratedByEnrollmentDate().isFalse - .blockingGetUids() - return !d2.eventModule().events() - .byEnrollmentUid().eq(enrollmentUid) - .byProgramStageUid().`in`(stagesWithReportDateToUse.union(stagesWithGeneratedBy)) - .blockingIsEmpty() + return conf.hasEventsGeneratedByIncidentDate() + } + + override fun firstSectionToOpen(): String? { + return if (enrollmentMode == EnrollmentMode.CHECK && isEnrollmentDataCompleted()) { + sectionUids().blockingFirst().filterIndexed { index, _ -> index != 0 }.firstOrNull() + } else { + super.firstSectionToOpen() + } + } + + private fun isEnrollmentDataCompleted(): Boolean { + val program = conf.program() + val enrollment = conf.enrollment() + + val hasEnrollmentDate = enrollment?.enrollmentDate() != null + if (!hasEnrollmentDate) return false + + if (program?.displayIncidentDate() == true) { + val hasIncidentDate = enrollment?.incidentDate() != null + if (!hasIncidentDate) return false + } + + val hasOrganisationUnit = enrollment?.organisationUnit() != null + if (!hasOrganisationUnit) return false + + if (conf.trackedEntityType()?.featureType() != FeatureType.NONE) { + val hasTeiCoordinates = conf.tei()?.geometry() != null + if (!hasTeiCoordinates) return false + } + + if (program?.featureType() != FeatureType.NONE) { + val hasEnrollmentCoordinates = enrollment?.geometry() != null + if (!hasEnrollmentCoordinates) return false + } + + return true } companion object { diff --git a/form/src/main/java/org/dhis2/form/data/EventRepository.kt b/form/src/main/java/org/dhis2/form/data/EventRepository.kt index d452cf7d0b..67fbb4b7bc 100644 --- a/form/src/main/java/org/dhis2/form/data/EventRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EventRepository.kt @@ -5,6 +5,7 @@ import io.reactivex.Flowable import io.reactivex.Single import org.dhis2.bindings.blockingGetValueCheck import org.dhis2.bindings.userFriendlyValue +import org.dhis2.form.data.metadata.FormBaseConfiguration import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.OptionSetConfiguration import org.dhis2.form.ui.FieldViewModelFactory @@ -22,7 +23,7 @@ class EventRepository( private val fieldFactory: FieldViewModelFactory, private val eventUid: String, private val d2: D2, -) : DataEntryBaseRepository(d2, fieldFactory) { +) : DataEntryBaseRepository(FormBaseConfiguration(d2), fieldFactory) { private val event by lazy { d2.eventModule().events().uid(eventUid) diff --git a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt index 4d52a7041e..3f7ab17614 100644 --- a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt +++ b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt @@ -54,10 +54,10 @@ class FormRepositoryImpl( shouldOpenErrorLocation -> itemList.firstOrNull { it.error != null || it.warning != null }?.programStageSection - ?: dataEntryRepository?.sectionUids()?.blockingFirst()?.firstOrNull() + ?: dataEntryRepository?.firstSectionToOpen() else -> - dataEntryRepository?.sectionUids()?.blockingFirst()?.firstOrNull() + dataEntryRepository?.firstSectionToOpen() } override fun composeList(skipProgramRules: Boolean): List { diff --git a/form/src/main/java/org/dhis2/form/data/SearchRepository.kt b/form/src/main/java/org/dhis2/form/data/SearchRepository.kt index fd6b66bf9b..944b75914e 100644 --- a/form/src/main/java/org/dhis2/form/data/SearchRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/SearchRepository.kt @@ -1,6 +1,7 @@ package org.dhis2.form.data import io.reactivex.Flowable +import org.dhis2.form.data.metadata.FormBaseConfiguration import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.OptionSetConfiguration import org.dhis2.form.ui.FieldViewModelFactory @@ -14,7 +15,7 @@ class SearchRepository( override val programUid: String?, private val teiTypeUid: String, private val currentSearchValues: Map, -) : DataEntryBaseRepository(d2, fieldViewModelFactory) { +) : DataEntryBaseRepository(FormBaseConfiguration(d2), fieldViewModelFactory) { override fun list(): Flowable> { return programUid?.let { diff --git a/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt new file mode 100644 index 0000000000..1e89339500 --- /dev/null +++ b/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt @@ -0,0 +1,123 @@ +package org.dhis2.form.data.metadata + +import org.dhis2.bindings.userFriendlyValue +import org.dhis2.commons.bindings.enrollment +import org.dhis2.commons.bindings.enrollmentImportConflicts +import org.dhis2.commons.bindings.program +import org.dhis2.commons.bindings.tei +import org.dhis2.commons.bindings.teiAttribute +import org.dhis2.commons.bindings.trackedEntityType +import org.dhis2.form.model.OptionSetConfiguration +import org.hisp.dhis.android.core.D2 +import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope +import org.hisp.dhis.android.core.enrollment.Enrollment +import org.hisp.dhis.android.core.organisationunit.OrganisationUnit + +class EnrollmentConfiguration(private val d2: D2, private val enrollmentUid: String) : + FormBaseConfiguration(d2) { + private val _enrollment: Enrollment? by lazy { + d2.enrollment(enrollmentUid) + } + + fun enrollment() = _enrollment + + fun program() = enrollment()?.program()?.let { + d2.program(it) + } + + fun tei() = enrollment()?.trackedEntityInstance()?.let { d2.tei(it) } + fun trackedEntityType() = d2.trackedEntityType(program()?.trackedEntityType()?.uid()!!) + fun sections() = d2.programModule().programSections() + .withAttributes() + .byProgramUid().eq(enrollment()?.program()) + .blockingGet() + + fun programAttributes() = + d2.programModule().programTrackedEntityAttributes() + .withRenderType() + .byProgram().eq(enrollment()?.program()) + .orderBySortOrder(RepositoryScope.OrderByDirection.ASC) + .blockingGet() + + fun programAttribute(attributeUid: String) = + d2.programModule().programTrackedEntityAttributes().withRenderType() + .byProgram().eq(enrollment()?.program()) + .byTrackedEntityAttribute().eq(attributeUid) + .one().blockingGet() + + fun trackedEntityAttribute(trackedEntityAttributeUid: String) = + d2.teiAttribute(trackedEntityAttributeUid) + + fun attributeValue(trackedEntityAttributeUid: String) = + d2.trackedEntityModule().trackedEntityAttributeValues() + .value( + trackedEntityAttributeUid, + enrollment()?.trackedEntityInstance()!!, + ).blockingGet()?.userFriendlyValue(d2) + + fun conflicts() = d2.enrollmentImportConflicts(enrollmentUid) + + fun fetchAutogeneratedValue(trackedEntityAttributeUid: String, orgUnitUid: String) = + d2.trackedEntityModule().reservedValueManager() + .blockingGetValue(trackedEntityAttributeUid, orgUnitUid) + + fun captureOrgUnitsCount() = d2.organisationUnitModule().organisationUnits() + .byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) + .byProgramUids(enrollment()?.program()?.let { listOf(it) } ?: emptyList()) + .blockingCount() + + fun hasEventsGeneratedByEnrollmentDate(): Boolean { + val stagesWithReportDateToUse = d2.programModule().programStages() + .byProgramUid().eq(enrollment()?.program()) + .byOpenAfterEnrollment().isTrue + .byReportDateToUse().eq("enrollmentDate") + .blockingGetUids() + val stagesWithGeneratedBy = d2.programModule().programStages() + .byProgramUid().eq(enrollment()?.program()) + .byAutoGenerateEvent().isTrue + .byGeneratedByEnrollmentDate().isTrue + .blockingGetUids() + return !d2.eventModule().events() + .byEnrollmentUid().eq(enrollmentUid) + .byProgramStageUid().`in`(stagesWithReportDateToUse.union(stagesWithGeneratedBy)) + .blockingIsEmpty() + } + + fun hasEventsGeneratedByIncidentDate(): Boolean { + val stagesWithReportDateToUse = d2.programModule().programStages() + .byProgramUid().eq(enrollment()?.program()) + .byOpenAfterEnrollment().isTrue + .byReportDateToUse().eq("incidentDate") + .blockingGetUids() + val stagesWithGeneratedBy = d2.programModule().programStages() + .byProgramUid().eq(enrollment()?.program()) + .byAutoGenerateEvent().isTrue + .byGeneratedByEnrollmentDate().isFalse + .blockingGetUids() + return !d2.eventModule().events() + .byEnrollmentUid().eq(enrollmentUid) + .byProgramStageUid().`in`(stagesWithReportDateToUse.union(stagesWithGeneratedBy)) + .blockingIsEmpty() + } + + fun setValue(attributeUid: String, value: String) { + d2.trackedEntityModule().trackedEntityAttributeValues() + .value(attributeUid, tei()?.uid()!!) + .blockingSet(value) + } + + fun getValue(attributeUid: String) = d2.trackedEntityModule().trackedEntityAttributeValues() + .value(attributeUid, tei()?.uid()!!) + .blockingGet() + + fun optionSetConfig(optionSetUid: String) = + d2.optionModule().options().byOptionSetUid().eq(optionSetUid).blockingCount() + .let { optionCount -> + OptionSetConfiguration.config(optionCount) { + d2.optionModule().options() + .byOptionSetUid().eq(optionSetUid) + .orderBySortOrder(RepositoryScope.OrderByDirection.ASC) + .blockingGet() + } + } +} diff --git a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt new file mode 100644 index 0000000000..d24fb47498 --- /dev/null +++ b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt @@ -0,0 +1,14 @@ +package org.dhis2.form.data.metadata + +import org.dhis2.commons.bindings.disableCollapsableSectionsInProgram +import org.hisp.dhis.android.core.D2 + +open class FormBaseConfiguration(private val d2: D2) { + fun optionGroups(optionGroupUids: List) = d2.optionModule().optionGroups() + .withOptions() + .byUid().`in`(optionGroupUids) + .blockingGet() + + fun disableCollapsableSectionsInProgram(programUid: String) = + d2.disableCollapsableSectionsInProgram(programUid) +} diff --git a/form/src/main/java/org/dhis2/form/di/Injector.kt b/form/src/main/java/org/dhis2/form/di/Injector.kt index 58b45a292a..b2fec9542f 100644 --- a/form/src/main/java/org/dhis2/form/di/Injector.kt +++ b/form/src/main/java/org/dhis2/form/di/Injector.kt @@ -21,6 +21,7 @@ import org.dhis2.form.data.RuleEngineRepository import org.dhis2.form.data.RulesUtilsProviderImpl import org.dhis2.form.data.SearchOptionSetOption import org.dhis2.form.data.SearchRepository +import org.dhis2.form.data.metadata.EnrollmentConfiguration import org.dhis2.form.data.metadata.FileResourceConfiguration import org.dhis2.form.data.metadata.OptionSetConfiguration import org.dhis2.form.data.metadata.OrgUnitConfiguration @@ -157,8 +158,7 @@ object Injector { enrollmentRecords.allowMandatoryFields, enrollmentRecords.isBackgroundTransparent, ), - enrollmentUid = enrollmentRecords.enrollmentUid, - d2 = provideD2(), + conf = EnrollmentConfiguration(provideD2(), enrollmentRecords.enrollmentUid), enrollmentMode = enrollmentRecords.enrollmentMode, enrollmentFormLabelsProvider = provideEnrollmentFormLabelsProvider(context), ) diff --git a/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt b/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt index a006c8e012..c9c37647ea 100644 --- a/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt +++ b/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt @@ -44,6 +44,7 @@ class FormRepositoryImplTest { @Before fun setUp() { whenever(dataEntryRepository.disableCollapsableSections()) doReturn null + whenever(dataEntryRepository.firstSectionToOpen())doReturn mockedSections().first() whenever(dataEntryRepository.sectionUids()) doReturn Flowable.just(mockedSections()) whenever(dataEntryRepository.list()) doReturn Flowable.just(provideItemList()) repository = FormRepositoryImpl( diff --git a/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt b/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt new file mode 100644 index 0000000000..588888961a --- /dev/null +++ b/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt @@ -0,0 +1,192 @@ +package org.dhis2.form.data + +import org.dhis2.form.data.metadata.EnrollmentConfiguration +import org.dhis2.form.model.EnrollmentMode +import org.dhis2.form.model.SectionUiModelImpl +import org.dhis2.form.ui.FieldViewModelFactoryImpl +import org.dhis2.form.ui.provider.AutoCompleteProvider +import org.dhis2.form.ui.provider.DisplayNameProvider +import org.dhis2.form.ui.provider.EnrollmentFormLabelsProvider +import org.dhis2.form.ui.provider.HintProvider +import org.dhis2.form.ui.provider.KeyboardActionProvider +import org.dhis2.form.ui.provider.LayoutProvider +import org.dhis2.form.ui.provider.LegendValueProvider +import org.dhis2.form.ui.provider.UiEventTypesProvider +import org.dhis2.form.ui.provider.UiStyleProvider +import org.dhis2.form.ui.validation.FieldErrorMessageProvider +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.common.ValueType +import org.hisp.dhis.android.core.enrollment.Enrollment +import org.hisp.dhis.android.core.program.Program +import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute +import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute +import org.hisp.dhis.android.core.trackedentity.TrackedEntityType +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.util.Date + +class FormRepositoryIntegrationTest { + private val rulesUtilsProvider: RulesUtilsProvider = mock() + private val ruleEngineRepository: RuleEngineRepository = mock() + private val formValueStore: FormValueStore = mock() + private val fieldErrorMessageProvider: FieldErrorMessageProvider = mock() + private val conf: EnrollmentConfiguration = mock() + private val enrollmentFormLabelsProvider: EnrollmentFormLabelsProvider = mock { + on { provideEnrollmentOrgUnitLabel() } doReturn "OrgUnit label" + } + + private val program: Program = mock { + on { description() } doReturn "program description" + on { enrollmentDateLabel() } doReturn "enrollment date label" + on { selectEnrollmentDatesInFuture() } doReturn false + on { displayIncidentDate() } doReturn false + on { access() } doReturn mock() + on { access().data() } doReturn mock() + on { access().data().write() } doReturn true + on { featureType() } doReturn FeatureType.NONE + } + + private val teType: TrackedEntityType = mock { + on { access() } doReturn mock() + on { access().data() } doReturn mock() + on { access().data().write() } doReturn true + on { featureType() } doReturn FeatureType.NONE + } + + @Before + fun setUp() { + whenever(conf.sections()) doReturn emptyList() + val programAttribute: ProgramTrackedEntityAttribute = mock { + on { trackedEntityAttribute() } doReturn ObjectWithUid.create("teAttributeUid") + on { mandatory() } doReturn false + } + whenever(conf.programAttributes()) doReturn listOf(programAttribute) + val teAttribute: TrackedEntityAttribute = mock { + on { uid() } doReturn "teAttributeUid" + on { valueType() } doReturn ValueType.TEXT + on { optionSet() } doReturn null + on { generated() } doReturn false + on { style() } doReturn ObjectStyle.builder().build() + on { fieldMask() } doReturn null + } + whenever(conf.trackedEntityAttribute("teAttributeUid")) doReturn teAttribute + whenever(conf.attributeValue(any())) doReturn null + whenever(conf.conflicts()) doReturn emptyList() + whenever(conf.program()) doReturn program + whenever(conf.trackedEntityType()) doReturn teType + whenever(conf.captureOrgUnitsCount()) doReturn 1 + + whenever(enrollmentFormLabelsProvider.provideSingleSectionLabel()) doReturn "single section label" + } + + @Test + fun shouldOpenEnrollmentDetailSectionIfIsNewAndNotCompleted() { + mockUncompletedEnrollment() + whenever(conf.disableCollapsableSectionsInProgram(any())) doReturn false + + val repository = mockFormRepository() + + val fields = repository.fetchFormItems() + assertTrue((fields.first { it.isSection() } as SectionUiModelImpl).isOpen == true) + } + + @Test + fun shouldOpenEnrollmentDetailSectionIfIsNewAndCompleted() { + mockCompletedEnrollment() + whenever(conf.disableCollapsableSectionsInProgram(any())) doReturn false + + val repository = mockFormRepository(EnrollmentMode.NEW) + + val fields = repository.fetchFormItems() + assertTrue((fields.first { it.isSection() } as SectionUiModelImpl).isOpen == true) + } + + @Test + fun shouldOpenEnrollmentDetailSectionIfNotCompleted() { + mockUncompletedEnrollment() + whenever(conf.disableCollapsableSectionsInProgram(any())) doReturn false + + val repository = mockFormRepository(EnrollmentMode.CHECK) + + val fields = repository.fetchFormItems() + assertTrue((fields.first { it.isSection() } as SectionUiModelImpl).isOpen == true) + } + + @Test + fun shouldNotOpenEnrollmentDetailSectionIfCompleted() { + mockCompletedEnrollment() + whenever(conf.disableCollapsableSectionsInProgram(any())) doReturn false + + val repository = mockFormRepository(EnrollmentMode.CHECK) + + val fields = repository.fetchFormItems() + assertTrue( + (fields.filter { it.isSection() }[1] as SectionUiModelImpl).isOpen == true, + ) + } + + private fun mockUncompletedEnrollment() { + val enrollment: Enrollment = mock { + on { enrollmentDate() } doReturn null + on { organisationUnit() } doReturn "orgUnitUid" + } + whenever(conf.enrollment()) doReturn enrollment + } + + private fun mockCompletedEnrollment() { + val enrollment: Enrollment = mock { + on { enrollmentDate() } doReturn Date() + on { organisationUnit() } doReturn "orgUnitUid" + } + whenever(conf.enrollment()) doReturn enrollment + } + + private fun mockFormRepository(enrollmentMode: EnrollmentMode = EnrollmentMode.NEW): FormRepositoryImpl { + val styleProvider: UiStyleProvider = mock() + val layoutProvider: LayoutProvider = mock() + val hintProvider: HintProvider = mock() + val displayNameProvider: DisplayNameProvider = mock() + val uiEventTypesProvider: UiEventTypesProvider = mock() + val keyboardActionProvider: KeyboardActionProvider = mock() + val legendValueProvider: LegendValueProvider = mock() + val autoCompleteProvider: AutoCompleteProvider = mock() + + val fieldFactory = FieldViewModelFactoryImpl( + false, + styleProvider, + layoutProvider, + hintProvider, + displayNameProvider, + uiEventTypesProvider, + keyboardActionProvider, + legendValueProvider, + autoCompleteProvider, + ) + + val dataEntryRepository = EnrollmentRepository( + fieldFactory, + conf, + enrollmentMode, + enrollmentFormLabelsProvider, + ) + + val repository = FormRepositoryImpl( + formValueStore, + fieldErrorMessageProvider, + displayNameProvider, + dataEntryRepository, + ruleEngineRepository, + rulesUtilsProvider, + legendValueProvider, + false, + ) + return repository + } +}