diff --git a/src/adapters/encounter-diagnosis-adapter.test.ts b/src/adapters/encounter-diagnosis-adapter.test.ts index f9dd460e..e1324173 100644 --- a/src/adapters/encounter-diagnosis-adapter.test.ts +++ b/src/adapters/encounter-diagnosis-adapter.test.ts @@ -31,6 +31,7 @@ const formContext = { customDependencies: { patientPrograms: [], }, + deletedFields: [], getFormField: jest.fn(), addFormField: jest.fn(), updateFormField: jest.fn(), @@ -39,6 +40,7 @@ const formContext = { removeInvalidField: jest.fn(), setInvalidFields: jest.fn(), setForm: jest.fn(), + setDeletedFields: jest.fn(), } as FormContextProps; const field = { @@ -173,18 +175,6 @@ describe('EncounterDiagnosisAdapter', () => { const value = '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; field.meta = { - previousValue: { - patient: '833db896-c1f0-11eb-8529-0242ac130003', - condition: null, - diagnosis: { - coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - }, - certainty: 'PROVISIONAL', - rank: 1, - formFieldPath: 'rfe-forms-DiagNosIS', - formFieldNamespace: 'rfe-forms', - uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', - }, initialValue: { omrsObject: { uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', @@ -215,23 +205,10 @@ describe('EncounterDiagnosisAdapter', () => { expect(field.meta.submission.voidedValue).toBe(undefined); }); - it('should handle deleting a diagnosis', () => { + it('should void removed diagnosis in edit mode', () => { formContext.sessionMode = 'edit'; - const value = null; field.meta = { - previousValue: { - patient: '833db896-c1f0-11eb-8529-0242ac130003', - condition: null, - diagnosis: { - coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - }, - certainty: 'PROVISIONAL', - rank: 1, - formFieldPath: 'rfe-forms-DiagNosIS', - formFieldNamespace: 'rfe-forms', - uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', - }, initialValue: { omrsObject: { uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', @@ -244,11 +221,12 @@ describe('EncounterDiagnosisAdapter', () => { }, refinedValue: null, }, - repeat: { - wasDeleted: true, - }, }; - EncounterDiagnosisAdapter.transformFieldValue(field, value, formContext); - expect(field.meta.submission.voidedValue).toEqual({ uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', voided: true }); + + EncounterDiagnosisAdapter.transformFieldValue(field, null, formContext); + expect(field.meta.submission.voidedValue).toEqual({ + voided: true, + uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', + }); }); }); diff --git a/src/adapters/encounter-diagnosis-adapter.ts b/src/adapters/encounter-diagnosis-adapter.ts index 1719a831..af3af17e 100644 --- a/src/adapters/encounter-diagnosis-adapter.ts +++ b/src/adapters/encounter-diagnosis-adapter.ts @@ -1,15 +1,28 @@ import { type OpenmrsResource } from '@openmrs/esm-framework'; -import { type DiagnosisPayload, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types'; +import { type OpenmrsObs, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types'; import { type FormContextProps } from '../provider/form-provider'; import { type OpenmrsEncounter, type FormField } from '../types'; -import { clearSubmission, gracefullySetSubmission } from '../utils/common-utils'; +import { gracefullySetSubmission } from '../utils/common-utils'; +import { isEmpty } from '../validators/form-validator'; +import { voidObs } from './obs-adapter'; +import { isTrue } from '../utils/boolean-utils'; export let assignedDiagnosesIds: string[] = []; export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { transformFieldValue: function (field: FormField, value: any, context: FormContextProps) { - if (context.sessionMode == 'edit' && field.meta?.previousValue?.uuid) { - return editDiagnosis(value, field, context.patient.id); + if (field.meta.initialValue?.omrsObject && isEmpty(value)) { + return gracefullySetSubmission(field, undefined, voidObs(field.meta.initialValue.omrsObject as OpenmrsObs)); + } + if (!isEmpty(value)) { + const previousDiagnosis = field.meta.initialValue?.omrsObject as OpenmrsResource; + if (hasPreviousDiagnosisValueChanged(previousDiagnosis, value)) { + return gracefullySetSubmission( + field, + editDiagnosis(value, field, previousDiagnosis, context.patient.id), + undefined, + ); + } } const newValue = constructNewDiagnosis(value, field, context.patient.id); gracefullySetSubmission(field, newValue, null); @@ -26,7 +39,13 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { ); if (matchedDiagnosis) { - field.meta = { ...(field.meta || {}), previousValue: matchedDiagnosis }; + field.meta = { + ...(field.meta || {}), + initialValue: { + omrsObject: matchedDiagnosis, + refinedValue: matchedDiagnosis.diagnosis?.coded.uuid, + }, + }; if (!assignedDiagnosesIds.includes(matchedDiagnosis.diagnosis?.coded?.uuid)) { assignedDiagnosesIds.push(matchedDiagnosis.diagnosis?.coded?.uuid); } @@ -49,47 +68,46 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { }, }; -const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string, uuid?: string) => { +const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string) => { if (!value) { return null; } - const diagnosis: DiagnosisPayload = { + return { patient: patientUuid, condition: null, diagnosis: { coded: value, }, - certainty: field.questionOptions?.diagnosis?.isConfirmed ? 'CONFIRMED' : 'PROVISIONAL', + certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL', rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary formFieldPath: `rfe-forms-${field.id}`, formFieldNamespace: 'rfe-forms', }; - - if (uuid && uuid.trim() !== '') { - diagnosis.uuid = uuid; - } - - return diagnosis; }; -function editDiagnosis(newEncounterDiagnosis: any, field: FormField, patientUuid: string) { - if (newEncounterDiagnosis === field.meta.previousValue?.diagnosis?.coded?.uuid && !field.meta.repeat?.wasDeleted) { - clearSubmission(field); - return null; - } +function editDiagnosis( + newEncounterDiagnosis: any, + field: FormField, + previousDiagnosis: OpenmrsResource, + patientUuid: string, +) { + return { + patient: patientUuid, + condition: null, + diagnosis: { + coded: newEncounterDiagnosis, + }, + certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL', + rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary + formFieldPath: `rfe-forms-${field.id}`, + formFieldNamespace: 'rfe-forms', + uuid: previousDiagnosis.uuid, + }; +} - //the field has been deleted - if (field.meta.repeat?.wasDeleted) { - const voided = { - uuid: field.meta.previousValue?.uuid, - voided: true, - }; - gracefullySetSubmission(field, constructNewDiagnosis(newEncounterDiagnosis, field, null), voided); - return field.meta.submission.newValue || null; - } else { - const oldDiagnosis = field.meta.initialValue?.omrsObject as OpenmrsResource; - const newValue = constructNewDiagnosis(newEncounterDiagnosis, field, patientUuid, oldDiagnosis.uuid); - gracefullySetSubmission(field, newValue, null); - return newValue; +export function hasPreviousDiagnosisValueChanged(previousDiagnosis: OpenmrsResource, newValue: any) { + if (isEmpty(previousDiagnosis)) { + return false; } + return previousDiagnosis.value !== newValue; } diff --git a/src/datasources/concept-data-source.ts b/src/datasources/concept-data-source.ts index a70a2fda..c1655ab4 100644 --- a/src/datasources/concept-data-source.ts +++ b/src/datasources/concept-data-source.ts @@ -18,11 +18,13 @@ export class ConceptDataSource extends BaseOpenMRSDataSource { const urlParts = searchUrl.split('searchType=fuzzy'); searchUrl = `${urlParts[0]}searchType=fuzzy&class=${config.class}&${urlParts[1]}`; } else { - return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : searchUrl).then(({ data }) => { - return data.results.filter( - (concept) => concept.conceptClass && config.class.includes(concept.conceptClass.uuid), - ); - }); + return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : `${searchUrl}&q=${'Diagnosis'}`).then( + ({ data }) => { + return data.results.filter( + (concept) => concept.conceptClass && config.class.includes(concept.conceptClass.uuid), + ); + }, + ); } } diff --git a/src/processors/encounter/encounter-processor-helper.ts b/src/processors/encounter/encounter-processor-helper.ts index 8707b49c..7ccd0323 100644 --- a/src/processors/encounter/encounter-processor-helper.ts +++ b/src/processors/encounter/encounter-processor-helper.ts @@ -32,6 +32,7 @@ export function prepareEncounter( prepareObs(obsForSubmission, allFormFields); const ordersForSubmission = prepareOrders(allFormFields); const diagnosesForSubmission = prepareDiagnosis(allFormFields); + let encounterForSubmission: OpenmrsEncounter = {}; if (encounter) { @@ -321,38 +322,28 @@ export async function hydrateRepeatField( const unMappedDiagnoses = encounter.diagnoses.filter((diagnosis) => { return ( + !diagnosis.voided && !assignedDiagnosesIds.includes(diagnosis?.diagnosis?.coded.uuid) && diagnosis.formFieldPath.startsWith(`rfe-forms-${field.id}_`) ); }); - const sortedDiagnoses = unMappedDiagnoses - .filter((diagnosis) => !diagnosis.voided) - .sort((a, b) => { - // Extract numeric part of formFieldPath for sorting - const numberA = parseInt(a.formFieldPath.split('_')[1], 10); - const numberB = parseInt(b.formFieldPath.split('_')[1], 10); - return numberA - numberB; // Sort numerically based on formFieldPath - }); - if (field.type === 'diagnosis') { return Promise.all( - sortedDiagnoses - .filter((diagnosis) => !diagnosis.voided) - .map(async (diagnosis) => { - const clone = cloneRepeatField(field, diagnosis, counter++); - initialValues[clone.id] = await formFieldAdapters[field.type].getInitialValue( - clone, - { diagnoses: [diagnosis] } as any, - context, - ); - - if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) { - assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid); - } + unMappedDiagnoses.map(async (diagnosis) => { + const idSuffix = parseInt(diagnosis.formFieldPath.split('_')[1]); + const clone = cloneRepeatField(field, diagnosis, idSuffix); + initialValues[clone.id] = await formFieldAdapters[field.type].getInitialValue( + clone, + { diagnoses: [diagnosis] } as any, + context, + ); + if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) { + assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid); + } - return clone; - }), + return clone; + }), ); } // handle obs groups @@ -375,8 +366,10 @@ export async function hydrateRepeatField( } function prepareDiagnosis(fields: FormField[]) { - return fields + const diagnoses = fields .filter((field) => field.type === 'diagnosis' && hasSubmission(field)) - .flatMap((field) => [field.meta.submission.newValue, field.meta.submission.voidedValue]) - .filter((d) => d); + .map((field) => field.meta.submission.newValue || field.meta.submission.voidedValue) + .filter((o) => o); + + return diagnoses; } diff --git a/src/transformers/default-schema-transformer.ts b/src/transformers/default-schema-transformer.ts index ba81b5b1..91559dad 100644 --- a/src/transformers/default-schema-transformer.ts +++ b/src/transformers/default-schema-transformer.ts @@ -291,6 +291,19 @@ function handleDiagnosis(question: FormField) { class: question.questionOptions.diagnosis?.conceptClasses, }, }; + if (question.questionOptions.diagnosis?.conceptSet) { + question.questionOptions = { + ...question.questionOptions, + concept: question.questionOptions.diagnosis?.conceptSet, + datasource: { + name: 'problem_datasource', + config: { + useSetMembersByConcept: true, + }, + }, + }; + } + delete question.questionOptions['dataSource']; } } diff --git a/src/types/schema.ts b/src/types/schema.ts index d40e6ea5..be00e315 100644 --- a/src/types/schema.ts +++ b/src/types/schema.ts @@ -196,6 +196,7 @@ export interface FormQuestionOptions { rank?: number; isConfirmed?: boolean; conceptClasses?: Array; + conceptSet?: string; }; }