Skip to content

Commit

Permalink
Add Related entity location tag on newly created resources via configs (
Browse files Browse the repository at this point in the history
#3086)

* Introduce linkIds configuration to Questionnaire

This property will be used to provide linkIds as configuration in a
more flexible way.

Signed-off-by: Elly Kitoto <[email protected]>

* Add related entity location metadata tag on resources via configuration

Signed-off-by: Elly Kitoto <[email protected]>

* Fix adding related entity location and app version resource metadata tags

Signed-off-by: Elly Kitoto <[email protected]>

* Set resource entity location tag on Task and CarePlan resources

Signed-off-by: Elly Kitoto <[email protected]>

* Fix location tagging for QuestionnaireResponse

Fix an issue with tagging QR generated for Patient resources that
are members of a Group.

Signed-off-by: Elly Kitoto <[email protected]>

* Test appendRelatedEntityLocation in resource meta tag

Signed-off-by: Elly Kitoto <[email protected]>

* Test that Related Entity Location is appended to resource meta tag

Signed-off-by: Elly Kitoto <[email protected]>

* Document QuestionnaireConfig.linkIds property

Signed-off-by: Elly Kitoto <[email protected]>

* Use both system and code to filter related entity location tag

Signed-off-by: Elly Kitoto <[email protected]>

---------

Signed-off-by: Elly Kitoto <[email protected]>
Co-authored-by: Peter Lubell-Doughtie <[email protected]>
  • Loading branch information
ellykits and pld authored Feb 21, 2024
1 parent 30acdee commit 04110ad
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.configuration.event.EventWorkflow
import org.smartregister.fhircore.engine.domain.model.ActionConfig
import org.smartregister.fhircore.engine.domain.model.ActionParameter
import org.smartregister.fhircore.engine.domain.model.QuestionnaireType
import org.smartregister.fhircore.engine.domain.model.RuleConfig
import org.smartregister.fhircore.engine.domain.model.SnackBarMessageConfig
import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid
Expand All @@ -35,7 +36,7 @@ data class QuestionnaireConfig(
val title: String? = null,
val saveButtonText: String? = null,
val planDefinitions: List<String>? = null,
var type: String = "DEFAULT",
var type: String = QuestionnaireType.DEFAULT.name,
val resourceIdentifier: String? = null,
val resourceType: ResourceType? = null,
val removeResource: Boolean? = null,
Expand All @@ -49,7 +50,7 @@ data class QuestionnaireConfig(
val configRules: List<RuleConfig>? = null,
val extraParams: List<ActionParameter>? = null,
val onSubmitActions: List<ActionConfig>? = null,
val barcodeLinkId: String = "patient-barcode",
val barcodeLinkId: String? = "patient-barcode",
val extractedResourceUniquePropertyExpressions: List<ExtractedResourceUniquePropertyExpression>? =
null,
val saveQuestionnaireResponse: Boolean = true,
Expand All @@ -59,6 +60,7 @@ data class QuestionnaireConfig(
val showRequiredTextAsterisk: Boolean = true,
val showRequiredText: Boolean = false,
val managingEntityRelationshipCode: String? = null,
val linkIds: List<LinkIdConfig>? = null,
) : java.io.Serializable, Parcelable {

fun interpolate(computedValuesMap: Map<String, Any>) =
Expand All @@ -85,8 +87,9 @@ data class QuestionnaireConfig(
planDefinitions = planDefinitions?.map { it.interpolate(computedValuesMap) },
readOnlyLinkIds = readOnlyLinkIds?.map { it.interpolate(computedValuesMap) },
onSubmitActions = onSubmitActions?.map { it.interpolate(computedValuesMap) },
barcodeLinkId = barcodeLinkId.interpolate(computedValuesMap),
barcodeLinkId = barcodeLinkId?.interpolate(computedValuesMap),
cqlInputResources = cqlInputResources?.map { it.interpolate(computedValuesMap) },
linkIds = linkIds?.onEach { it.linkId.interpolate(computedValuesMap) },
)
}

Expand Down Expand Up @@ -114,3 +117,19 @@ data class ExtractedResourceUniquePropertyExpression(
val resourceType: ResourceType,
val fhirPathExpression: String,
) : java.io.Serializable, Parcelable

@Serializable
@Parcelize
data class LinkIdConfig(
val linkId: String,
val type: LinkIdType,
) : java.io.Serializable, Parcelable

@Serializable
@Parcelize
enum class LinkIdType : Parcelable {
READ_ONLY,
BARCODE,
LOCATION,
IDENTIFIER,
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,32 @@ interface ConfigService {
fun provideResourceTags(sharedPreferencesHelper: SharedPreferencesHelper): List<Coding> {
val tags = mutableListOf<Coding>()
defineResourceTags().forEach { strategy ->
if (strategy.type == ResourceType.Practitioner.name) {
val id = sharedPreferencesHelper.read(SharedPreferenceKey.PRACTITIONER_ID.name, null)
if (id.isNullOrBlank()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
strategy.tag.let { tag ->
tags.add(tag.copy().apply { code = id.extractLogicalIdUuid() })
}
}
} else {
val ids = sharedPreferencesHelper.read<List<String>>(strategy.type)
if (ids.isNullOrEmpty()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
ids.forEach { id ->
when (strategy.type) {
ResourceType.Practitioner.name -> {
val id = sharedPreferencesHelper.read(SharedPreferenceKey.PRACTITIONER_ID.name, null)
if (id.isNullOrBlank() || id.isEmpty()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
strategy.tag.let { tag ->
tags.add(tag.copy().apply { code = id.extractLogicalIdUuid() })
}
}
}
APP_VERSION -> tags.add(strategy.tag.copy())
else -> {
val ids = sharedPreferencesHelper.read<List<String>>(strategy.type)
if (ids.isNullOrEmpty()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
ids.forEach { id ->
strategy.tag.let { tag ->
tags.add(tag.copy().apply { code = id.extractLogicalIdUuid() })
}
}
}
}
}
}

return tags
}

Expand Down Expand Up @@ -91,5 +94,6 @@ interface ConfigService {

companion object {
const val ACTIVE_SEARCH_PARAM = "active"
const val APP_VERSION = "AppVersion"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

package org.smartregister.fhircore.engine.task

import android.content.Context
import androidx.annotation.VisibleForTesting
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.util.TerserUtil
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.get
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.search
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -33,6 +35,7 @@ import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CanonicalType
import org.hl7.fhir.r4.model.CarePlan
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.Dosage
import org.hl7.fhir.r4.model.Expression
Expand All @@ -51,6 +54,7 @@ import org.hl7.fhir.r4.model.Timing
import org.hl7.fhir.r4.model.Timing.UnitsOfTime
import org.hl7.fhir.r4.utils.FHIRPathEngine
import org.hl7.fhir.r4.utils.StructureMapUtilities
import org.smartregister.fhircore.engine.R
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
import org.smartregister.fhircore.engine.configuration.event.EventType
import org.smartregister.fhircore.engine.data.local.DefaultRepository
Expand All @@ -76,6 +80,7 @@ constructor(
val defaultRepository: DefaultRepository,
val fhirResourceUtil: FhirResourceUtil,
val workflowCarePlanGenerator: WorkflowCarePlanGenerator,
@ApplicationContext val context: Context,
) {
private val structureMapUtilities by lazy {
StructureMapUtilities(transformSupportServices.simpleWorkerContext, transformSupportServices)
Expand All @@ -85,20 +90,34 @@ constructor(
planDefinitionId: String,
subject: Resource,
data: Bundle = Bundle(),
relatedEntityLocationCode: String? = null,
generateCarePlanWithWorkflowApi: Boolean = false,
): CarePlan? {
val planDefinition = defaultRepository.loadResource<PlanDefinition>(planDefinitionId)
return planDefinition?.let {
generateOrUpdateCarePlan(it, subject, data, generateCarePlanWithWorkflowApi)
generateOrUpdateCarePlan(
planDefinition = it,
subject = subject,
data = data,
relatedEntityLocationCode = relatedEntityLocationCode,
generateCarePlanWithWorkflowApi = generateCarePlanWithWorkflowApi,
)
}
}

suspend fun generateOrUpdateCarePlan(
planDefinition: PlanDefinition,
subject: Resource,
data: Bundle = Bundle(),
relatedEntityLocationCode: String? = null,
generateCarePlanWithWorkflowApi: Boolean = false,
): CarePlan? {
val relatedEntityLocationTags =
subject.meta.tag.filter {
it.system == context.getString(R.string.sync_strategy_related_entity_location_system) &&
it.code == relatedEntityLocationCode
}

// Only one CarePlan per plan, update or init a new one if not exists
val output =
fhirEngine
Expand All @@ -121,25 +140,26 @@ constructor(
this.title = planDefinition.title
this.description = planDefinition.description
this.instantiatesCanonical = listOf(CanonicalType(planDefinition.asReference().reference))
// Add the subject's Related Entity Location tag to the CarePlan
relatedEntityLocationTags.forEach(this.meta::addTag)
}

var carePlanModified = false

if (generateCarePlanWithWorkflowApi) {
workflowCarePlanGenerator.applyPlanDefinitionOnPatient(
planDefinition = planDefinition,
patient = subject as Patient,
data = data,
output = output,
)
carePlanModified = true
} else {
carePlanModified = liteApplyPlanDefinitionOnPatient(planDefinition, data, subject, output)
}
val carePlanModified: Boolean =
if (generateCarePlanWithWorkflowApi) {
workflowCarePlanGenerator.applyPlanDefinitionOnPatient(
planDefinition = planDefinition,
patient = subject as Patient,
data = data,
output = output,
)
true
} else {
liteApplyPlanDefinitionOnPatient(planDefinition, data, subject, output)
}

val carePlanTasks = output.contained.filterIsInstance<Task>()

if (carePlanModified) saveCarePlan(output)
if (carePlanModified) saveCarePlan(output, relatedEntityLocationTags)

if (carePlanTasks.isNotEmpty()) {
fhirResourceUtil.updateUpcomingTasksToDue(
Expand Down Expand Up @@ -217,7 +237,7 @@ constructor(
return carePlanModified
}

private suspend fun saveCarePlan(output: CarePlan) {
private suspend fun saveCarePlan(output: CarePlan, relatedEntityLocationTags: List<Coding>) {
output
.also { Timber.d(it.encodeResourceToString()) }
.also { carePlan ->
Expand All @@ -228,7 +248,10 @@ constructor(

defaultRepository.addOrUpdate(true, carePlan)

dependents.forEach { defaultRepository.addOrUpdate(true, it) }
dependents.forEach {
relatedEntityLocationTags.forEach(it.meta::addTag)
defaultRepository.addOrUpdate(true, it)
}

if (carePlan.status == CarePlan.CarePlanStatus.COMPLETED) {
carePlan.activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.smartregister.fhircore.engine.util.extension

import android.content.Context
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.parser.IParser
import ca.uhn.fhir.rest.gclient.ReferenceClientParam
Expand Down Expand Up @@ -54,12 +55,16 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.StringType
import org.hl7.fhir.r4.model.StructureMap
import org.hl7.fhir.r4.model.Task
import org.hl7.fhir.r4.model.Timing
import org.hl7.fhir.r4.model.Type
import org.joda.time.Instant
import org.json.JSONException
import org.json.JSONObject
import org.smartregister.fhircore.engine.configuration.LinkIdType
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData
import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor
Expand Down Expand Up @@ -300,6 +305,39 @@ fun Resource.appendPractitionerInfo(practitionerId: String?) {
}
}

fun Resource.appendRelatedEntityLocation(
questionnaireResponse: QuestionnaireResponse,
questionnaireConfig: QuestionnaireConfig,
context: Context,
) {
val locationCoding =
Coding().apply {
system =
context.getString(
org.smartregister.fhircore.engine.R.string.sync_strategy_related_entity_location_system,
)
display =
context.getString(
org.smartregister.fhircore.engine.R.string.sync_strategy_related_entity_location_display,
)
}
questionnaireConfig.linkIds
?.filter { it.type == LinkIdType.LOCATION }
?.forEach { linkIdConfig ->
val answer: Type? = questionnaireResponse.find(linkIdConfig.linkId)?.answerFirstRep?.value
val locationId =
when (answer) {
is Reference -> answer.reference.extractLogicalIdUuid()
is StringType -> answer.value.extractLogicalIdUuid()
else -> null
}
val existingTag = this.meta.getTag(locationCoding.system, locationId)
if (!locationId.isNullOrEmpty() && existingTag == null) {
this.meta.addTag(locationCoding.apply { setCode(locationId) })
}
}
}

private fun updateReference(oldReference: Reference?, newReference: Reference): Reference =
if (oldReference == null || oldReference.reference.isNullOrEmpty()) {
newReference
Expand Down
16 changes: 9 additions & 7 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="sync">Manual Sync</string>
<string name="language">Language</string>
<string name="logout_as_user">Log out as </string>
Expand Down Expand Up @@ -76,7 +76,7 @@
<string name="forgot_password_title">Forgot Password!</string>
<string name="more">More</string>
<string name="call_supervisor">Please call your supervisor at %1$s</string>
<string name="cancel">CANCEL</string>
<string name="cancel" tools:ignore="ButtonCase">CANCEL</string>
<string name="dial_number">DIAL NUMBER</string>
<string name="register">Register</string>
<string name="visits">Visits</string>
Expand Down Expand Up @@ -135,18 +135,20 @@
<string name="sync_strategy_location_system" translatable="false">https://smartregister.org/location-tag-id</string>
<string name="sync_strategy_organization_system" translatable="false">https://smartregister.org/organisation-tag-id</string>
<string name="sync_strategy_practitioner_system" translatable="false">https://smartregister.org/practitioner-tag-id</string>
<string name="sync_strategy_careteam_display">Practitioner CareTeam</string>
<string name="sync_strategy_location_display">Practitioner Location</string>
<string name="sync_strategy_organization_display">Practitioner Organization</string>
<string name="sync_strategy_practitioner_display">Practitioner</string>
<string name="sync_strategy_related_entity_location_system" translatable="false">https://smartregister.org/related-entity-location-tag-id</string>
<string name="sync_strategy_related_entity_location_display" translatable="false">Related Entity Location</string>
<string name="sync_strategy_careteam_display" translatable="false">Practitioner CareTeam</string>
<string name="sync_strategy_location_display" translatable="false">Practitioner Location</string>
<string name="sync_strategy_organization_display" translatable="false">Practitioner Organization</string>
<string name="sync_strategy_practitioner_display" translatable="false">Practitioner</string>
<string name="year">Year</string>
<string name="month">Month</string>
<string name="weeks">Week(s)</string>
<string name="days">Days(s)</string>
<string name="initializing">Initializing settings &#8230;</string>
<string name="username_sample" translatable="false">e.g JohnDoe</string>
<string name="percentage_progress" translatable="false">%1$d%%</string>
<string name="error_occurred">Something went wrong...</string>
<string name="error_occurred">Something went wrong</string>
<string name="insights">Insights</string>
<string name="contact_help">"Contact help"</string>
<string name="offline_map">"Offline Maps"</string>
Expand Down
Loading

0 comments on commit 04110ad

Please sign in to comment.