Skip to content

Commit

Permalink
(fix) O3-4015 - Ward App - unassign bed from patient when they are tr…
Browse files Browse the repository at this point in the history
…ansferred
  • Loading branch information
chibongho committed Oct 21, 2024
1 parent f2f1e1b commit 0c9ebf1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 89 deletions.
9 changes: 9 additions & 0 deletions packages/esm-ward-app/src/hooks/useAssignedBedByPatient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { type BedDetail } from '../types';
import useSWR from 'swr';

export function useAssignedBedByPatient(patientUuid: string) {
const url = `${restBaseUrl}/beds?patientUuid=${patientUuid}`;

return useSWR<{ data: { results: Array<BedDetail> } }, Error>(url, openmrsFetch);
}
10 changes: 9 additions & 1 deletion packages/esm-ward-app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ export interface Bed {
status: BedStatus;
}

export interface BedDetail {
bedId: number;
bedNumber: number;
bedType: BedType;
physicalLocation: Location;
patients: Array<Patient>;
}

export interface BedLayout {
rowNumber: number;
columnNumber: number;
Expand Down Expand Up @@ -232,4 +240,4 @@ export interface MaternalWardViewContext {
motherChildrenRelationshipsByPatient: Map<string, MotherAndChild[]>;
}

export type PatientWorkspaceAdditionalProps = Omit<WardPatientWorkspaceProps, keyof DefaultWorkspaceProps>;
export type PatientWorkspaceAdditionalProps = Omit<WardPatientWorkspaceProps, keyof DefaultWorkspaceProps>;
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { openmrsFetch, showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
import { showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { mockAdmissionLocation, mockLocationInpatientWard, mockPatientAlice } from '../../../../../__mocks__';
import { renderWithSwr } from '../../../../../tools';
import { mockWardPatientGroupDetails, mockWardViewContext } from '../../../mock';
import { useAssignedBedByPatient } from '../../hooks/useAssignedBedByPatient';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
import useWardLocation from '../../hooks/useWardLocation';
import type { DispositionType, WardViewContext } from '../../types';
import { assignPatientToBed, createEncounter, removePatientFromBed } from '../../ward.resource';
import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
import type { AdmitPatientFormWorkspaceProps } from './types';

Expand All @@ -31,12 +33,25 @@ jest.mock('../../hooks/useInpatientAdmission', () => ({
useInpatientAdmission: jest.fn(),
}));

jest.mock('../../hooks/useAssignedBedByPatient', () => ({
useAssignedBedByPatient: jest.fn(),
}));

jest.mock('../../ward.resource', () => ({
createEncounter: jest.fn(),
assignPatientToBed: jest.fn(),
removePatientFromBed: jest.fn(),
}));

const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
const mockedUseWardLocation = jest.mocked(useWardLocation);
const mockedOpenmrsFetch = jest.mocked(openmrsFetch);
const mockedUseFeatureFlag = jest.mocked(useFeatureFlag);
const mockedShowSnackbar = jest.mocked(showSnackbar);
const mockedUseSession = jest.mocked(useSession);
const mockedUseAssignedBedByPatient = jest.mocked(useAssignedBedByPatient);
const mockedAssignPatientToBed = jest.mocked(assignPatientToBed);
const mockedCreateEncounter = jest.mocked(createEncounter);
const mockedRemovePatientFromBed = jest.mocked(removePatientFromBed);

jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);

Expand Down Expand Up @@ -91,6 +106,39 @@ describe('Testing AdmitPatientForm', () => {
isLoadingLocation: false,
errorFetchingLocation: null,
});
mockedUseAssignedBedByPatient.mockReturnValue({
data: {
data: {
results: [
{
bedId: 1,
bedNumber: 1,
bedType: null,
patients: [mockPatientAlice],
physicalLocation: mockLocationInpatientWard,
},
],
},
},
} as ReturnType<typeof useAssignedBedByPatient>);

// @ts-ignore - we only need these two keys for now
mockedCreateEncounter.mockResolvedValue({
ok: true,
data: {
uuid: 'encounter-uuid',
},
});

// @ts-ignore - we only need the ok key for now
mockedAssignPatientToBed.mockResolvedValue({
ok: true,
});

// @ts-ignore - we only need the ok key for now
mockedRemovePatientFromBed.mockResolvedValue({
ok: true,
});
});

it('should render admit patient form', async () => {
Expand Down Expand Up @@ -152,13 +200,6 @@ describe('Testing AdmitPatientForm', () => {
});

it('should submit the form, create encounter and submit bed', async () => {
// @ts-ignore - we only need these two keys for now
mockedOpenmrsFetch.mockResolvedValue({
ok: true,
data: {
uuid: 'encounter-uuid',
},
});
const user = userEvent.setup();
renderAdmissionForm();
const combobox = screen.getByRole('combobox', {
Expand All @@ -170,35 +211,19 @@ describe('Testing AdmitPatientForm', () => {
const admitButton = screen.getByRole('button', { name: 'Admit' });
expect(admitButton).toBeEnabled();
await user.click(admitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: {
patient: mockPatientAlice.uuid,
encounterType: 'admission-encounter-type-uuid',
location: mockAdmissionLocation.ward.uuid,
obs: [],
encounterProviders: [
{
provider: 'current-provider-uuid',
encounterRole: 'clinician-encounter-role-uuid',
},
],
},
});
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/beds/3', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: {
patientUuid: mockPatientAlice.uuid,
encounterUuid: 'encounter-uuid',
},
expect(mockedCreateEncounter).toHaveBeenCalledWith({
patient: mockPatientAlice.uuid,
encounterType: 'admission-encounter-type-uuid',
location: mockAdmissionLocation.ward.uuid,
obs: [],
encounterProviders: [
{
provider: 'current-provider-uuid',
encounterRole: 'clinician-encounter-role-uuid',
},
],
});
expect(mockedAssignPatientToBed).toHaveBeenCalledWith(3, mockPatientAlice.uuid, 'encounter-uuid');
expect(mockedShowSnackbar).toHaveBeenCalledWith({
kind: 'success',
subtitle: '{{patientName}} has been successfully admitted and assigned to bed bed3',
Expand All @@ -207,7 +232,7 @@ describe('Testing AdmitPatientForm', () => {
});

it('should show snackbar if there was an issue creating an encounter', async () => {
mockedOpenmrsFetch.mockRejectedValue(new Error('Failed to create encounter'));
mockedCreateEncounter.mockRejectedValue(new Error('Failed to create encounter'));
const user = userEvent.setup();
renderAdmissionForm();
const combobox = screen.getByRole('combobox', {
Expand All @@ -219,7 +244,6 @@ describe('Testing AdmitPatientForm', () => {
const admitButton = screen.getByRole('button', { name: 'Admit' });
expect(admitButton).toBeEnabled();
await user.click(admitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(1);
expect(mockedShowSnackbar).toHaveBeenCalledWith({
kind: 'error',
title: 'Failed to admit patient',
Expand All @@ -228,18 +252,7 @@ describe('Testing AdmitPatientForm', () => {
});

it('should show warning snackbar if encounter was created and bed assignment was not successful', async () => {
// @ts-ignore - matching whole FetchResponse type is not necessary
mockedOpenmrsFetch.mockImplementation((url) => {
if (url.startsWith('/ws/rest/v1/beds')) {
return Promise.reject(new Error('Failed to assign bed'));
}
return Promise.resolve({
ok: true,
data: {
uuid: 'encounter-uuid',
},
});
});
mockedAssignPatientToBed.mockRejectedValue(new Error('Failed to assign bed'));

const user = userEvent.setup();
renderAdmissionForm();
Expand All @@ -252,49 +265,32 @@ describe('Testing AdmitPatientForm', () => {
const admitButton = screen.getByRole('button', { name: 'Admit' });
expect(admitButton).toBeEnabled();
await user.click(admitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
expect(mockedShowSnackbar).toHaveBeenCalledWith({
kind: 'warning',
title: 'Patient admitted successfully',
subtitle: 'Patient admitted successfully but fail to assign bed to patient',
});
});

it('should admit patient if no beds are configured', async () => {
// @ts-ignore - we only need these two keys for now
mockedOpenmrsFetch.mockResolvedValue({
ok: true,
data: {
uuid: 'encounter-uuid',
},
});
it('should admit patient if no bed is selected', async () => {
const user = userEvent.setup();
renderAdmissionForm();
const admitButton = screen.getByRole('button', { name: 'Admit' });
expect(admitButton).toBeEnabled();
await user.click(admitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: {
patient: mockPatientAlice.uuid,
encounterType: 'admission-encounter-type-uuid',
location: mockAdmissionLocation.ward.uuid,
obs: [],
encounterProviders: [
{
provider: 'current-provider-uuid',
encounterRole: 'clinician-encounter-role-uuid',
},
],
},
});
expect(mockedOpenmrsFetch).toHaveBeenCalledWith(`/ws/rest/v1/beds/1?patientUuid=${mockPatientAlice.uuid}`, {
method: 'DELETE',
expect(mockedCreateEncounter).toHaveBeenCalledWith({
patient: mockPatientAlice.uuid,
encounterType: 'admission-encounter-type-uuid',
location: mockAdmissionLocation.ward.uuid,
obs: [],
encounterProviders: [
{
provider: 'current-provider-uuid',
encounterRole: 'clinician-encounter-role-uuid',
},
],
});
expect(mockedRemovePatientFromBed).toHaveBeenCalledWith(1, mockPatientAlice.uuid);
expect(mockedShowSnackbar).toHaveBeenCalledWith({
kind: 'success',
subtitle: 'Patient admitted successfully to Inpatient Ward',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { BedLayout, WardViewContext } from '../../types';
import { assignPatientToBed, createEncounter, removePatientFromBed } from '../../ward.resource';
import styles from './admit-patient-form.scss';
import type { AdmitPatientFormWorkspaceProps } from './types';
import { useAssignedBedByPatient } from '../../hooks/useAssignedBedByPatient';

const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
patient,
Expand All @@ -25,10 +26,13 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
const [isSubmitting, setIsSubmitting] = useState(false);
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
const {wardPatientGroupDetails} = useAppContext<WardViewContext>('ward-view-context') ?? {};
const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
const { isLoading, mutate: mutateAdmissionLocation } = wardPatientGroupDetails?.admissionLocationResponse ?? {};
const { mutate: mutateInpatientRequest } = wardPatientGroupDetails?.inpatientRequestResponse ?? {};
const { mutate: mutateInpatientAdmission } = wardPatientGroupDetails?.inpatientAdmissionResponse ?? {};
const { data: bedsAssignedToPatient, isLoading: isLoadingBedsAssignedToPatient } = useAssignedBedByPatient(
patient.uuid,
);
const beds = isLoading ? [] : wardPatientGroupDetails?.bedLayouts ?? [];
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
const getBedRepresentation = useCallback((bedLayout: BedLayout) => {
Expand Down Expand Up @@ -92,11 +96,9 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
if (bedSelected) {
return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
} else {
const bed = wardPatientGroupDetails.bedLayouts.find((bedLayout) =>
bedLayout.patients.some((p) => p.uuid == patient.uuid),
);
if (bed) {
return removePatientFromBed(bed.bedId, patient.uuid);
const assignedBedId = bedsAssignedToPatient?.data?.results?.[0]?.bedId;
if (assignedBedId) {
return removePatientFromBed(assignedBedId, patient.uuid);
}
return response;
}
Expand Down Expand Up @@ -272,7 +274,13 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
<Button
type="submit"
size="xl"
disabled={isSubmitting || isLoadingEmrConfiguration || errorFetchingEmrConfiguration}>
disabled={
isSubmitting ||
isLoadingEmrConfiguration ||
errorFetchingEmrConfiguration ||
isLoading ||
isLoadingBedsAssignedToPatient
}>
{!isSubmitting ? t('admit', 'Admit') : t('admitting', 'Admitting...')}
</Button>
</ButtonSet>
Expand Down

0 comments on commit 0c9ebf1

Please sign in to comment.