From e5de3c916eb57d5a9dd57b44c9d208089bc80f1e Mon Sep 17 00:00:00 2001
From: CynthiaKamau <cynthiakamau54@gmail.com>
Date: Mon, 16 Sep 2024 22:10:57 +0300
Subject: [PATCH] (feat) O3-3316 Add support for encounter diagnosis

---
 package.json                                  |  2 +-
 src/adapters/encounter-diagnosis-adapter.ts   | 78 +++++++++++++++++++
 src/adapters/orders-adapter.ts                |  1 +
 src/api/index.ts                              |  2 +-
 src/components/repeat/helpers.ts              |  5 ++
 src/components/repeat/repeat.component.tsx    |  1 +
 src/constants.ts                              |  1 +
 .../encounter/encounter-form-processor.ts     | 10 +++
 .../encounter/encounter-processor-helper.ts   | 31 ++++++++
 .../inbuiltFieldValueAdapters.ts              |  5 ++
 .../default-schema-transformer.ts             | 19 +++++
 src/types/domain.ts                           | 27 +++++++
 src/types/schema.ts                           |  1 +
 13 files changed, 181 insertions(+), 2 deletions(-)
 create mode 100644 src/adapters/encounter-diagnosis-adapter.ts

diff --git a/package.json b/package.json
index 83a1a2819..da661c6c1 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
     "access": "public"
   },
   "dependencies": {
-    "@carbon/react": ">1.47.0 <1.50.0",
+    "@carbon/react": "1.40.0",
     "classnames": "^2.5.1",
     "lodash-es": "^4.17.21",
     "react-error-boundary": "^4.0.13",
diff --git a/src/adapters/encounter-diagnosis-adapter.ts b/src/adapters/encounter-diagnosis-adapter.ts
new file mode 100644
index 000000000..5e8a23b96
--- /dev/null
+++ b/src/adapters/encounter-diagnosis-adapter.ts
@@ -0,0 +1,78 @@
+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 assignedEncounterDiagnosisIds: 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);
+    }
+    const newValue = constructNewDiagnosis(value, field, context.patient.id);
+    gracefullySetSubmission(field, newValue, null);
+    console.log('====newValue diagnosis', newValue);
+    return newValue;
+  },
+  getInitialValue: function (
+    field: FormField,
+    sourceObject: OpenmrsResource,
+    context: FormProcessorContextProps,
+  ): Promise<any> {
+    const availableDiagnoses = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
+    const matchedDiagnosis = 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;
+    }
+    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 {
+    assignedEncounterDiagnosisIds = [];
+  },
+};
+
+const constructNewDiagnosis = (value: any, field: FormField, patientUuid: string) => {
+  if (!value) {
+    return null;
+  }
+  return {
+    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;
+}
diff --git a/src/adapters/orders-adapter.ts b/src/adapters/orders-adapter.ts
index 0410178df..a01ae2162 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);
+    console.log('====newValue', newValue);
     return newValue;
   },
   getInitialValue: function (
diff --git a/src/api/index.ts b/src/api/index.ts
index 8124c4df5..82cb5014f 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 OpenmrsForm, type PatientIdentifier, type PatientProgramPayload } from '../types';
+import { type DiagnosisPayload, 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/components/repeat/helpers.ts b/src/components/repeat/helpers.ts
index fbbc0bc97..83532cc1b 100644
--- a/src/components/repeat/helpers.ts
+++ b/src/components/repeat/helpers.ts
@@ -5,8 +5,13 @@ import { isEmpty } from '../../validators/form-validator';
 import { clearSubmission } from '../../utils/common-utils';
 
 export function cloneRepeatField(srcField: FormField, value: OpenmrsResource, idSuffix: number) {
+  // console.log('====srcField', srcField);
+  // console.log('====value', value);
+  // console.log('====idSuffix', idSuffix);
+
   const originalGroupMembersIds: string[] = [];
   const clonedField = cloneDeep(srcField) as FormField;
+  // console.log('====clonedField', clonedField);
   clonedField.questionOptions.repeatOptions = { ...(clonedField.questionOptions.repeatOptions ?? {}) };
   clonedField.meta = { repeat: { ...(clonedField.meta ?? {}), isClone: true }, previousValue: value };
   clonedField.id = `${clonedField.id}_${idSuffix}`;
diff --git a/src/components/repeat/repeat.component.tsx b/src/components/repeat/repeat.component.tsx
index 28aaba4ae..0450038d0 100644
--- a/src/components/repeat/repeat.component.tsx
+++ b/src/components/repeat/repeat.component.tsx
@@ -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 }) => {
diff --git a/src/constants.ts b/src/constants.ts
index 567569286..88ee7c866 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -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';
diff --git a/src/processors/encounter/encounter-form-processor.ts b/src/processors/encounter/encounter-form-processor.ts
index 1b8faf7be..b29fdec29 100644
--- a/src/processors/encounter/encounter-form-processor.ts
+++ b/src/processors/encounter/encounter-form-processor.ts
@@ -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 saveDiagnosis = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display);
       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 (saveDiagnosis.length) {
+        showSnackbar({
+          title: translateFn('diagnosisSaved', 'Diagnosis(s) saved successfully'),
+          subtitle: saveDiagnosis.join(', '),
+          kind: 'success',
+          isLowContrast: true,
+        });
+      }
       // handle attachments
       try {
         const attachmentsResponse = await Promise.all(
diff --git a/src/processors/encounter/encounter-processor-helper.ts b/src/processors/encounter/encounter-processor-helper.ts
index 852187109..b3a01c86c 100644
--- a/src/processors/encounter/encounter-processor-helper.ts
+++ b/src/processors/encounter/encounter-processor-helper.ts
@@ -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 { assignedEncounterDiagnosisIds } from '../../adapters/encounter-diagnosis-adapter';
 
 export function prepareEncounter(
   context: FormContextProps,
@@ -28,6 +29,7 @@ export function prepareEncounter(
   const obsForSubmission = [];
   prepareObs(obsForSubmission, formFields);
   const ordersForSubmission = prepareOrders(formFields);
+  const diagnosisForSubmission = prepareDiagnosis(formFields);
   let encounterForSubmission: OpenmrsEncounter = {};
 
   if (encounter) {
@@ -57,6 +59,7 @@ export function prepareEncounter(
     }
     encounterForSubmission.obs = obsForSubmission;
     encounterForSubmission.orders = ordersForSubmission;
+    encounterForSubmission.diagnoses = diagnosisForSubmission;
   } else {
     encounterForSubmission = {
       patient: patient.id,
@@ -75,6 +78,7 @@ export function prepareEncounter(
       },
       visit: visit?.uuid,
       orders: ordersForSubmission,
+      diagnoses: diagnosisForSubmission,
     };
   }
   return encounterForSubmission;
@@ -300,6 +304,27 @@ export async function hydrateRepeatField(
         }),
     );
   }
+
+  //handle diagnoses
+  const unMappedDiagnosis = encounter.diagnoses.filter((diagnosis) => {
+    return !assignedEncounterDiagnosisIds.includes(diagnosis.diagnosis.coded.uuid);
+  });
+
+  if (field.type === 'diagnosis') {
+    return Promise.all(
+      unMappedDiagnosis
+        .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,
+          );
+          return clone;
+        }),
+    );
+  }
   // handle obs groups
   return Promise.all(
     unMappedGroups.map(async (group) => {
@@ -318,3 +343,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);
+}
diff --git a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts
index 9a4c3d850..2a5632bf7 100644
--- a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts
+++ b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts
@@ -10,6 +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 { type FormFieldValueAdapter } from '../../types';
 
 export const inbuiltFieldValueAdapters: RegistryItem<FormFieldValueAdapter>[] = [
@@ -61,4 +62,8 @@ export const inbuiltFieldValueAdapters: RegistryItem<FormFieldValueAdapter>[] =
     type: 'patientIdentifier',
     component: PatientIdentifierAdapter,
   },
+  {
+    type: 'diagnosis',
+    component: EncounterDiagnosisAdapter,
+  },
 ];
diff --git a/src/transformers/default-schema-transformer.ts b/src/transformers/default-schema-transformer.ts
index 40447a5fe..0b98188ff 100644
--- a/src/transformers/default-schema-transformer.ts
+++ b/src/transformers/default-schema-transformer.ts
@@ -131,6 +131,9 @@ function transformByType(question: FormField) {
         ? 'date'
         : question.questionOptions.rendering;
       break;
+    case 'diagnosis':
+      handleDiagnosesDataSource(question);
+      break;
   }
 }
 
@@ -237,3 +240,19 @@ function handleQuestionsWithObsComments(sectionQuestions: Array<FormField>): Arr
 
   return augmentedQuestions;
 }
+
+function handleDiagnosesDataSource(question: FormField) {
+  if ('dataSource' in question.questionOptions && question.questionOptions['dataSource'] === 'diagnoses') {
+    question.questionOptions.datasource = {
+      name: 'problem_datasource',
+      config: {
+        class: [
+          '8d4918b0-c2cc-11de-8d13-0010c6dffd0f',
+          '8d492954-c2cc-11de-8d13-0010c6dffd0f',
+          '8d492b2a-c2cc-11de-8d13-0010c6dffd0f',
+        ],
+      },
+    };
+    delete question.questionOptions['dataSource'];
+  }
+}
diff --git a/src/types/domain.ts b/src/types/domain.ts
index 010e349b6..1030942ae 100644
--- a/src/types/domain.ts
+++ b/src/types/domain.ts
@@ -12,6 +12,7 @@ export interface OpenmrsEncounter {
   visit?: OpenmrsResource | string;
   encounterProviders?: Array<Record<string, any>>;
   form?: OpenmrsFormResource;
+  diagnoses?: Array<Diagnosis>;
 }
 
 export interface OpenmrsObs extends OpenmrsResource {
@@ -127,3 +128,29 @@ export interface PatientIdentifier {
   location?: string;
   preferred?: boolean;
 }
+
+export interface DiagnosisPayload {
+  encounter: string;
+  patient: string;
+  condition: null;
+  diagnosis: {
+    coded: string;
+  };
+  certainty: string;
+  rank: number;
+}
+
+export interface Diagnosis {
+  encounter: string;
+  patient: string;
+  diagnosis: {
+    coded: {
+      uuid: string;
+    };
+  };
+  certainty: string;
+  rank: number;
+  display: string;
+  voided: boolean;
+  uuid: string;
+}
diff --git a/src/types/schema.ts b/src/types/schema.ts
index 56f453390..4d473bc27 100644
--- a/src/types/schema.ts
+++ b/src/types/schema.ts
@@ -174,6 +174,7 @@ export interface FormQuestionOptions {
   comment?: string;
   orientation?: 'vertical' | 'horizontal';
   shownCommentOptions?: { validators?: Array<Record<string, any>>; hide?: { hideWhenExpression: string } };
+  rank?: number;
 }
 
 export interface QuestionAnswerOption {