Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/adapters/encounter-diagnoses-adapter.ts
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 encounter = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
const matchedDiagnosis = encounter.diagnoses.find(
(diagnosis) => diagnosis.formFieldPath === `rfe-forms-${field.id}`,
);

if (matchedDiagnosis) {
field.meta = { ...(field.meta || {}), previousValue: matchedDiagnosis };
if (!assignedDiagnosesIds.includes(matchedDiagnosis.diagnosis?.coded?.uuid)) {
assignedDiagnosesIds.push(matchedDiagnosis.diagnosis?.coded?.uuid);
}
return matchedDiagnosis.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?.diagnosis?.coded?.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;
}
202 changes: 202 additions & 0 deletions src/adapters/encounter-diagnoses.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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,
},
previousValue: 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', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add test case(s) around:

  • editing a value and the fact that the edited value gets voided

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau I discovered a bug while testing the entire feature.
Issue: When you delete a diagnosis (using the form repeat controls) and launch the form again in edit mode, the deleted diagnosis still exists, meaning it was never voided. This is partly why I requested the tests.

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();
});

it('should edit a diagnosis value', () => {
formContext.sessionMode = 'edit';

const value = '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
field.meta.previousValue = {
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
certainty: 'CONFIRMED',
condition: null,
formFieldPath: 'rfe-forms-DiagNosIS_1',
formFieldNamespace: 'rfe-forms',
display: 'Schistosoma Mansonii Infection',
rank: 1,
voided: false,
diagnosis: {
coded: {
uuid: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
display: 'Schistosoma Mansonii Infection',
},
},
};
EncounterDiagnosesAdapter.transformFieldValue(field, value, formContext);
expect(field.meta.submission.newValue).toEqual({
patient: null,
condition: null,
diagnosis: {
coded: '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
},
certainty: 'CONFIRMED',
rank: 1,
formFieldPath: 'rfe-forms-DiagNosIS',
formFieldNamespace: 'rfe-forms',
});
expect(field.meta.submission.voidedValue).toEqual({ uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f', voided: true });
});
});
1 change: 1 addition & 0 deletions src/adapters/orders-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const OrdersAdapter: FormFieldValueAdapter = {
}
const newValue = constructNewOrder(value, field, context.currentProvider.uuid);
gracefullySetSubmission(field, newValue, null);

return newValue;
},
getInitialValue: function (
Expand Down
1 change: 1 addition & 0 deletions src/components/repeat/repeat.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useFormFactory } from '../../provider/form-factory-provider';
const renderingByTypeMap: Record<string, RenderType> = {
obsGroup: 'group',
testOrder: 'select',
diagnosis: 'ui-select-extended',
};

const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const encounterRepresentation =
'custom:(uuid,encounterDatetime,encounterType:(uuid,name,description),location:(uuid,name),' +
'patient:(uuid,display),encounterProviders:(uuid,provider:(uuid,name),encounterRole:(uuid,name)),' +
'orders:(uuid,display,concept:(uuid,display),voided),' +
'diagnoses:(uuid,certainty,condition,formFieldPath,formFieldNamespace,display,rank,voided,diagnosis:(coded:(uuid,display))),' +
'obs:(uuid,obsDatetime,comment,voided,groupMembers,formFieldNamespace,formFieldPath,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' +
'names:(uuid,conceptNameType,name))))';
export const FormsStore = 'forms-engine-store';
Expand Down
16 changes: 13 additions & 3 deletions src/processors/encounter/encounter-form-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,21 @@ export class EncounterFormProcessor extends FormProcessor {
// save encounter
try {
const { data: savedEncounter } = await saveEncounter(abortController, encounter, encounter.uuid);
const saveOrders = savedEncounter.orders.map((order) => order.orderNumber);
if (saveOrders.length) {
const savedOrders = savedEncounter.orders.map((order) => order.orderNumber);
const savedDiagnoses = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display);
if (savedOrders.length) {
showSnackbar({
title: translateFn('ordersSaved', 'Order(s) saved successfully'),
subtitle: saveOrders.join(', '),
subtitle: savedOrders.join(', '),
kind: 'success',
isLowContrast: true,
});
}
// handle diagnoses
if (savedDiagnoses.length) {
showSnackbar({
title: translateFn('diagnosisSaved', 'Diagnosis(es) saved successfully'),
subtitle: savedDiagnoses.join(', '),
kind: 'success',
isLowContrast: true,
});
Expand Down
Loading