Skip to content

Commit

Permalink
O3-4043: Dispensing: Allow back dating dispense events for retrospect… (
Browse files Browse the repository at this point in the history
  • Loading branch information
mogoodrich authored Oct 4, 2024
1 parent 34d02bc commit 3c46454
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 107 deletions.
24 changes: 20 additions & 4 deletions src/components/action-buttons.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
getMostRecentMedicationDispenseStatus,
} from '../utils';
import { type PharmacyConfig } from '../config-schema';
import { initiateMedicationDispenseBody } from '../medication-dispense/medication-dispense.resource';
import { initiateMedicationDispenseBody, useProviders } from '../medication-dispense/medication-dispense.resource';
import DispenseForm from '../forms/dispense-form.component';
import PauseDispenseForm from '../forms/pause-dispense-form.component';
import CloseDispenseForm from '../forms/close-dispense-form.component';
Expand All @@ -26,6 +26,7 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ medicationRequestBundle,
const { t } = useTranslation();
const config = useConfig<PharmacyConfig>();
const session = useSession();
const providers = useProviders(config.dispenserProviderRoles);
const mostRecentMedicationDispenseStatus: MedicationDispenseStatus = getMostRecentMedicationDispenseStatus(
medicationRequestBundle.dispenses,
);
Expand Down Expand Up @@ -64,7 +65,12 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ medicationRequestBundle,
<DispenseForm
patientUuid={patientUuid}
encounterUuid={encounterUuid}
medicationDispense={initiateMedicationDispenseBody(medicationRequestBundle.request, session, true)}
medicationDispense={initiateMedicationDispenseBody(
medicationRequestBundle.request,
session,
providers,
true,
)}
medicationRequestBundle={medicationRequestBundle}
quantityRemaining={quantityRemaining}
mode="enter"
Expand All @@ -83,7 +89,12 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ medicationRequestBundle,
<PauseDispenseForm
patientUuid={patientUuid}
encounterUuid={encounterUuid}
medicationDispense={initiateMedicationDispenseBody(medicationRequestBundle.request, session, false)}
medicationDispense={initiateMedicationDispenseBody(
medicationRequestBundle.request,
session,
providers,
false,
)}
mode="enter"
/>,
)
Expand All @@ -100,7 +111,12 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ medicationRequestBundle,
<CloseDispenseForm
patientUuid={patientUuid}
encounterUuid={encounterUuid}
medicationDispense={initiateMedicationDispenseBody(medicationRequestBundle.request, session, false)}
medicationDispense={initiateMedicationDispenseBody(
medicationRequestBundle.request,
session,
providers,
false,
)}
mode="enter"
/>,
)
Expand Down
7 changes: 7 additions & 0 deletions src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export const configSchema = {
_default: false,
},
},
dispenserProviderRoles: {
_type: Type.Array,
_description:
'Array of provider roles uuids. If specified, only providers with these roles will be listed in the "Dispensed By" dropdown. Note that this simply restricts the providers that can be recorded as Dispensers, it does not limit who can create dispense events.',
_default: [],
},
medicationRequestExpirationPeriodInDays: {
_type: Type.Number,
_description: 'Medication Requests older that this will be considered expired',
Expand Down Expand Up @@ -144,6 +150,7 @@ export interface PharmacyConfig {
allowModifyingPrescription: boolean;
restrictTotalQuantityDispensed: boolean;
};
dispenserProviderRoles: [];
medicationRequestExpirationPeriodInDays: number;
locationBehavior: {
locationColumn: {
Expand Down
2 changes: 2 additions & 0 deletions src/forms/dispense-form.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
const checkIsValid = () => {
if (
medicationDispensePayload &&
medicationDispensePayload.performer &&
medicationDispensePayload.performer[0]?.actor.reference &&
medicationDispensePayload.quantity?.value &&
(!quantityRemaining || medicationDispensePayload?.quantity?.value <= quantityRemaining) &&
medicationDispensePayload.quantity?.code &&
Expand Down
35 changes: 18 additions & 17 deletions src/forms/medication-dispense-review.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ import React from 'react';
import { render } from '@testing-library/react';
import { type MedicationDispense, MedicationDispenseStatus } from '../types';
import MedicationDispenseReview from './medication-dispense-review.component';
import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';

jest.mock('@openmrs/esm-framework', () => {
const originalModule = jest.requireActual('@openmrs/esm-framework');
return {
__esModule: true,
...originalModule,
useConfig: jest.fn(() => ({
dispenseBehavior: {
allowModifyingPrescription: false,
restrictTotalQuantityDispensed: false,
},
valueSets: {
substitutionType: { uuid: '123' },
substitutionReason: { uuid: 'abc' },
},
})),
};
const mockUseConfig = jest.mocked(useConfig);
const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker);

beforeEach(() => {
mockUseConfig.mockReturnValue({
dispenseBehavior: {
allowModifyingPrescription: false,
restrictTotalQuantityDispensed: false,
},
valueSets: {
substitutionType: { uuid: '123' },
substitutionReason: { uuid: 'abc' },
},
});

mockOpenmrsDatePicker.mockReturnValue(<div />);
});

describe('Medication Dispense Review Component tests', () => {
Expand Down Expand Up @@ -129,7 +130,7 @@ describe('Medication Dispense Review Component tests', () => {
};

const mockUpdate: Function = jest.fn();
const { getByText, container } = render(
render(
<MedicationDispenseReview
medicationDispense={medicationDispense}
updateMedicationDispense={mockUpdate}
Expand Down
85 changes: 71 additions & 14 deletions src/forms/medication-dispense-review.component.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ComboBox, Dropdown, NumberInput, Stack, TextArea } from '@carbon/react';
import { useLayoutType, useConfig, useSession, userHasAccess } from '@openmrs/esm-framework';
import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenMRSMedicineDrugName } from '../utils';
import { OpenmrsDatePicker, useLayoutType, useConfig, useSession, userHasAccess } from '@openmrs/esm-framework';
import {
getConceptCodingUuid,
getMedicationReferenceOrCodeableConcept,
getOpenMRSMedicineDrugName,
isSameDay,
} from '../utils';
import MedicationCard from '../components/medication-card.component';
import { useMedicationCodeableConcept, useMedicationFormulations } from '../medication/medication.resource';
import { useMedicationRequest } from '../medication-request/medication-request.resource';
import { useMedicationRequest, usePrescriptionDetails } from '../medication-request/medication-request.resource';
import {
useOrderConfig,
useProviders,
useSubstitutionReasonValueSet,
useSubstitutionTypeValueSet,
} from '../medication-dispense/medication-dispense.resource';
import { PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS } from '../constants';
import { type Medication, type MedicationDispense } from '../types';
import { type PharmacyConfig } from '../config-schema';
import styles from '../components/medication-dispense-review.scss';
import dayjs from 'dayjs';

interface MedicationDispenseReviewProps {
medicationDispense: MedicationDispense;
Expand Down Expand Up @@ -48,11 +55,25 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({

const isTablet = useLayoutType() === 'tablet';

const allowEditing = config.dispenseBehavior.allowModifyingPrescription;

const { orderConfigObject } = useOrderConfig();
const { substitutionTypeValueSet } = useSubstitutionTypeValueSet(config.valueSets.substitutionType.uuid);
const { substitutionReasonValueSet } = useSubstitutionReasonValueSet(config.valueSets.substitutionReason.uuid);
const providers = useProviders(config.dispenserProviderRoles);

const allowEditing = config.dispenseBehavior.allowModifyingPrescription;
// get the medication request associated with this dispense event
// (we fetch this so that we can use it below to determine if the formulation dispensed varies from what was
// ordered; it's slightly inefficient/awkward to fetch it from the server here because we *have* fetched it earlier,
// it just seems cleaner to fetch it here rather than to make sure we pass it down through various components; with
// SWR handling caching, we may want to consider pulling more down into this)
const { medicationRequest } = useMedicationRequest(
medicationDispense.authorizingPrescription ? medicationDispense.authorizingPrescription[0].reference : null,
config.refreshInterval,
);

// we fetch this just to get the prescription date
const { prescriptionDate } = usePrescriptionDetails(medicationRequest ? medicationRequest.encounter.reference : null);

useEffect(() => {
if (orderConfigObject) {
Expand Down Expand Up @@ -145,16 +166,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
: null,
);

// get the medication request associated with this dispense event
// (we fetch this so that we can use it below to determine if the formulation dispensed varies from what was
// ordered; it's slightly inefficient/awkward to fetch it from the server here because we *have* fetched it earlier,
// it just seems cleaner to fetch it here rather than to make sure we pass it down through various components; with
// SWR handling caching, we may want to consider pulling more down into this)
const { medicationRequest } = useMedicationRequest(
medicationDispense.authorizingPrescription ? medicationDispense.authorizingPrescription[0].reference : null,
config.refreshInterval,
);

// check to see if the current dispense would be a substitution, and update accordingly
useEffect(() => {
if (
Expand Down Expand Up @@ -489,6 +500,52 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
});
}}
/>

<OpenmrsDatePicker
id="dispenseDate"
labelText={t('dispenseDate', 'Date of Dispense')}
minDate={prescriptionDate ? dayjs(prescriptionDate).startOf('day').toDate() : null}
maxDate={dayjs().toDate()}
onChange={(selectedDate) => {
const currentDate = dayjs(medicationDispense.whenHandedOver);
updateMedicationDispense({
...medicationDispense,
whenHandedOver: isSameDay(currentDate, selectedDate)
? currentDate.toISOString()
: selectedDate.toString(), // to preserve any time component, only update if the day actually changes
});
}}
value={dayjs(medicationDispense.whenHandedOver).toDate()}></OpenmrsDatePicker>

{providers && (
<ComboBox
id="dispenser"
light={isTablet}
initialSelectedItem={
medicationDispense?.performer[0].actor.reference
? providers.find(
(provider) => provider.uuid === medicationDispense?.performer[0].actor.reference.split('/')[1],
)
: null
}
onChange={({ selectedItem }) => {
updateMedicationDispense({
...medicationDispense,
performer: [
{
actor: {
reference: `Practitioner/${selectedItem?.uuid}`,
},
},
],
});
}}
items={providers}
itemToString={(item) => item?.person?.display}
required
titleText={t('dispensedBy', 'Dispensed by')}
/>
)}
</Stack>
</div>
);
Expand Down
7 changes: 3 additions & 4 deletions src/history/history-and-comments.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import {
import {
computeNewFulfillerStatusAfterDelete,
computeQuantityRemaining,
getDateRecorded,
getFulfillerStatus,
getMedicationRequestBundleContainingMedicationDispense,
getUuidFromReference,
revalidate,
sortMedicationDispensesByDateRecorded,
sortMedicationDispensesByWhenHandedOver,
} from '../utils';
import PauseDispenseForm from '../forms/pause-dispense-form.component';
import CloseDispenseForm from '../forms/close-dispense-form.component';
Expand Down Expand Up @@ -214,7 +213,7 @@ const HistoryAndComments: React.FC<{
{medicationRequestBundles &&
medicationRequestBundles
.flatMap((medicationDispenseBundle) => medicationDispenseBundle.dispenses)
.sort(sortMedicationDispensesByDateRecorded)
.sort(sortMedicationDispensesByWhenHandedOver)
.map((dispense) => {
return (
<div key={dispense.id}>
Expand All @@ -225,7 +224,7 @@ const HistoryAndComments: React.FC<{
fontSize: '0.9rem',
}}>
{dispense.performer && dispense.performer[0]?.actor?.display} {generateDispenseVerbiage(dispense)} -{' '}
{formatDatetime(parseDate(getDateRecorded(dispense)))}
{formatDatetime(parseDate(dispense.whenHandedOver))}
</h5>
<Tile className={styles.dispenseTile}>
{generateMedicationDispenseActionMenu(
Expand Down
1 change: 1 addition & 0 deletions src/location/location.resource.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const pharmacyConfig: PharmacyConfig = {
allowModifyingPrescription: false,
restrictTotalQuantityDispensed: false,
},
dispenserProviderRoles: [],
locationBehavior: {
locationColumn: { enabled: false },
locationFilter: { enabled: false, tag: 'Login Location' },
Expand Down
20 changes: 14 additions & 6 deletions src/medication-dispense/medication-dispense.resource.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
type MedicationRequest,
MedicationDispenseStatus,
MedicationRequestStatus,
type Provider,
} from '../types';
import dayjs from 'dayjs';

jest.mock('@openmrs/esm-framework', () => {
const originalModule = jest.requireActual('@openmrs/esm-framework');
Expand Down Expand Up @@ -46,7 +48,7 @@ describe('Medication Dispense Resource tests', () => {
};
const abortController: AbortController = {
signal: undefined,
abort(reason?: any): void {},
abort(): void {},
};

saveMedicationDispense(medicationDispense, MedicationDispenseStatus.completed, abortController);
Expand Down Expand Up @@ -79,7 +81,7 @@ describe('Medication Dispense Resource tests', () => {
};
const abortController: AbortController = {
signal: undefined,
abort(reason?: any): void {},
abort(): void {},
};

saveMedicationDispense(medicationDispense, MedicationDispenseStatus.completed, abortController);
Expand All @@ -102,7 +104,6 @@ describe('Medication Dispense Resource tests', () => {
// @ts-ignore
useSWR.mockImplementation(() => ({ data: { data: 'mockedOrderConfig' } }));
const orderConfig = useOrderConfig();
expect(useSWR).toHaveBeenCalledWith('/ws/rest/v1/orderentryconfig', openmrsFetch);
expect(orderConfig.orderConfigObject).toBe('mockedOrderConfig');
});

Expand Down Expand Up @@ -205,11 +206,18 @@ describe('Medication Dispense Resource tests', () => {
links: undefined,
},
};
const medicationRequestExpirationPeriodInDay = 30;

const providers: Provider[] = [
{
uuid: 'ghi789',
person: null,
},
];

const medicationDispense: MedicationDispense = initiateMedicationDispenseBody(
activeMedicationRequest,
session,
providers,
true,
);

Expand All @@ -227,8 +235,8 @@ describe('Medication Dispense Resource tests', () => {
expect(medicationDispense.quantity.system).toBe('http://snomed.info/sct');
expect(medicationDispense.quantity.unit).toBe('Tablet');
expect(medicationDispense.quantity.code).toBe('123456789');
expect(medicationDispense.whenPrepared).toBeNull();
expect(medicationDispense.whenHandedOver).toBeNull();
expect(dayjs(medicationDispense.whenPrepared).isToday()).toBeTruthy();
expect(dayjs(medicationDispense.whenHandedOver).isToday()).toBeTruthy();
expect(medicationDispense.dosageInstruction[0].text).toBe('Take with food');
expect(medicationDispense.dosageInstruction[0].timing.repeat.duration).toBe(30.0);
expect(medicationDispense.dosageInstruction[0].timing.repeat.durationUnit).toBe('d');
Expand Down
Loading

0 comments on commit 3c46454

Please sign in to comment.