From 9a5d067dd15e87442aa2bbfd4aa84f1a028324c8 Mon Sep 17 00:00:00 2001 From: Yassin Date: Tue, 22 Oct 2024 15:44:21 +0200 Subject: [PATCH 1/4] (feat) O3-4065 Allow manual entry of auto generated identifiers (#1338) * (feat) O3-4065 Allow manual entry of auto generated identifiers * Remove id field configuration Interpolate optional identifier label * Update packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts Co-authored-by: Ian <52504170+ibacher@users.noreply.github.com> * Restore line * Update packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx Co-authored-by: Ian <52504170+ibacher@users.noreply.github.com> --------- Co-authored-by: Ian <52504170+ibacher@users.noreply.github.com> --- .../field/id/id-field.component.tsx | 12 +- .../field/id/id-field.test.tsx | 33 +++- .../patient-registration/form-manager.test.ts | 17 ++ .../src/patient-registration/form-manager.ts | 14 +- .../identifier/identifier-input.component.tsx | 24 ++- .../identifier/identifier-input.test.tsx | 153 +++++++++++------- .../translations/en.json | 1 + 7 files changed, 174 insertions(+), 80 deletions(-) diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx index cfd27ba5e..9fb88e79e 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.component.tsx @@ -25,14 +25,16 @@ export function setIdentifierSource( selectedSource: IdentifierSource; } { const autoGeneration = identifierSource?.autoGenerationOption?.automaticGenerationEnabled; + const manualEntryEnabled = identifierSource?.autoGenerationOption?.manualEntryEnabled; return { selectedSource: identifierSource, autoGeneration, - identifierValue: autoGeneration - ? 'auto-generated' - : identifierValue !== 'auto-generated' - ? identifierValue - : initialValue, + identifierValue: + autoGeneration && !manualEntryEnabled + ? 'auto-generated' + : identifierValue !== 'auto-generated' + ? identifierValue + : initialValue, }; } diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx index 0b9e2ab20..49f28bc4b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx @@ -1,13 +1,14 @@ -import React from 'react'; -import userEvent from '@testing-library/user-event'; +import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Form, Formik } from 'formik'; -import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; -import { Identifiers } from './id-field.component'; -import { mockOpenmrsId, mockIdentifierTypes, mockPatient, mockSession } from '__mocks__'; -import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema'; -import { type Resources, ResourcesContext } from '../../../offline.resources'; +import React from 'react'; +import { mockIdentifierTypes, mockOpenmrsId, mockPatient, mockSession } from '__mocks__'; +import { esmPatientRegistrationSchema, type RegistrationConfig } from '../../../config-schema'; +import { ResourcesContext, type Resources } from '../../../offline.resources'; import { PatientRegistrationContext, type PatientRegistrationContextProps } from '../../patient-registration-context'; +import { type IdentifierSource } from '../../patient-registration.types'; +import { Identifiers, setIdentifierSource } from './id-field.component'; const mockUseConfig = jest.mocked(useConfig); @@ -120,3 +121,21 @@ describe('Identifiers', () => { expect(screen.getByRole('button', { name: 'Close overlay' })).toBeInTheDocument(); }); }); + +describe('setIdentifierSource', () => { + describe('auto-generation', () => { + it('should return auto-generated as the identifier value', () => { + const identifierSource = { autoGenerationOption: { automaticGenerationEnabled: true } } as IdentifierSource; + const { identifierValue } = setIdentifierSource(identifierSource, '', ''); + expect(identifierValue).toBe('auto-generated'); + }); + + it('should return the identifier value when manual entry enabled', () => { + const identifierSource = { + autoGenerationOption: { automaticGenerationEnabled: true, manualEntryEnabled: true }, + } as IdentifierSource; + const { identifierValue } = setIdentifierSource(identifierSource, '10001V', ''); + expect(identifierValue).toBe('10001V'); + }); + }); +}); diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts index ae625319e..e2e6eea63 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts @@ -1,8 +1,11 @@ import { FormManager } from './form-manager'; import { type FormValues } from './patient-registration.types'; +import { generateIdentifier } from './patient-registration.resource'; jest.mock('./patient-registration.resource'); +const mockGenerateIdentifier = generateIdentifier as jest.Mock; + const formValues: FormValues = { patientUuid: '', givenName: '', @@ -66,5 +69,19 @@ describe('FormManager', () => { }, ]); }); + it('should generate identifier if it has autoGeneration and manual entry disabled', async () => { + formValues.identifiers.foo.autoGeneration = true; + formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = false; + mockGenerateIdentifier.mockResolvedValue({ data: { identifier: '10001V' } }); + await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc'); + expect(mockGenerateIdentifier.mock.calls).toHaveLength(1); + }); + + it('should not generate identifiers if manual entry enabled and identifier value given', async () => { + formValues.identifiers.foo.autoGeneration = true; + formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = true; + await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc'); + expect(mockGenerateIdentifier.mock.calls).toHaveLength(0); + }); }); }); diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts index b682d038f..d1b9c9c05 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts @@ -241,11 +241,15 @@ export class FormManager { initialValue, } = patientIdentifier; - const identifier = !autoGeneration - ? identifierValue - : await ( - await generateIdentifier(selectedSource.uuid) - ).data.identifier; + const autoGenerationManualEntry = + autoGeneration && selectedSource?.autoGenerationOption?.manualEntryEnabled && !!identifierValue; + + const identifier = + !autoGeneration || autoGenerationManualEntry + ? identifierValue + : await ( + await generateIdentifier(selectedSource.uuid) + ).data.identifier; const identifierToCreate = { uuid: identifierUuid, identifier, diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx index 3a3166ceb..3a8dd0490 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx @@ -26,7 +26,8 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi () => identifierTypes.find((identifierType) => identifierType.uuid === patientIdentifier.identifierTypeUuid), [patientIdentifier, identifierTypes], ); - const { autoGeneration, initialValue, identifierValue, identifierName, required } = patientIdentifier; + const { autoGeneration, initialValue, identifierValue, identifierName, required, selectedSource } = patientIdentifier; + const manualEntryEnabled = selectedSource?.autoGenerationOption?.manualEntryEnabled; const [hideInputField, setHideInputField] = useState(autoGeneration || initialValue === identifierValue); const name = `identifiers.${fieldName}.identifierValue`; const [identifierField, identifierFieldMeta] = useField(name); @@ -46,8 +47,8 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi setFieldValue(`identifiers.${fieldName}`, { ...patientIdentifier, identifierValue: initialValue, - selectedSource: null, - autoGeneration: false, + selectedSource, + autoGeneration, } as PatientIdentifierValue); // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialValue, setHideInputField]); @@ -57,6 +58,7 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi setFieldValue(`identifiers.${fieldName}`, { ...patientIdentifier, ...setIdentifierSource(identifierType?.identifierSources?.[0], initialValue, initialValue), + ...(autoGeneration && manualEntryEnabled && { identifierValue: initialValue ?? '' }), }); }; @@ -83,9 +85,12 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi } }; + const showEditButton = !required && hideInputField && (!!initialValue || manualEntryEnabled); + const showResetButton = + (!!initialValue && initialValue !== identifierValue) || (!hideInputField && manualEntryEnabled); return (
- {!autoGeneration && !hideInputField ? ( + {!hideInputField ? ( = ({ patientIdentifier, fi /> ) : (
-

{identifierName}

-

+

+ {required ? identifierName : `${t('optionalIdentifierLabel', { identifierName })}`} +

+

{autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}

@@ -111,9 +118,10 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi
)}
- {!patientIdentifier.required && patientIdentifier.initialValue && hideInputField && ( + {showEditButton && ( )} - {initialValue && initialValue !== identifierValue && ( + {showResetButton && (
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx index 49f28bc4b..6865cc827 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/id/id-field.test.tsx @@ -1,19 +1,19 @@ -import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; +import React from 'react'; +import { Form, Formik } from 'formik'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Form, Formik } from 'formik'; -import React from 'react'; +import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; +import { type AddressTemplate, type IdentifierSource } from '../../patient-registration.types'; import { mockIdentifierTypes, mockOpenmrsId, mockPatient, mockSession } from '__mocks__'; import { esmPatientRegistrationSchema, type RegistrationConfig } from '../../../config-schema'; import { ResourcesContext, type Resources } from '../../../offline.resources'; import { PatientRegistrationContext, type PatientRegistrationContextProps } from '../../patient-registration-context'; -import { type IdentifierSource } from '../../patient-registration.types'; import { Identifiers, setIdentifierSource } from './id-field.component'; const mockUseConfig = jest.mocked(useConfig); const mockResourcesContextValue = { - addressTemplate: null, + addressTemplate: null as unknown as AddressTemplate, currentSession: mockSession.data, identifierTypes: [], relationshipTypes: [], @@ -53,7 +53,7 @@ const mockContextValues: PatientRegistrationContextProps = { setInitialFormValues: jest.fn(), validationSchema: null, values: mockInitialFormValues, -}; +} as unknown as PatientRegistrationContextProps; describe('Identifiers', () => { beforeEach(() => { diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts index e2e6eea63..b54a3ee48 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts @@ -69,6 +69,7 @@ describe('FormManager', () => { }, ]); }); + it('should generate identifier if it has autoGeneration and manual entry disabled', async () => { formValues.identifiers.foo.autoGeneration = true; formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = false; diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts index d1b9c9c05..531e8dc44 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts @@ -250,6 +250,7 @@ export class FormManager { : await ( await generateIdentifier(selectedSource.uuid) ).data.identifier; + const identifierToCreate = { uuid: identifierUuid, identifier, diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx index 3a8dd0490..a7053512c 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx @@ -117,7 +117,7 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi )}
)} -
+
{showEditButton && (