@@ -39,7 +39,11 @@ import java.util.Calendar
39
39
import java.util.Date
40
40
import java.util.UUID
41
41
import javax.inject.Inject
42
- import kotlinx.coroutines.Dispatchers
42
+ import kotlin.time.Duration.Companion.milliseconds
43
+ import kotlinx.coroutines.FlowPreview
44
+ import kotlinx.coroutines.flow.MutableStateFlow
45
+ import kotlinx.coroutines.flow.debounce
46
+ import kotlinx.coroutines.flow.filter
43
47
import kotlinx.coroutines.launch
44
48
import kotlinx.coroutines.withContext
45
49
import org.hl7.fhir.r4.context.IWorkerContext
@@ -93,6 +97,7 @@ import org.smartregister.fhircore.engine.util.helper.TransformSupportServices
93
97
import timber.log.Timber
94
98
95
99
@HiltViewModel
100
+ @OptIn(FlowPreview ::class )
96
101
open class QuestionnaireViewModel
97
102
@Inject
98
103
constructor (
@@ -126,6 +131,17 @@ constructor(
126
131
?.extractLogicalIdUuid()
127
132
}
128
133
134
+ private val extractAndSaveRequestStateFlow: MutableStateFlow < suspend () -> Unit > =
135
+ MutableStateFlow {}
136
+
137
+ init {
138
+ viewModelScope.launch(dispatcherProvider.io()) {
139
+ extractAndSaveRequestStateFlow.debounce(800 .milliseconds).collect {
140
+ it.invoke() // invoke request
141
+ }
142
+ }
143
+ }
144
+
129
145
suspend fun loadQuestionnaire (id : String , type : QuestionnaireType ): Questionnaire ? =
130
146
defaultRepository.loadResource<Questionnaire >(id)?.apply {
131
147
if (type.isReadOnly() || type.isEditMode()) {
@@ -254,100 +270,124 @@ constructor(
254
270
questionnaireType : QuestionnaireType = QuestionnaireType .DEFAULT ,
255
271
questionnaire : Questionnaire ,
256
272
) {
257
- viewModelScope.launch(dispatcherProvider.io()) {
258
- tracer.startTrace(QUESTIONNAIRE_TRACE )
259
- // important to set response subject so that structure map can handle subject for all entities
260
- handleQuestionnaireResponseSubject(resourceId, questionnaire, questionnaireResponse)
261
- Timber .e(jsonParser.encodeResourceToString(questionnaireResponse))
262
- val extras = mutableListOf<Resource >()
263
- if (questionnaire.isExtractionCandidate()) {
264
- val bundle = performExtraction(context, questionnaire, questionnaireResponse)
265
- questionnaireResponse.contained = mutableListOf ()
266
- bundle.entry.forEach { bundleEntry ->
267
- // add organization to entities representing individuals in registration questionnaire
268
- if (bundleEntry.resource.resourceType.isIn(ResourceType .Patient , ResourceType .Group )) {
269
- if (questionnaireConfig.setOrganizationDetails) {
270
- appendOrganizationInfo(bundleEntry.resource)
271
- }
272
- // if it is new registration set response subject
273
- if (resourceId == null ) {
274
- questionnaireResponse.subject = bundleEntry.resource.asReference()
275
- }
276
- }
277
- if (questionnaireConfig.setPractitionerDetails) {
278
- appendPractitionerInfo(bundleEntry.resource)
279
- }
273
+ val request = suspend {
274
+ try {
275
+ val extras =
276
+ doExtractAndSaveResources(
277
+ context,
278
+ resourceId,
279
+ groupResourceId,
280
+ questionnaireResponse,
281
+ questionnaireType,
282
+ questionnaire,
283
+ )
284
+ extractionProgress.postValue(ExtractionProgress .Success (questionnaireResponse, extras))
285
+ } catch (e: Exception ) {
286
+ extractionProgress.postValue(ExtractionProgress .Failed (questionnaireResponse, e))
287
+ }
288
+ }
289
+
290
+ extractAndSaveRequestStateFlow.value = request
291
+ }
280
292
281
- if (
282
- questionnaireType != QuestionnaireType .EDIT &&
283
- bundleEntry.resource.resourceType.isIn(
284
- ResourceType .Patient ,
285
- ResourceType .RelatedPerson ,
286
- )
287
- ) {
288
- groupResourceId?.let {
289
- appendPatientsAndRelatedPersonsToGroups(
290
- resource = bundleEntry.resource,
291
- groupResourceId = it,
292
- )
293
- }
293
+ private suspend fun doExtractAndSaveResources (
294
+ context : Context ,
295
+ resourceId : String? ,
296
+ groupResourceId : String? = null,
297
+ questionnaireResponse : QuestionnaireResponse ,
298
+ questionnaireType : QuestionnaireType = QuestionnaireType .DEFAULT ,
299
+ questionnaire : Questionnaire ,
300
+ ): List <Resource > {
301
+ tracer.startTrace(QUESTIONNAIRE_TRACE )
302
+ // important to set response subject so that structure map can handle subject for all entities
303
+ handleQuestionnaireResponseSubject(resourceId, questionnaire, questionnaireResponse)
304
+ Timber .e(jsonParser.encodeResourceToString(questionnaireResponse))
305
+ val extras = mutableListOf<Resource >()
306
+ if (questionnaire.isExtractionCandidate()) {
307
+ val bundle = performExtraction(context, questionnaire, questionnaireResponse)
308
+ questionnaireResponse.contained = mutableListOf ()
309
+ bundle.entry.forEach { bundleEntry ->
310
+ // add organization to entities representing individuals in registration questionnaire
311
+ if (bundleEntry.resource.resourceType.isIn(ResourceType .Patient , ResourceType .Group )) {
312
+ if (questionnaireConfig.setOrganizationDetails) {
313
+ appendOrganizationInfo(bundleEntry.resource)
294
314
}
315
+ // if it is new registration set response subject
316
+ if (resourceId == null ) {
317
+ questionnaireResponse.subject = bundleEntry.resource.asReference()
318
+ }
319
+ }
320
+ if (questionnaireConfig.setPractitionerDetails) {
321
+ appendPractitionerInfo(bundleEntry.resource)
322
+ }
295
323
296
- // response MUST have subject by far otherwise flow has issues
297
- if (! questionnaire.experimental) questionnaireResponse.assertSubject()
298
-
299
- // TODO https://github.com/opensrp/fhircore/issues/900
300
- // for edit mode replace client and resource subject ids.
301
- // Ideally ResourceMapper should allow this internally via structure-map
302
- if (questionnaireType.isEditMode()) {
303
- if (bundleEntry.resource.resourceType.isIn(ResourceType .Patient , ResourceType .Group )) {
304
- bundleEntry.resource.id = questionnaireResponse.subject.extractId()
305
- } else {
306
- bundleEntry.resource.setPropertySafely(" subject" , questionnaireResponse.subject)
307
- bundleEntry.resource.setPropertySafely(" patient" , questionnaireResponse.subject)
308
- }
324
+ if (
325
+ questionnaireType != QuestionnaireType .EDIT &&
326
+ bundleEntry.resource.resourceType.isIn(
327
+ ResourceType .Patient ,
328
+ ResourceType .RelatedPerson ,
329
+ )
330
+ ) {
331
+ groupResourceId?.let {
332
+ appendPatientsAndRelatedPersonsToGroups(
333
+ resource = bundleEntry.resource,
334
+ groupResourceId = it,
335
+ )
309
336
}
310
- questionnaireResponse.contained.add(bundleEntry.resource)
337
+ }
311
338
312
- if (bundleEntry.resource is Encounter ) extras.add(bundleEntry.resource)
339
+ // response MUST have subject by far otherwise flow has issues
340
+ if (! questionnaire.experimental) questionnaireResponse.assertSubject()
313
341
314
- if (
315
- (bundleEntry.resource is CarePlan || bundleEntry.resource is Patient ) &&
316
- bundleEntry.resource.meta.tag.isNotEmpty()
317
- ) {
318
- carePlanAndPatientMetaExtraction(bundleEntry.resource)
342
+ // TODO https://github.com/opensrp/fhircore/issues/900
343
+ // for edit mode replace client and resource subject ids.
344
+ // Ideally ResourceMapper should allow this internally via structure-map
345
+ if (questionnaireType.isEditMode()) {
346
+ if (bundleEntry.resource.resourceType.isIn(ResourceType .Patient , ResourceType .Group )) {
347
+ bundleEntry.resource.id = questionnaireResponse.subject.extractId()
348
+ } else {
349
+ bundleEntry.resource.setPropertySafely(" subject" , questionnaireResponse.subject)
350
+ bundleEntry.resource.setPropertySafely(" patient" , questionnaireResponse.subject)
319
351
}
320
352
}
353
+ questionnaireResponse.contained.add(bundleEntry.resource)
321
354
322
- if (questionnaire.experimental) {
323
- Timber .w(
324
- " ${questionnaire.name} (${questionnaire.logicalId} ) is experimental and not save any data" ,
325
- )
326
- } else {
327
- saveBundleResources(bundle)
328
- }
355
+ if (bundleEntry.resource is Encounter ) extras.add(bundleEntry.resource)
329
356
330
- if (questionnaireType.isEditMode() && editQuestionnaireResponse != null ) {
331
- questionnaireResponse.retainMetadata(editQuestionnaireResponse!! )
357
+ if (
358
+ (bundleEntry.resource is CarePlan || bundleEntry.resource is Patient ) &&
359
+ bundleEntry.resource.meta.tag.isNotEmpty()
360
+ ) {
361
+ carePlanAndPatientMetaExtraction(bundleEntry.resource)
332
362
}
363
+ }
333
364
334
- saveQuestionnaireResponse(questionnaire, questionnaireResponse)
335
- questionnaireResponseLiveData.postValue(questionnaireResponse)
336
- // TODO https://github.com/opensrp/fhircore/issues/900
337
- // reassess following i.e. deleting/updating older resources because one resource
338
- // might have generated other flow in subsequent followups
339
- if (questionnaireType.isEditMode() && editQuestionnaireResponse != null ) {
340
- editQuestionnaireResponse!! .deleteRelatedResources(defaultRepository)
341
- }
342
- extractCarePlan(questionnaireResponse, bundle)
365
+ if (questionnaire.experimental) {
366
+ Timber .w(
367
+ " ${questionnaire.name} (${questionnaire.logicalId} ) is experimental and not save any data" ,
368
+ )
343
369
} else {
344
- saveQuestionnaireResponse(questionnaire, questionnaireResponse)
370
+ saveBundleResources(bundle)
371
+ }
372
+
373
+ if (questionnaireType.isEditMode() && editQuestionnaireResponse != null ) {
374
+ questionnaireResponse.retainMetadata(editQuestionnaireResponse!! )
345
375
}
346
- tracer.stopTrace(QUESTIONNAIRE_TRACE )
347
- viewModelScope.launch(Dispatchers .Main ) {
348
- extractionProgress.postValue(ExtractionProgress .Success (extras))
376
+
377
+ saveQuestionnaireResponse(questionnaire, questionnaireResponse)
378
+ questionnaireResponseLiveData.postValue(questionnaireResponse)
379
+ // TODO https://github.com/opensrp/fhircore/issues/900
380
+ // reassess following i.e. deleting/updating older resources because one resource
381
+ // might have generated other flow in subsequent followups
382
+ if (questionnaireType.isEditMode() && editQuestionnaireResponse != null ) {
383
+ editQuestionnaireResponse!! .deleteRelatedResources(defaultRepository)
349
384
}
385
+ extractCarePlan(questionnaireResponse, bundle)
386
+ } else {
387
+ saveQuestionnaireResponse(questionnaire, questionnaireResponse)
350
388
}
389
+ tracer.stopTrace(QUESTIONNAIRE_TRACE )
390
+ return extras
351
391
}
352
392
353
393
suspend fun carePlanAndPatientMetaExtraction (source : Resource ) {
0 commit comments