From 1d6047d536e994618a5e6a18f03bf3c575adffe0 Mon Sep 17 00:00:00 2001 From: Jack Meyer Date: Tue, 3 Mar 2020 20:06:38 -0600 Subject: [PATCH] feat(patients): use randomly generated code instead of sequence BREAKING CHANGE: Any patient saved with friendly id will no longer map friendly id to the patient object, thus not displaying it in the UI or have it available for search fix #1876 --- package.json | 2 + src/__tests__/HospitalRun.test.tsx | 4 +- .../clients/db/PatientRepository.test.ts | 39 +++++++------------ .../patients/GeneralInformation.test.tsx | 4 +- .../patients/edit/EditPatient.test.tsx | 2 +- src/__tests__/patients/list/Patients.test.tsx | 6 +-- .../patients/view/ViewPatient.test.tsx | 4 +- .../edit/EditAppointment.test.tsx | 2 +- src/clients/db/PatientRepository.ts | 32 +++------------ src/model/Patient.ts | 2 +- src/patients/edit/EditPatient.tsx | 6 +-- src/patients/list/Patients.tsx | 2 +- .../related-persons/NewRelatedPersonModal.tsx | 2 +- src/patients/view/ViewPatient.tsx | 6 +-- .../appointments/AppointmentDetailForm.tsx | 4 +- 15 files changed, 43 insertions(+), 74 deletions(-) diff --git a/package.json b/package.json index 85d5a0295f..c8d444d596 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react-scripts": "3.4.0", "redux": "~4.0.5", "redux-thunk": "~2.3.0", + "shortid": "^2.2.15", "typescript": "~3.8.2" }, "repository": { @@ -56,6 +57,7 @@ "@types/react-router": "~5.1.2", "@types/react-router-dom": "~5.1.0", "@types/redux-mock-store": "~1.0.1", + "@types/shortid": "^0.0.29", "@typescript-eslint/eslint-plugin": "~2.22.0", "@typescript-eslint/parser": "~2.22.0", "commitizen": "~4.0.3", diff --git a/src/__tests__/HospitalRun.test.tsx b/src/__tests__/HospitalRun.test.tsx index bf7572ae68..15068582c7 100644 --- a/src/__tests__/HospitalRun.test.tsx +++ b/src/__tests__/HospitalRun.test.tsx @@ -84,7 +84,7 @@ describe('HospitalRun', () => { givenName: 'test', familyName: 'test', suffix: 'test', - friendlyId: 'P00001', + code: 'P00001', } as Patient mockedPatientRepository.find.mockResolvedValue(patient) @@ -163,7 +163,7 @@ describe('HospitalRun', () => { givenName: 'test', familyName: 'test', suffix: 'test', - friendlyId: 'P00001', + code: 'P00001', } as Patient mockedPatientRepository.find.mockResolvedValue(patient) diff --git a/src/__tests__/clients/db/PatientRepository.test.ts b/src/__tests__/clients/db/PatientRepository.test.ts index 6c16fe003f..1ad6ac3770 100644 --- a/src/__tests__/clients/db/PatientRepository.test.ts +++ b/src/__tests__/clients/db/PatientRepository.test.ts @@ -2,6 +2,7 @@ import { patients } from 'config/pouchdb' import PatientRepository from 'clients/db/PatientRepository' import Patient from 'model/Patient' import { fromUnixTime } from 'date-fns' +import * as shortid from 'shortid' async function removeAllDocs() { // eslint-disable-next-line @@ -36,22 +37,22 @@ describe('patient repository', () => { await removeAllDocs() }) - it('should return all records that friendly ids match search text', async () => { - // same full name to prove that it is finding by friendly id - const expectedFriendlyId = 'P00001' - await patients.put({ _id: 'someId1', friendlyId: expectedFriendlyId, fullName: 'test test' }) - await patients.put({ _id: 'someId2', friendlyId: 'P00002', fullName: 'test test' }) + it('should return all records that patient code matches search text', async () => { + // same full name to prove that it is finding by patient code + const expectedPatientCode = 'P00001' + await patients.put({ _id: 'someId1', code: expectedPatientCode, fullName: 'test test' }) + await patients.put({ _id: 'someId2', code: 'P00002', fullName: 'test test' }) - const result = await PatientRepository.search(expectedFriendlyId) + const result = await PatientRepository.search(expectedPatientCode) expect(result).toHaveLength(1) - expect(result[0].friendlyId).toEqual(expectedFriendlyId) + expect(result[0].code).toEqual(expectedPatientCode) }) it('should return all records that fullName contains search text', async () => { - await patients.put({ _id: 'id3333', friendlyId: 'P00002', fullName: 'blh test test blah' }) - await patients.put({ _id: 'id4444', friendlyId: 'P00001', fullName: 'test test' }) - await patients.put({ _id: 'id5555', friendlyId: 'P00003', fullName: 'not found' }) + await patients.put({ _id: 'id3333', code: 'P00002', fullName: 'blh test test blah' }) + await patients.put({ _id: 'id4444', code: 'P00001', fullName: 'test test' }) + await patients.put({ _id: 'id5555', code: 'P00003', fullName: 'not found' }) const result = await PatientRepository.search('test test') @@ -61,8 +62,8 @@ describe('patient repository', () => { }) it('should match search criteria with case insensitive match', async () => { - await patients.put({ _id: 'id6666', friendlyId: 'P00001', fullName: 'test test' }) - await patients.put({ _id: 'id7777', friendlyId: 'P00002', fullName: 'not found' }) + await patients.put({ _id: 'id6666', code: 'P00001', fullName: 'test test' }) + await patients.put({ _id: 'id7777', code: 'P00002', fullName: 'not found' }) const result = await PatientRepository.search('TEST TEST') @@ -100,22 +101,12 @@ describe('patient repository', () => { expect(fromUnixTime(parseInt(newPatient.id, 10)).getTime() > 0).toBeTruthy() }) - it('should generate a friendly id', async () => { + it('should generate a patient code', async () => { const newPatient = await PatientRepository.save({ fullName: 'test1 test1', } as Patient) - expect(newPatient.friendlyId).toEqual('P00001') - }) - - it('should sequentially generate a friendly id', async () => { - await patients.put({ _id: 'id9999', friendlyId: 'P00001' }) - - const newPatient = await PatientRepository.save({ - fullName: 'test3 test3', - } as Patient) - - expect(newPatient.friendlyId).toEqual('P00002') + expect(shortid.isValid(newPatient.code)).toBeTruthy() }) }) diff --git a/src/__tests__/patients/GeneralInformation.test.tsx b/src/__tests__/patients/GeneralInformation.test.tsx index ed6034153c..7178d093a5 100644 --- a/src/__tests__/patients/GeneralInformation.test.tsx +++ b/src/__tests__/patients/GeneralInformation.test.tsx @@ -42,7 +42,7 @@ describe('General Information, without isEditable', () => { phoneNumber: 'phoneNumber', email: 'email@email.com', address: 'address', - friendlyId: 'P00001', + code: 'P00001', dateOfBirth: startOfDay(subYears(new Date(), 30)).toISOString(), isApproximateDateOfBirth: false, } as Patient @@ -197,7 +197,7 @@ describe('General Information, isEditable', () => { phoneNumber: 'phoneNumber', email: 'email@email.com', address: 'address', - friendlyId: 'P00001', + code: 'P00001', dateOfBirth: startOfDay(subYears(new Date(), 30)).toISOString(), isApproximateDateOfBirth: false, } as Patient diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index 54679f6493..7a632f89a3 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -33,7 +33,7 @@ describe('Edit Patient', () => { phoneNumber: 'phoneNumber', email: 'email@email.com', address: 'address', - friendlyId: 'P00001', + code: 'P00001', dateOfBirth: new Date().toISOString(), } as Patient diff --git a/src/__tests__/patients/list/Patients.test.tsx b/src/__tests__/patients/list/Patients.test.tsx index 98fcab49c0..bcfe79afcf 100644 --- a/src/__tests__/patients/list/Patients.test.tsx +++ b/src/__tests__/patients/list/Patients.test.tsx @@ -17,7 +17,7 @@ const middlewares = [thunk] const mockStore = configureStore(middlewares) describe('Patients', () => { - const patients = [{ id: '123', fullName: 'test test', friendlyId: 'P12345' }] + const patients = [{ id: '123', fullName: 'test test', code: 'P12345' }] const mockedPatientRepository = mocked(PatientRepository, true) const setup = (isLoading?: boolean) => { @@ -67,9 +67,7 @@ describe('Patients', () => { const patientListItems = wrapper.find(ListItem) expect(patientListItems).toHaveLength(1) - expect(patientListItems.at(0).text()).toEqual( - `${patients[0].fullName} (${patients[0].friendlyId})`, - ) + expect(patientListItems.at(0).text()).toEqual(`${patients[0].fullName} (${patients[0].code})`) }) it('should add a "New Patient" button to the button tool bar', () => { diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index 473d67a8e7..29a8ad18b7 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -37,7 +37,7 @@ describe('ViewPatient', () => { phoneNumber: 'phoneNumber', email: 'email@email.com', address: 'address', - friendlyId: 'P00001', + code: 'P00001', dateOfBirth: new Date().toISOString(), } as Patient @@ -90,7 +90,7 @@ describe('ViewPatient', () => { await setup() }) expect(titleUtil.default).toHaveBeenCalledWith( - `${patient.givenName} ${patient.familyName} ${patient.suffix} (${patient.friendlyId})`, + `${patient.givenName} ${patient.familyName} ${patient.suffix} (${patient.code})`, ) }) diff --git a/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx b/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx index ef5d059fbe..7241b098bc 100644 --- a/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx +++ b/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx @@ -46,7 +46,7 @@ describe('Edit Appointment', () => { phoneNumber: 'phoneNumber', email: 'email@email.com', address: 'address', - friendlyId: 'P00001', + code: 'P00001', dateOfBirth: new Date().toISOString(), } as Patient diff --git a/src/clients/db/PatientRepository.ts b/src/clients/db/PatientRepository.ts index e92b2d8190..932b78b666 100644 --- a/src/clients/db/PatientRepository.ts +++ b/src/clients/db/PatientRepository.ts @@ -1,17 +1,11 @@ +import * as shortid from 'shortid' import Patient from '../../model/Patient' import Repository from './Repository' import { patients } from '../../config/pouchdb' -const formatFriendlyId = (prefix: string, sequenceNumber: string) => `${prefix}${sequenceNumber}` +const formatPatientCode = (prefix: string, sequenceNumber: string) => `${prefix}${sequenceNumber}` -const generateSequenceNumber = (currentNumber: number): string => { - const newNumber = currentNumber + 1 - if (newNumber < 10000) { - return newNumber.toString().padStart(5, '0') - } - - return newNumber.toString() -} +const getPatientCode = (): string => formatPatientCode('P-', shortid.generate()) export class PatientRepository extends Repository { constructor() { @@ -28,30 +22,16 @@ export class PatientRepository extends Repository { }, }, { - friendlyId: text, + code: text, }, ], }, }) } - async getFriendlyId(): Promise { - const storedPatients = await this.findAll() - - if (storedPatients.length === 0) { - return formatFriendlyId('P', generateSequenceNumber(0)) - } - - const maxPatient = storedPatients[storedPatients.length - 1] - const { friendlyId } = maxPatient - const currentSequenceNumber = friendlyId.slice(1, friendlyId.length) - - return formatFriendlyId('P', generateSequenceNumber(parseInt(currentSequenceNumber, 10))) - } - async save(entity: Patient): Promise { - const friendlyId = await this.getFriendlyId() - entity.friendlyId = friendlyId + const patientCode = getPatientCode() + entity.code = patientCode return super.save(entity) } } diff --git a/src/model/Patient.ts b/src/model/Patient.ts index 11ae80f29d..5a16deb4aa 100644 --- a/src/model/Patient.ts +++ b/src/model/Patient.ts @@ -12,7 +12,7 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati preferredLanguage?: string occupation?: string type?: string - friendlyId: string + code: string relatedPersons?: RelatedPerson[] allergies?: Allergy[] diagnoses?: Diagnosis[] diff --git a/src/patients/edit/EditPatient.tsx b/src/patients/edit/EditPatient.tsx index 5dc801c3f5..9890cf57ea 100644 --- a/src/patients/edit/EditPatient.tsx +++ b/src/patients/edit/EditPatient.tsx @@ -12,9 +12,9 @@ import { RootState } from '../../store' import { getPatientFullName, getPatientName } from '../util/patient-name-util' import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs' -const getFriendlyId = (p: Patient): string => { +const getPatientCode = (p: Patient): string => { if (p) { - return p.friendlyId + return p.code } return '' @@ -30,7 +30,7 @@ const EditPatient = () => { const { patient: reduxPatient, isLoading } = useSelector((state: RootState) => state.patient) useTitle( - `${t('patients.editPatient')}: ${getPatientFullName(reduxPatient)} (${getFriendlyId( + `${t('patients.editPatient')}: ${getPatientFullName(reduxPatient)} (${getPatientCode( reduxPatient, )})`, ) diff --git a/src/patients/list/Patients.tsx b/src/patients/list/Patients.tsx index 2a428793ee..50fa041b73 100644 --- a/src/patients/list/Patients.tsx +++ b/src/patients/list/Patients.tsx @@ -50,7 +50,7 @@ const Patients = () => {
    {patients.map((p) => ( history.push(`/patients/${p.id}`)}> - {p.fullName} ({p.friendlyId}) + {p.fullName} ({p.code}) ))}
diff --git a/src/patients/related-persons/NewRelatedPersonModal.tsx b/src/patients/related-persons/NewRelatedPersonModal.tsx index ee23cc8e41..238d640d63 100644 --- a/src/patients/related-persons/NewRelatedPersonModal.tsx +++ b/src/patients/related-persons/NewRelatedPersonModal.tsx @@ -51,7 +51,7 @@ const NewRelatedPersonModal = (props: Props) => { onChange={onPatientSelect} onSearch={async (query: string) => PatientRepository.search(query)} renderMenuItemChildren={(patient: Patient) => ( -
{`${patient.fullName} (${patient.friendlyId})`}
+
{`${patient.fullName} (${patient.code})`}
)} /> diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index c0a9ae3033..dea8cf12f3 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -18,9 +18,9 @@ import RelatedPerson from '../related-persons/RelatedPersonTab' import useAddBreadcrumbs from '../../breadcrumbs/useAddBreadcrumbs' import AppointmentsList from '../appointments/AppointmentsList' -const getFriendlyId = (p: Patient): string => { +const getPatientCode = (p: Patient): string => { if (p) { - return p.friendlyId + return p.code } return '' @@ -35,7 +35,7 @@ const ViewPatient = () => { const { patient, isLoading } = useSelector((state: RootState) => state.patient) const { permissions } = useSelector((state: RootState) => state.user) - useTitle(`${getPatientFullName(patient)} (${getFriendlyId(patient)})`) + useTitle(`${getPatientFullName(patient)} (${getPatientCode(patient)})`) const setButtonToolBar = useButtonToolbarSetter() diff --git a/src/scheduling/appointments/AppointmentDetailForm.tsx b/src/scheduling/appointments/AppointmentDetailForm.tsx index d30758394c..4dd08dc11a 100644 --- a/src/scheduling/appointments/AppointmentDetailForm.tsx +++ b/src/scheduling/appointments/AppointmentDetailForm.tsx @@ -45,9 +45,7 @@ const AppointmentDetailForm = (props: Props) => { onChange={(p: Patient[]) => onFieldChange && onFieldChange('patientId', p[0].id)} onSearch={async (query: string) => PatientRepository.search(query)} searchAccessor="fullName" - renderMenuItemChildren={(p: Patient) => ( -
{`${p.fullName} (${p.friendlyId})`}
- )} + renderMenuItemChildren={(p: Patient) =>
{`${p.fullName} (${p.code})`}
} />