-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(feat) O3-3316 Add support for encounter diagnosis #298
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,80 @@ | ||||||
import { type OpenmrsResource } from '@openmrs/esm-framework'; | ||||||
import { type FormFieldValueAdapter, type FormProcessorContextProps } from '..'; | ||||||
import { type FormContextProps } from '../provider/form-provider'; | ||||||
import { type OpenmrsEncounter, type FormField } from '../types'; | ||||||
import { clearSubmission, gracefullySetSubmission } from '../utils/common-utils'; | ||||||
|
||||||
export let assignedDiagnosesIds: string[] = []; | ||||||
|
||||||
export const EncounterDiagnosesAdapter: FormFieldValueAdapter = { | ||||||
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) { | ||||||
if (context.sessionMode == 'edit' && field.meta?.previousValue?.uuid) { | ||||||
return editDiagnosis(value, field); | ||||||
} | ||||||
const newValue = constructNewDiagnosis(value, field, context.patient.id); | ||||||
gracefullySetSubmission(field, newValue, null); | ||||||
return newValue; | ||||||
}, | ||||||
getInitialValue: function ( | ||||||
field: FormField, | ||||||
sourceObject: OpenmrsResource, | ||||||
context: FormProcessorContextProps, | ||||||
): Promise<any> { | ||||||
const availableDiagnoses = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter); | ||||||
const matchedDiagnoses = availableDiagnoses.diagnoses.find( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
(diagnosis) => diagnosis.formFieldPath === `rfe-forms-${field.id}`, | ||||||
); | ||||||
|
||||||
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; | ||||||
}, | ||||||
getPreviousValue: function ( | ||||||
field: FormField, | ||||||
sourceObject: OpenmrsResource, | ||||||
context: FormProcessorContextProps, | ||||||
): Promise<any> { | ||||||
return null; | ||||||
}, | ||||||
getDisplayValue: (field: FormField, value: any) => { | ||||||
return field.questionOptions.answers?.find((option) => option.concept == value)?.label || value; | ||||||
}, | ||||||
tearDown: function (): void { | ||||||
assignedDiagnosesIds = []; | ||||||
}, | ||||||
}; | ||||||
|
||||||
const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string) => { | ||||||
if (!value) { | ||||||
return null; | ||||||
} | ||||||
return { | ||||||
patient: patientUuid, | ||||||
condition: null, | ||||||
diagnosis: { | ||||||
coded: value, | ||||||
}, | ||||||
certainty: 'CONFIRMED', | ||||||
rank: field.questionOptions.rank, // rank 1 denotes a diagnosis is primary, else secondary | ||||||
formFieldPath: `rfe-forms-${field.id}`, | ||||||
formFieldNamespace: 'rfe-forms', | ||||||
}; | ||||||
}; | ||||||
|
||||||
function editDiagnosis(newEncounterDiagnosis: any, field: FormField) { | ||||||
if (newEncounterDiagnosis === field.meta.previousValue?.concept?.uuid) { | ||||||
clearSubmission(field); | ||||||
return null; | ||||||
} | ||||||
const voided = { | ||||||
uuid: field.meta.previousValue?.uuid, | ||||||
voided: true, | ||||||
}; | ||||||
gracefullySetSubmission(field, constructNewDiagnosis(newEncounterDiagnosis, field, null), voided); | ||||||
return field.meta.submission.newValue || null; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add test case(s) around:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CynthiaKamau I discovered a bug while testing the entire feature. |
||
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(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -166,6 +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 saveDiagnoses = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(I would also rename |
||||||
if (saveOrders.length) { | ||||||
showSnackbar({ | ||||||
title: translateFn('ordersSaved', 'Order(s) saved successfully'), | ||||||
|
@@ -174,6 +175,15 @@ export class EncounterFormProcessor extends FormProcessor { | |||||
isLowContrast: true, | ||||||
}); | ||||||
} | ||||||
// handle diagnoses | ||||||
if (saveDiagnoses.length) { | ||||||
showSnackbar({ | ||||||
title: translateFn('diagnosisSaved', 'Diagnosis(s) saved successfully'), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
subtitle: saveDiagnoses.join(', '), | ||||||
kind: 'success', | ||||||
isLowContrast: true, | ||||||
}); | ||||||
} | ||||||
// handle attachments | ||||||
try { | ||||||
const attachmentsResponse = await Promise.all( | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +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 { assignedDiagnosesIds } from '../../adapters/encounter-diagnoses-adapter'; | ||
|
||
export function prepareEncounter( | ||
context: FormContextProps, | ||
|
@@ -28,6 +29,7 @@ export function prepareEncounter( | |
const obsForSubmission = []; | ||
prepareObs(obsForSubmission, formFields); | ||
const ordersForSubmission = prepareOrders(formFields); | ||
const diagnosesForSubmission = prepareDiagnosis(formFields); | ||
let encounterForSubmission: OpenmrsEncounter = {}; | ||
|
||
if (encounter) { | ||
|
@@ -57,6 +59,7 @@ export function prepareEncounter( | |
} | ||
encounterForSubmission.obs = obsForSubmission; | ||
encounterForSubmission.orders = ordersForSubmission; | ||
encounterForSubmission.diagnoses = diagnosesForSubmission; | ||
} else { | ||
encounterForSubmission = { | ||
patient: patient.id, | ||
|
@@ -75,6 +78,7 @@ export function prepareEncounter( | |
}, | ||
visit: visit?.uuid, | ||
orders: ordersForSubmission, | ||
diagnoses: diagnosesForSubmission, | ||
}; | ||
} | ||
return encounterForSubmission; | ||
|
@@ -300,6 +304,40 @@ export async function hydrateRepeatField( | |
}), | ||
); | ||
} | ||
|
||
const unMappedDiagnoses = encounter.diagnoses.filter((diagnosis) => { | ||
return !assignedDiagnosesIds.includes(diagnosis?.diagnosis?.coded.uuid); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the Diagnosis should include the field's ID in it's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CynthiaKamau did you see this? |
||
}); | ||
|
||
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++); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a demo where we capture diagnoses with "repeat controls"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
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; | ||
}), | ||
); | ||
} | ||
// handle obs groups | ||
return Promise.all( | ||
unMappedGroups.map(async (group) => { | ||
|
@@ -318,3 +356,9 @@ export async function hydrateRepeatField( | |
}), | ||
).then((results) => results.flat()); | ||
} | ||
|
||
function prepareDiagnosis(fields: FormField[]) { | ||
return fields | ||
.filter((field) => field.type === 'diagnosis' && hasSubmission(field)) | ||
.map((field) => field.meta.submission.newValue); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.