Skip to content

Commit ec0a325

Browse files
authored
Merge pull request #4129 from dhis2/merge-release-3.2.0-into-develop
merge-release-3.2.0-into-develop [skip size]
2 parents 59baa22 + d2368a8 commit ec0a325

File tree

33 files changed

+689
-237
lines changed

33 files changed

+689
-237
lines changed

.github/workflows/continuous-delivery.yml

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
# Create APK Debug
4646
- name: Build apk debug project (APK) - ${{ env.main_project_module }} module
4747
run: ./gradlew assembleDhis2Debug
48+
env:
49+
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
4850

4951
- name: Read version name from file
5052
working-directory: ./gradle

aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/data/DataSetInstanceRepositoryImpl.kt

+135-31
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import org.dhis2.mobile.aggregates.data.mappers.toInputType
99
import org.dhis2.mobile.aggregates.model.CellElement
1010
import org.dhis2.mobile.aggregates.model.DataElementInfo
1111
import org.dhis2.mobile.aggregates.model.DataSetDetails
12+
import org.dhis2.mobile.aggregates.model.DataSetEdition
1213
import org.dhis2.mobile.aggregates.model.DataSetInstanceConfiguration
1314
import org.dhis2.mobile.aggregates.model.DataSetInstanceSectionConfiguration
1415
import org.dhis2.mobile.aggregates.model.DataSetRenderingConfig
1516
import org.dhis2.mobile.aggregates.model.DataToReview
17+
import org.dhis2.mobile.aggregates.model.GreyedOutField
1618
import org.dhis2.mobile.aggregates.model.InputType
1719
import org.dhis2.mobile.aggregates.model.MandatoryCellElements
20+
import org.dhis2.mobile.aggregates.model.NonEditableReason
1821
import org.dhis2.mobile.aggregates.model.PivoteMode
1922
import org.dhis2.mobile.aggregates.model.TableGroup
2023
import org.dhis2.mobile.aggregates.model.ValidationResultStatus
@@ -35,6 +38,7 @@ import org.hisp.dhis.android.core.common.ValueType
3538
import org.hisp.dhis.android.core.dataelement.DataElement
3639
import org.hisp.dhis.android.core.dataelement.DataElementOperand
3740
import org.hisp.dhis.android.core.dataset.DataSetEditableStatus
41+
import org.hisp.dhis.android.core.dataset.DataSetNonEditableReason
3842
import org.hisp.dhis.android.core.dataset.Section
3943
import org.hisp.dhis.android.core.dataset.SectionPivotMode
4044
import org.hisp.dhis.android.core.dataset.TabsDirection
@@ -67,31 +71,96 @@ internal class DataSetInstanceRepositoryImpl(
6771

6872
val dataSetDTOCustomTitle = dataSet?.displayOptions()?.customText()
6973

74+
val period = d2.periodModule().periods().byPeriodId().eq(periodId)
75+
.one().blockingGet()
76+
77+
val periodLabel = period?.let {
78+
periodLabelProvider(
79+
periodType = period.periodType(),
80+
periodId = period.periodId()!!,
81+
periodStartDate = period.startDate()!!,
82+
periodEndDate = period.endDate()!!,
83+
locale = Locale.getDefault(),
84+
)
85+
} ?: periodId
86+
87+
val edition = d2.dataSetModule().dataSetInstanceService().blockingGetEditableStatus(
88+
dataSetUid,
89+
periodId,
90+
orgUnitUid,
91+
attrOptionComboUid,
92+
).let {
93+
DataSetEdition(
94+
editable = it == DataSetEditableStatus.Editable,
95+
nonEditableReason = (it as? DataSetEditableStatus.NonEditable)?.reason?.let { reason ->
96+
when (reason) {
97+
DataSetNonEditableReason.NO_DATASET_DATA_WRITE_ACCESS ->
98+
NonEditableReason.NoDataWriteAccess
99+
100+
DataSetNonEditableReason.NO_ATTRIBUTE_OPTION_COMBO_ACCESS ->
101+
NonEditableReason.NoAttributeOptionComboAccess(
102+
d2.categoryModule().categoryOptionCombos()
103+
.uid(attrOptionComboUid)
104+
.blockingGet()?.displayName() ?: attrOptionComboUid,
105+
)
106+
107+
DataSetNonEditableReason.ORGUNIT_IS_NOT_IN_CAPTURE_SCOPE ->
108+
NonEditableReason.OrgUnitNotInCaptureScope(
109+
d2.organisationUnitModule().organisationUnits()
110+
.uid(orgUnitUid)
111+
.blockingGet()?.displayName() ?: orgUnitUid,
112+
)
113+
114+
DataSetNonEditableReason.ATTRIBUTE_OPTION_COMBO_NO_ASSIGN_TO_ORGUNIT ->
115+
NonEditableReason.AttributeOptionComboNotAssignedToOrgUnit(
116+
d2.categoryModule().categoryOptionCombos()
117+
.uid(attrOptionComboUid)
118+
.blockingGet()?.displayName() ?: attrOptionComboUid,
119+
d2.organisationUnitModule().organisationUnits()
120+
.uid(orgUnitUid)
121+
.blockingGet()?.displayName() ?: orgUnitUid,
122+
)
123+
124+
DataSetNonEditableReason.PERIOD_IS_NOT_IN_ORGUNIT_RANGE ->
125+
NonEditableReason.PeriodIsNotInOrgUnitRange(
126+
periodLabel,
127+
d2.organisationUnitModule().organisationUnits()
128+
.uid(orgUnitUid)
129+
.blockingGet()?.displayName() ?: orgUnitUid,
130+
)
131+
132+
DataSetNonEditableReason.PERIOD_IS_NOT_IN_ATTRIBUTE_OPTION_RANGE ->
133+
NonEditableReason.PeriodIsNotInAttributeOptionComboRange(
134+
periodLabel,
135+
d2.categoryModule().categoryOptionCombos()
136+
.uid(attrOptionComboUid)
137+
.blockingGet()?.displayName() ?: attrOptionComboUid,
138+
)
139+
140+
DataSetNonEditableReason.CLOSED ->
141+
NonEditableReason.Closed
142+
143+
DataSetNonEditableReason.EXPIRED ->
144+
NonEditableReason.Expired
145+
}
146+
} ?: NonEditableReason.None,
147+
)
148+
}
149+
70150
return d2.dataSetModule().dataSetInstances()
71-
.byDataSetUid().eq(dataSetUid)
72-
.byPeriod().eq(periodId)
73-
.byOrganisationUnitUid().eq(orgUnitUid)
74-
.byAttributeOptionComboUid().eq(attrOptionComboUid)
75-
.blockingGet()
76-
.map { dataSetInstance ->
77-
val period = d2.periodModule().periods().byPeriodId().eq(dataSetInstance.period())
78-
.one().blockingGet()
79-
80-
dataSetInstance.toDataSetDetails(
81-
periodLabel = period?.let {
82-
periodLabelProvider(
83-
periodType = period.periodType(),
84-
periodId = period.periodId()!!,
85-
periodStartDate = period.startDate()!!,
86-
periodEndDate = period.endDate()!!,
87-
locale = Locale.getDefault(),
88-
)
89-
} ?: dataSetInstance.period(),
90-
isDefaultCatCombo = isDefaultCatCombo == true,
91-
customText = dataSetDTOCustomTitle,
92-
)
93-
}
94-
.firstOrNull() ?: DataSetDetails(
151+
.dataSetInstance(
152+
dataSet = dataSetUid,
153+
period = periodId,
154+
organisationUnit = orgUnitUid,
155+
attributeOptionCombo = attrOptionComboUid,
156+
)
157+
.blockingGet()?.toDataSetDetails(
158+
periodLabel = periodLabel,
159+
isDefaultCatCombo = isDefaultCatCombo == true,
160+
customText = dataSetDTOCustomTitle,
161+
isCompleted = isComplete(dataSetUid, periodId, orgUnitUid, attrOptionComboUid),
162+
edition = edition,
163+
) ?: DataSetDetails(
95164
customTitle = dataSetDTOCustomTitle.toCustomTitle(),
96165
dataSetTitle = dataSet?.displayName()!!,
97166
dateLabel = periodId,
@@ -103,6 +172,8 @@ internal class DataSetInstanceRepositoryImpl(
103172
.uid(attrOptionComboUid)
104173
.blockingGet()
105174
?.displayName(),
175+
isCompleted = isComplete(dataSetUid, periodId, orgUnitUid, attrOptionComboUid),
176+
edition = edition,
106177
)
107178
}
108179

@@ -154,6 +225,7 @@ internal class DataSetInstanceRepositoryImpl(
154225
)
155226

156227
private suspend fun categoryOptionCombinations(
228+
categoryCombinationUid: String,
157229
categoryUids: List<String>,
158230
pivotedCategoryUid: String?,
159231
): List<String> {
@@ -174,6 +246,7 @@ internal class DataSetInstanceRepositoryImpl(
174246
}.mapNotNull { categoryOptions ->
175247
if (pivotedCategoryUid == null) {
176248
d2.categoryModule().categoryOptionCombos()
249+
.byCategoryComboUid().eq(categoryCombinationUid)
177250
.byCategoryOptions(categoryOptions)
178251
.one()
179252
.blockingGet()?.uid()
@@ -262,7 +335,22 @@ internal class DataSetInstanceRepositoryImpl(
262335
.uid(sectionUid)
263336
.blockingGet()
264337
?.greyedFields()
265-
?.mapNotNull { it.dataElement()?.uid() }
338+
?.mapNotNull {
339+
val dataElementUid = it.dataElement()?.uid() ?: return@mapNotNull null
340+
val categoryOptionComboUid =
341+
it.categoryOptionCombo()?.uid() ?: return@mapNotNull null
342+
val categoryOptionUids = d2.categoryModule().categoryOptionCombos()
343+
.withCategoryOptions()
344+
.uid(categoryOptionComboUid)
345+
.blockingGet()
346+
?.categoryOptions()?.map { it.uid() } ?: emptyList()
347+
348+
GreyedOutField(
349+
dataElementUid,
350+
categoryOptionComboUid,
351+
categoryOptionUids,
352+
)
353+
}
266354

267355
val isEditable = d2.dataSetModule().dataSetInstanceService()
268356
.getEditableStatus(dataSetUid, periodId, orgUnitUid, attrOptionComboUid)
@@ -371,7 +459,11 @@ internal class DataSetInstanceRepositoryImpl(
371459
subgroups = subGroups,
372460
cellElements = noGroupingDataSetElements,
373461
headerRows = getTableGroupHeaders(catComboUid!!, subGroups, pivotedCategory),
374-
headerCombinations = categoryOptionCombinations(subGroups, pivotedCategory),
462+
headerCombinations = categoryOptionCombinations(
463+
categoryCombinationUid = catCombo.uid(),
464+
categoryUids = subGroups,
465+
pivotedCategoryUid = pivotedCategory,
466+
),
375467
pivotMode = when {
376468
pivoted ->
377469
PivoteMode.Transpose
@@ -423,8 +515,9 @@ internal class DataSetInstanceRepositoryImpl(
423515
pivotedCategory,
424516
),
425517
headerCombinations = categoryOptionCombinations(
426-
subGroups,
427-
pivotedCategory,
518+
categoryCombinationUid = catCombo.uid(),
519+
categoryUids = subGroups,
520+
pivotedCategoryUid = pivotedCategory,
428521
),
429522
pivotMode = when {
430523
pivoted ->
@@ -473,13 +566,13 @@ internal class DataSetInstanceRepositoryImpl(
473566
.byPeriod().eq(periodId)
474567
.byOrganisationUnitUid().eq(orgUnitUid)
475568
.byAttributeOptionCombo().eq(catOptCombo)
476-
.blockingGet()?.mapNotNull { dataValueConflict ->
569+
.blockingGet().mapNotNull { dataValueConflict ->
477570
dataValueConflict.dataElement()?.let { dataElementUid ->
478571
sections.filter { it.value?.contains(dataElementUid) == true }.keys
479572
}
480-
}?.flatten()
573+
}.flatten()
481574

482-
return sectionWithError?.firstOrNull()?.let {
575+
return sectionWithError.firstOrNull()?.let {
483576
sections.keys.indexOf(it)
484577
} ?: 0
485578
} else {
@@ -831,6 +924,17 @@ internal class DataSetInstanceRepositoryImpl(
831924
}
832925
}
833926

927+
override suspend fun reopenDataSet(
928+
dataSetUid: String,
929+
periodId: String,
930+
orgUnitUid: String,
931+
attributeOptionComboUid: String,
932+
) {
933+
d2.dataSetModule().dataSetCompleteRegistrations()
934+
.value(periodId, orgUnitUid, dataSetUid, attributeOptionComboUid)
935+
.blockingDeleteIfExist()
936+
}
937+
834938
override suspend fun runValidationRules(
835939
dataSetUid: String,
836940
periodId: String,

aggregates/src/androidMain/kotlin/org/dhis2/mobile/aggregates/data/mappers/DataSetInstanceToDataSetDetails.kt

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.dhis2.mobile.aggregates.data.mappers
22

33
import org.dhis2.mobile.aggregates.model.DataSetCustomTitle
44
import org.dhis2.mobile.aggregates.model.DataSetDetails
5+
import org.dhis2.mobile.aggregates.model.DataSetEdition
56
import org.dhis2.mobile.aggregates.model.TextAlignment
67
import org.hisp.dhis.android.core.dataset.CustomText
78
import org.hisp.dhis.android.core.dataset.DataSetInstance
@@ -11,6 +12,8 @@ internal fun DataSetInstance.toDataSetDetails(
1112
isDefaultCatCombo: Boolean,
1213
customText: CustomText?,
1314
periodLabel: String,
15+
isCompleted: Boolean,
16+
edition: DataSetEdition,
1417
) = DataSetDetails(
1518
customTitle = customText?.toCustomTitle() ?: DataSetCustomTitle(
1619
header = null,
@@ -26,6 +29,8 @@ internal fun DataSetInstance.toDataSetDetails(
2629
} else {
2730
this.attributeOptionComboDisplayName()
2831
},
32+
isCompleted = isCompleted,
33+
edition = edition,
2934
)
3035

3136
internal fun CustomText?.toCustomTitle() = DataSetCustomTitle(

aggregates/src/commonMain/composeResources/values/strings.xml

+27
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,31 @@
4848
<string name="empty_dataset_message">This data set is misconfigured.\nContact your administrator.</string>
4949

5050
<string name="indicators_label">Indicators</string>
51+
<string name="no_data_write_access">You do not have permission to edit this data</string>
52+
<!--
53+
%1$s is the attribute option combo name
54+
-->
55+
<string name="attribute_option_combo_no_access">You do not have permission to edit data for %1$s</string>
56+
<!--
57+
%1$s is the organisation unit name
58+
-->
59+
<string name="org_unit_not_in_capture_scope">You cannot edit data from %1$s</string>
60+
<!--
61+
%1$s is the attribute option combo name
62+
%2$s is the organisation unit name
63+
-->
64+
<string name="attribute_option_combo_not_assigned_to_org_unit">You cannot edit data for %1$s in %2$s</string>
65+
<!--
66+
%1$s is the period name
67+
%2$s is the organisation unit name
68+
-->
69+
<string name="period_not_in_org_unit_range">You cannot edit data for %1$s in %2$s</string>
70+
<!--
71+
%1$s is the period name
72+
%2$s is the attribute option combo name
73+
-->
74+
<string name="period_not_in_attribute_option_combo_range">You cannot edit data for the period %1$s in %2$s</string>
75+
<string name="dataset_closed">This data is not editable because it is marked as closed</string>
76+
<string name="dataset_expired">This data is not editable because its edition time has expired</string>
77+
5178
</resources>

aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/data/DataSetInstanceRepository.kt

+7
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ internal interface DataSetInstanceRepository {
137137
attributeOptionComboUid: String,
138138
): Result<Unit>
139139

140+
suspend fun reopenDataSet(
141+
dataSetUid: String,
142+
periodId: String,
143+
orgUnitUid: String,
144+
attributeOptionComboUid: String,
145+
)
146+
140147
suspend fun runValidationRules(
141148
dataSetUid: String,
142149
periodId: String,

aggregates/src/commonMain/kotlin/org/dhis2/mobile/aggregates/di/AggregateModule.kt

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.dhis2.mobile.aggregates.domain.GetDataSetSectionData
1010
import org.dhis2.mobile.aggregates.domain.GetDataSetSectionIndicators
1111
import org.dhis2.mobile.aggregates.domain.GetDataValueData
1212
import org.dhis2.mobile.aggregates.domain.GetDataValueInput
13+
import org.dhis2.mobile.aggregates.domain.ReopenDataSet
1314
import org.dhis2.mobile.aggregates.domain.RunValidationRules
1415
import org.dhis2.mobile.aggregates.domain.SetDataValue
1516
import org.dhis2.mobile.aggregates.domain.UploadFile
@@ -132,6 +133,16 @@ internal val featureModule = module {
132133
)
133134
}
134135

136+
factory { params ->
137+
ReopenDataSet(
138+
dataSetUid = params.get(),
139+
periodId = params.get(),
140+
orgUnitUid = params.get(),
141+
attrOptionComboUid = params.get(),
142+
dataSetInstanceRepository = get(),
143+
)
144+
}
145+
135146
factory { params ->
136147
RunValidationRules(
137148
dataSetUid = params.get(),
@@ -207,6 +218,9 @@ internal val featureModule = module {
207218
completeDataSet = get {
208219
parametersOf(dataSetUid, periodId, orgUnitUid, attrOptionComboUid)
209220
},
221+
reopenDataSet = get {
222+
parametersOf(dataSetUid, periodId, orgUnitUid, attrOptionComboUid)
223+
},
210224
dispatcher = get(),
211225
runValidationRules = get {
212226
parametersOf(dataSetUid, periodId, orgUnitUid, attrOptionComboUid)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.dhis2.mobile.aggregates.domain
2+
3+
import org.dhis2.mobile.aggregates.data.DataSetInstanceRepository
4+
5+
internal class ReopenDataSet(
6+
private val dataSetUid: String,
7+
private val periodId: String,
8+
private val orgUnitUid: String,
9+
private val attrOptionComboUid: String,
10+
private val dataSetInstanceRepository: DataSetInstanceRepository,
11+
) {
12+
suspend operator fun invoke() {
13+
dataSetInstanceRepository.reopenDataSet(
14+
dataSetUid = dataSetUid,
15+
periodId = periodId,
16+
orgUnitUid = orgUnitUid,
17+
attributeOptionComboUid = attrOptionComboUid,
18+
)
19+
}
20+
}

0 commit comments

Comments
 (0)