Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Save draft MVP functionality #3596

Merged
merged 14 commits into from
Nov 13, 2024
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1. Added a new class (PdfGenerator) for generating PDF documents from HTML content using Android's WebView and PrintManager
2. Introduced a new class (HtmlPopulator) to populate HTML templates with data from a Questionnaire Response
3. Implemented functionality to launch PDF generation using a configuration setup
- Added Save draft MVP functionality

## [1.1.0] - 2024-02-15

Original file line number Diff line number Diff line change
@@ -55,6 +55,8 @@ object AlertDialogue {
@StringRes confirmButtonText: Int = R.string.questionnaire_alert_confirm_button_title,
neutralButtonListener: ((d: DialogInterface) -> Unit)? = null,
@StringRes neutralButtonText: Int = R.string.questionnaire_alert_neutral_button_title,
negativeButtonListener: ((d: DialogInterface) -> Unit)? = null,
@StringRes negativeButtonText: Int = R.string.questionnaire_alert_negative_button_title,
cancellable: Boolean = false,
options: Array<AlertDialogListItem>? = null,
): AlertDialog {
@@ -71,6 +73,9 @@ object AlertDialogue {
confirmButtonListener?.let {
setPositiveButton(confirmButtonText) { d, _ -> confirmButtonListener.invoke(d) }
}
negativeButtonListener?.let {
setNegativeButton(negativeButtonText) { d, _ -> negativeButtonListener.invoke(d) }
}
options?.run { setSingleChoiceItems(options.map { it.value }.toTypedArray(), -1, null) }
}
.show()
@@ -172,6 +177,8 @@ object AlertDialogue {
@StringRes confirmButtonText: Int,
neutralButtonListener: ((d: DialogInterface) -> Unit),
@StringRes neutralButtonText: Int,
negativeButtonListener: ((d: DialogInterface) -> Unit),
@StringRes negativeButtonText: Int,
cancellable: Boolean = true,
options: List<AlertDialogListItem>? = null,
): AlertDialog {
@@ -184,6 +191,8 @@ object AlertDialogue {
confirmButtonText = confirmButtonText,
neutralButtonListener = neutralButtonListener,
neutralButtonText = neutralButtonText,
negativeButtonListener = negativeButtonListener,
negativeButtonText = negativeButtonText,
cancellable = cancellable,
options = options?.toTypedArray(),
)
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus
import org.hl7.fhir.r4.model.StringType
import org.smartregister.fhircore.engine.configuration.LinkIdType
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
@@ -292,3 +293,19 @@
}
}
}

/**
* Determines the [QuestionnaireResponse.Status] depending on the [saveDraft] and [isEditable]
* values contained in the [QuestionnaireConfig]
*
* returns [COMPLETED] when [isEditable] is [true] returns [INPROGRESS] when [saveDraft] is [true]
*/
fun QuestionnaireConfig.questionnaireResponseStatus(): String? {
return if (this.isEditable()) {
QuestionnaireResponseStatus.COMPLETED.toCode()
} else if (this.saveDraft) {
QuestionnaireResponseStatus.INPROGRESS.toCode()

Check warning on line 307 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt#L307

Added line #L307 was not covered by tests
} else {
null

Check warning on line 309 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt#L309

Added line #L309 was not covered by tests
}
}
11 changes: 7 additions & 4 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -65,11 +65,12 @@
<string name="error_saving_form">Error encountered cannot save form</string>
<string name="form_progress_message">Processing data. Please wait</string>
<string name="questionnaire_alert_back_pressed_message">Are you sure you want to go back?</string>
<string name="questionnaire_in_progress_alert_back_pressed_message">Are you sure you want to discard the answers?</string>
<string name="questionnaire_alert_back_pressed_title">Discard changes</string>
<string name="questionnaire_alert_back_pressed_button_title">Discard</string>
<string name="questionnaire_alert_back_pressed_save_draft_button_title">Save partial draft</string>
<string name="questionnaire_in_progress_alert_back_pressed_message">If you leave without saving, all your changes will not be saved</string>
<string name="questionnaire_alert_back_pressed_title">You have unsaved changes</string>
<string name="questionnaire_alert_back_pressed_button_title">Discard changes</string>
<string name="questionnaire_alert_back_pressed_save_draft_button_title">Save as draft</string>
<string name="questionnaire_alert_neutral_button_title">Cancel</string>
<string name="questionnaire_alert_negative_button_title">Discard Changes</string>
<string name="questionnaire_alert_confirm_button_title">Yes</string>
<string name="questionnaire_alert_invalid_message">Given details have validation errors. Resolve errors and submit again</string>
<string name="questionnaire_alert_invalid_title">Validation Failed</string>
@@ -198,4 +199,6 @@
<string name="unsynced_data_present">There\'s some un-synced data</string>
<string name="missing_supervisor_contact">Supervisor contact missing or the provided phone number is invalid</string>
<string name="apply_filter">APPLY FILTER</string>
<string name="questionnaire_save_draft_title">Save draft changes</string>
<string name="questionnaire_save_draft_message">Do you want to save draft changes?</string>
</resources>
Original file line number Diff line number Diff line change
@@ -151,6 +151,8 @@ class AlertDialogueTest : ActivityRobolectricTest() {
confirmButtonText = R.string.questionnaire_alert_back_pressed_save_draft_button_title,
neutralButtonListener = {},
neutralButtonText = R.string.questionnaire_alert_back_pressed_button_title,
negativeButtonListener = {},
negativeButtonText = R.string.questionnaire_alert_negative_button_title,
)
val dialog = shadowOf(ShadowAlertDialog.getLatestAlertDialog())

Original file line number Diff line number Diff line change
@@ -471,4 +471,21 @@ class QuestionnaireExtensionTest : RobolectricTest() {
barCodeItemValue?.primitiveValue(),
)
}

@Test
fun testQuestionnaireResponseStatusReturnsCompletedWhenIsEditableIsTrue() {
val questionnaireConfig =
QuestionnaireConfig(id = "patient-reg-config", type = QuestionnaireType.EDIT.name)
Assert.assertEquals("completed", questionnaireConfig.questionnaireResponseStatus())
}

fun testQuestionnaireResponseStatusReturnsInProgressWhenSaveDraftIsTrue() {
val questionnaireConfig = QuestionnaireConfig(id = "patient-reg-config", saveDraft = true)
Assert.assertEquals("in-progress", questionnaireConfig.questionnaireResponseStatus())
}

fun testQuestionnaireResponseStatusReturnsNullWhenBothSaveDraftAndIsEditableAreFalse() {
val questionnaireConfig = QuestionnaireConfig(id = "patient-reg-config", saveDraft = true)
Assert.assertEquals("in-progress", questionnaireConfig.questionnaireResponseStatus())
}
}
Original file line number Diff line number Diff line change
@@ -366,16 +366,20 @@
confirmButtonListener = {
lifecycleScope.launch {
retrieveQuestionnaireResponse()?.let { questionnaireResponse ->
viewModel.saveDraftQuestionnaire(questionnaireResponse)
viewModel.saveDraftQuestionnaire(questionnaireResponse, questionnaireConfig)
finish()

Check warning on line 370 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L370

Added line #L370 was not covered by tests
}
}
},
confirmButtonText =
org.smartregister.fhircore.engine.R.string
.questionnaire_alert_back_pressed_save_draft_button_title,
neutralButtonListener = { finish() },
neutralButtonListener = {},

Check warning on line 377 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L377

Added line #L377 was not covered by tests
neutralButtonText =
org.smartregister.fhircore.engine.R.string.questionnaire_alert_back_pressed_button_title,
org.smartregister.fhircore.engine.R.string.questionnaire_alert_neutral_button_title,
negativeButtonListener = { finish() },

Check warning on line 380 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L379-L380

Added lines #L379 - L380 were not covered by tests
negativeButtonText =
org.smartregister.fhircore.engine.R.string.questionnaire_alert_negative_button_title,

Check warning on line 382 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt#L382

Added line #L382 was not covered by tests
)
} else {
AlertDialogue.showConfirmAlert(
Original file line number Diff line number Diff line change
@@ -95,6 +95,7 @@
import org.smartregister.fhircore.engine.util.extension.logErrorMessages
import org.smartregister.fhircore.engine.util.extension.packRepeatedGroups
import org.smartregister.fhircore.engine.util.extension.prepopulateWithComputedConfigValues
import org.smartregister.fhircore.engine.util.extension.questionnaireResponseStatus
import org.smartregister.fhircore.engine.util.extension.showToast
import org.smartregister.fhircore.engine.util.extension.updateLastUpdated
import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor
@@ -544,6 +545,7 @@
resourceType = questionnaireConfig.resourceType ?: subjectType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),

Check warning on line 548 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L548

Added line #L548 was not covered by tests
)
?.contained
?.asSequence()
@@ -672,12 +674,35 @@
* This function saves [QuestionnaireResponse] as draft if any of the [QuestionnaireResponse.item]
* has an answer.
*/
fun saveDraftQuestionnaire(questionnaireResponse: QuestionnaireResponse) {
fun saveDraftQuestionnaire(
questionnaireResponse: QuestionnaireResponse,
questionnaireConfig: QuestionnaireConfig,
) {
viewModelScope.launch {
val hasPages = questionnaireResponse.item.any { it.hasItem() }

Check warning on line 682 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L682

Added line #L682 was not covered by tests
val questionnaireHasAnswer =
questionnaireResponse.item.any {
it.answer.any { answerComponent -> answerComponent.hasValue() }
if (!hasPages) {
it.answer.any { answerComponent -> answerComponent.hasValue() }

Check warning on line 686 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L686

Added line #L686 was not covered by tests
} else {
questionnaireResponse.item.any { page ->
page.item.any { pageItem ->
pageItem.answer.any { answerComponent -> answerComponent.hasValue() }

Check warning on line 690 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L688-L690

Added lines #L688 - L690 were not covered by tests
}
}
}
}
questionnaireResponse.questionnaire =
questionnaireConfig.id.asReference(ResourceType.Questionnaire).reference

Check warning on line 696 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L695-L696

Added lines #L695 - L696 were not covered by tests
if (
!questionnaireConfig.resourceIdentifier.isNullOrBlank() &&
questionnaireConfig.resourceType != null
) {
questionnaireResponse.subject =
questionnaireConfig.resourceIdentifier!!.asReference(
questionnaireConfig.resourceType!!,

Check warning on line 703 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L701-L703

Added lines #L701 - L703 were not covered by tests
)
}
if (questionnaireHasAnswer) {
questionnaireResponse.status = QuestionnaireResponse.QuestionnaireResponseStatus.INPROGRESS
defaultRepository.addOrUpdate(
@@ -1011,6 +1036,7 @@
resourceType: ResourceType,
questionnaireId: String,
encounterId: String?,
questionnaireResponseStatus: String? = null,

Check warning on line 1039 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L1039

Added line #L1039 was not covered by tests
): QuestionnaireResponse? {
val search =
Search(ResourceType.QuestionnaireResponse).apply {
@@ -1031,6 +1057,12 @@
},
)
}
if (!questionnaireResponseStatus.isNullOrBlank()) {
filter(
QuestionnaireResponse.STATUS,

Check warning on line 1062 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L1061-L1062

Added lines #L1061 - L1062 were not covered by tests
{ value = of(questionnaireResponseStatus) },
)
}
}
val questionnaireResponses: List<QuestionnaireResponse> = defaultRepository.search(search)
return questionnaireResponses.maxByOrNull { it.meta.lastUpdated }
@@ -1100,13 +1132,16 @@
if (
resourceType != null &&
!resourceIdentifier.isNullOrEmpty() &&
(questionnaireConfig.isEditable() || questionnaireConfig.isReadOnly())
(questionnaireConfig.isEditable() ||
questionnaireConfig.isReadOnly() ||
questionnaireConfig.saveDraft)
) {
searchQuestionnaireResponse(
resourceId = resourceIdentifier,
resourceType = resourceType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),

Check warning on line 1144 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt#L1144

Added line #L1144 was not covered by tests
)
?.let {
QuestionnaireResponse().apply {
Loading