diff --git a/src/adapters/encounter-diagnosis-adapter.ts b/src/adapters/encounter-diagnoses-adapter.ts similarity index 83% rename from src/adapters/encounter-diagnosis-adapter.ts rename to src/adapters/encounter-diagnoses-adapter.ts index 1fac5691..d3c4673f 100644 --- a/src/adapters/encounter-diagnosis-adapter.ts +++ b/src/adapters/encounter-diagnoses-adapter.ts @@ -4,9 +4,9 @@ import { type FormContextProps } from '../provider/form-provider'; import { type OpenmrsEncounter, type FormField } from '../types'; import { clearSubmission, gracefullySetSubmission } from '../utils/common-utils'; -export let assignedEncounterDiagnosisIds: string[] = []; +export let assignedDiagnosesIds: string[] = []; -export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { +export const EncounterDiagnosesAdapter: FormFieldValueAdapter = { transformFieldValue: function (field: FormField, value: any, context: FormContextProps) { if (context.sessionMode == 'edit' && field.meta?.previousValue?.uuid) { return editDiagnosis(value, field); @@ -21,14 +21,16 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { context: FormProcessorContextProps, ): Promise { const availableDiagnoses = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter); - const matchedDiagnosis = availableDiagnoses.diagnoses?.find( + const matchedDiagnoses = availableDiagnoses.diagnoses.find( (diagnosis) => diagnosis.formFieldPath === `rfe-forms-${field.id}`, ); - if (matchedDiagnosis) { - field.meta = { ...(field.meta || {}), previousValue: matchedDiagnosis }; - assignedEncounterDiagnosisIds.push(matchedDiagnosis.diagnosis?.coded?.uuid); - return matchedDiagnosis.diagnosis?.coded.uuid; + if (matchedDiagnoses) { + field.meta = { ...(field.meta || {}), previousValue: matchedDiagnoses }; + if (!assignedDiagnosesIds.includes(matchedDiagnoses.diagnosis?.coded?.uuid)) { + assignedDiagnosesIds.push(matchedDiagnoses.diagnosis?.coded?.uuid); + } + return matchedDiagnoses.diagnosis?.coded.uuid; } return null; }, @@ -43,7 +45,7 @@ export const EncounterDiagnosisAdapter: FormFieldValueAdapter = { return field.questionOptions.answers?.find((option) => option.concept == value)?.label || value; }, tearDown: function (): void { - assignedEncounterDiagnosisIds = []; + assignedDiagnosesIds = []; }, }; diff --git a/src/adapters/encounter-diagnoses.test.ts b/src/adapters/encounter-diagnoses.test.ts new file mode 100644 index 00000000..1352aa3a --- /dev/null +++ b/src/adapters/encounter-diagnoses.test.ts @@ -0,0 +1,166 @@ +import { type FormContextProps } from '../provider/form-provider'; +import { type FormField } from '../types'; +import { EncounterDiagnosesAdapter } from './encounter-diagnoses-adapter'; + +const formContext = { + methods: null, + workspaceLayout: 'maximized', + isSubmitting: false, + patient: { + id: '833db896-c1f0-11eb-8529-0242ac130003', + }, + formJson: null, + visit: null, + sessionMode: 'enter', + sessionDate: new Date(), + location: { + uuid: '41e6e516-c1f0-11eb-8529-0242ac130003', + }, + currentProvider: null, + layoutType: 'small-desktop', + domainObjectValue: { + uuid: '873455da-3ec4-453c-b565-7c1fe35426be', + obs: [], + diagnoses: [], + }, + previousDomainObjectValue: null, + processor: null, + formFields: [], + formFieldAdapters: null, + formFieldValidators: null, + customDependencies: { + patientPrograms: [], + }, + getFormField: jest.fn(), + addFormField: jest.fn(), + updateFormField: jest.fn(), + removeFormField: () => {}, + addInvalidField: jest.fn(), + removeInvalidField: jest.fn(), + setInvalidFields: jest.fn(), + setForm: jest.fn(), +} as FormContextProps; + +const field = { + label: 'Test Diagnosis', + id: 'DiagNosIS', + type: 'diagnosis', + questionOptions: { + rendering: 'repeating', + rank: 1, + datasource: { + name: 'problem_datasource', + config: { + class: [ + '8d4918b0-c2cc-11de-8d13-0010c6dffd0f', + '8d492954-c2cc-11de-8d13-0010c6dffd0f', + '8d492b2a-c2cc-11de-8d13-0010c6dffd0f', + ], + }, + }, + }, + meta: { + submission: { + newValue: null, + }, + }, + validators: [ + { + type: 'form_field', + }, + { + type: 'default_value', + }, + ], + isHidden: false, + isRequired: false, + isDisabled: false, +} as FormField; + +const diagnoses = [ + { + uuid: '8d975f9e-e9e6-452f-be7c-0e87c047f056', + diagnosis: { + coded: { + uuid: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + display: 'Schistosoma Mansonii Infection', + links: [], + }, + }, + condition: null, + encounter: { + uuid: '9a4b06bd-d655-414f-b9ce-69e940c337ce', + }, + certainty: 'CONFIRMED', + rank: 1, + voided: false, + display: 'Schistosoma Mansonii Infection', + patient: { + uuid: '00affa97-0010-417c-87f5-de48362de915', + display: '1000VKV - Bett Tett', + }, + formFieldNamespace: 'rfe-forms', + formFieldPath: 'rfe-forms-DiagNosIS_1', + links: [], + resourceVersion: '1.8', + }, + { + uuid: 'b2d0e95b-d2f6-49d1-a477-acc7026edbd7', + diagnosis: { + coded: { + uuid: '137329AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + display: 'Infection due to Entamoeba Histolytica', + links: [], + }, + }, + condition: null, + encounter: { + uuid: '9a4b06bd-d655-414f-b9ce-69e940c337ce', + }, + certainty: 'CONFIRMED', + rank: 1, + voided: false, + display: 'Infection due to Entamoeba Histolytica', + patient: { + uuid: '00affa97-0010-417c-87f5-de48362de915', + display: '1000VKV - Bett Tett', + }, + formFieldNamespace: 'rfe-forms', + formFieldPath: 'rfe-forms-DiagNosIS', + links: [], + resourceVersion: '1.8', + }, +]; + +describe('EncounterDiagnosesAdapter', () => { + it('should should handle submission of a diagnosis field', async () => { + const value = '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + EncounterDiagnosesAdapter.transformFieldValue(field, value, formContext); + expect(field.meta.submission.newValue).toEqual({ + patient: '833db896-c1f0-11eb-8529-0242ac130003', + condition: null, + diagnosis: { + coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + }, + certainty: 'CONFIRMED', + rank: 1, + formFieldPath: 'rfe-forms-DiagNosIS', + formFieldNamespace: 'rfe-forms', + }); + }); + + it('should get initial value for the diagnosis', async () => { + formContext.domainObjectValue.diagnoses.push(...diagnoses); + const program = await EncounterDiagnosesAdapter.getInitialValue(field, null, formContext); + expect(program).toEqual('137329AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); + }); + + it('should return null for getPreviousValue', async () => { + const previousValue = await EncounterDiagnosesAdapter.getPreviousValue(field, null, formContext); + expect(previousValue).toBeNull(); + }); + + it('should execute tearDown without issues', () => { + expect(() => EncounterDiagnosesAdapter.tearDown()).not.toThrow(); + }); +}); diff --git a/src/adapters/orders-adapter.ts b/src/adapters/orders-adapter.ts index 0410178d..0962ebc0 100644 --- a/src/adapters/orders-adapter.ts +++ b/src/adapters/orders-adapter.ts @@ -15,6 +15,7 @@ export const OrdersAdapter: FormFieldValueAdapter = { } const newValue = constructNewOrder(value, field, context.currentProvider.uuid); gracefullySetSubmission(field, newValue, null); + return newValue; }, getInitialValue: function ( diff --git a/src/api/index.ts b/src/api/index.ts index 82cb5014..8124c4df 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,6 +1,6 @@ import { fhirBaseUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { encounterRepresentation } from '../constants'; -import { type DiagnosisPayload, type OpenmrsForm, type PatientIdentifier, type PatientProgramPayload } from '../types'; +import { type OpenmrsForm, type PatientIdentifier, type PatientProgramPayload } from '../types'; import { isUuid } from '../utils/boolean-utils'; export function saveEncounter(abortController: AbortController, payload, encounterUuid?: string) { diff --git a/src/processors/encounter/encounter-form-processor.ts b/src/processors/encounter/encounter-form-processor.ts index b29fdec2..1d5afcb4 100644 --- a/src/processors/encounter/encounter-form-processor.ts +++ b/src/processors/encounter/encounter-form-processor.ts @@ -166,7 +166,7 @@ export class EncounterFormProcessor extends FormProcessor { try { const { data: savedEncounter } = await saveEncounter(abortController, encounter, encounter.uuid); const saveOrders = savedEncounter.orders.map((order) => order.orderNumber); - const saveDiagnosis = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display); + const saveDiagnoses = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display); if (saveOrders.length) { showSnackbar({ title: translateFn('ordersSaved', 'Order(s) saved successfully'), @@ -176,10 +176,10 @@ export class EncounterFormProcessor extends FormProcessor { }); } // handle diagnoses - if (saveDiagnosis.length) { + if (saveDiagnoses.length) { showSnackbar({ title: translateFn('diagnosisSaved', 'Diagnosis(s) saved successfully'), - subtitle: saveDiagnosis.join(', '), + subtitle: saveDiagnoses.join(', '), kind: 'success', isLowContrast: true, }); diff --git a/src/processors/encounter/encounter-processor-helper.ts b/src/processors/encounter/encounter-processor-helper.ts index b3a01c86..e0138de1 100644 --- a/src/processors/encounter/encounter-processor-helper.ts +++ b/src/processors/encounter/encounter-processor-helper.ts @@ -16,7 +16,7 @@ import { ConceptTrue } from '../../constants'; import { DefaultValueValidator } from '../../validators/default-value-validator'; import { cloneRepeatField } from '../../components/repeat/helpers'; import { assignedOrderIds } from '../../adapters/orders-adapter'; -import { assignedEncounterDiagnosisIds } from '../../adapters/encounter-diagnosis-adapter'; +import { assignedDiagnosesIds } from '../../adapters/encounter-diagnoses-adapter'; export function prepareEncounter( context: FormContextProps, @@ -29,7 +29,7 @@ export function prepareEncounter( const obsForSubmission = []; prepareObs(obsForSubmission, formFields); const ordersForSubmission = prepareOrders(formFields); - const diagnosisForSubmission = prepareDiagnosis(formFields); + const diagnosesForSubmission = prepareDiagnosis(formFields); let encounterForSubmission: OpenmrsEncounter = {}; if (encounter) { @@ -59,7 +59,7 @@ export function prepareEncounter( } encounterForSubmission.obs = obsForSubmission; encounterForSubmission.orders = ordersForSubmission; - encounterForSubmission.diagnoses = diagnosisForSubmission; + encounterForSubmission.diagnoses = diagnosesForSubmission; } else { encounterForSubmission = { patient: patient.id, @@ -78,7 +78,7 @@ export function prepareEncounter( }, visit: visit?.uuid, orders: ordersForSubmission, - diagnoses: diagnosisForSubmission, + diagnoses: diagnosesForSubmission, }; } return encounterForSubmission; @@ -305,14 +305,22 @@ export async function hydrateRepeatField( ); } - //handle diagnoses - const unMappedDiagnosis = encounter.diagnoses.filter((diagnosis) => { - return !assignedEncounterDiagnosisIds.includes(diagnosis.diagnosis.coded.uuid); + const unMappedDiagnoses = encounter.diagnoses.filter((diagnosis) => { + return !assignedDiagnosesIds.includes(diagnosis?.diagnosis?.coded.uuid); }); + 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( - unMappedDiagnosis + sortedDiagnoses .filter((diagnosis) => !diagnosis.voided) .map(async (diagnosis) => { const clone = cloneRepeatField(field, diagnosis, counter++); @@ -321,6 +329,11 @@ export async function hydrateRepeatField( { diagnoses: [diagnosis] } as any, context, ); + + if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) { + assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid); + } + return clone; }), ); diff --git a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts index 2a5632bf..43be7ccc 100644 --- a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +++ b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts @@ -10,7 +10,7 @@ import { ObsCommentAdapter } from '../../adapters/obs-comment-adapter'; import { OrdersAdapter } from '../../adapters/orders-adapter'; import { PatientIdentifierAdapter } from '../../adapters/patient-identifier-adapter'; import { ProgramStateAdapter } from '../../adapters/program-state-adapter'; -import { EncounterDiagnosisAdapter } from '../../adapters/encounter-diagnosis-adapter'; +import { EncounterDiagnosesAdapter } from '../../adapters/encounter-diagnoses-adapter'; import { type FormFieldValueAdapter } from '../../types'; export const inbuiltFieldValueAdapters: RegistryItem[] = [ @@ -64,6 +64,6 @@ export const inbuiltFieldValueAdapters: RegistryItem[] = }, { type: 'diagnosis', - component: EncounterDiagnosisAdapter, + component: EncounterDiagnosesAdapter, }, ]; diff --git a/src/transformers/default-schema-transformer.ts b/src/transformers/default-schema-transformer.ts index b778d602..1ec7db00 100644 --- a/src/transformers/default-schema-transformer.ts +++ b/src/transformers/default-schema-transformer.ts @@ -134,7 +134,7 @@ function transformByType(question: FormField) { : question.questionOptions.rendering; break; case 'diagnosis': - handleDiagnosesDataSource(question); + handleDiagnosis(question); break; } } @@ -258,7 +258,7 @@ function handleQuestionsWithObsComments(sectionQuestions: Array): Arr return augmentedQuestions; } -function handleDiagnosesDataSource(question: FormField) { +function handleDiagnosis(question: FormField) { if ('dataSource' in question.questionOptions && question.questionOptions['dataSource'] === 'diagnoses') { question.questionOptions.datasource = { name: 'problem_datasource', diff --git a/src/types/domain.ts b/src/types/domain.ts index 1030942a..c41cd127 100644 --- a/src/types/domain.ts +++ b/src/types/domain.ts @@ -138,6 +138,8 @@ export interface DiagnosisPayload { }; certainty: string; rank: number; + formFieldNamespace?: string; + formFieldPath?: string; } export interface Diagnosis { @@ -153,4 +155,6 @@ export interface Diagnosis { display: string; voided: boolean; uuid: string; + formFieldNamespace?: string; + formFieldPath?: string; }