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