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
…ive entry
  • Loading branch information
mogoodrich committed Sep 27, 2024
1 parent 34d02bc commit 35c1e00
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 70 deletions.
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const spaRoot = window['getOpenmrsSpaBase'];
export const basePath = '/dispensing';
export const spaBasePath = `${window.spaBase}${basePath}`;

export const datePickerFormat = 'd/m/Y';

// defined in FHIR 2 module
export const OPENMRS_FHIR_PREFIX = 'http://fhir.openmrs.org';
export const OPENMRS_FHIR_EXT_PREFIX = OPENMRS_FHIR_PREFIX + '/ext';
Expand Down
54 changes: 51 additions & 3 deletions src/forms/medication-dispense-review.component.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ComboBox, Dropdown, NumberInput, Stack, TextArea } from '@carbon/react';
import { ComboBox, DatePicker, DatePickerInput, Dropdown, NumberInput, Stack, TextArea } from '@carbon/react';
import { useLayoutType, useConfig, useSession, userHasAccess } from '@openmrs/esm-framework';
import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenMRSMedicineDrugName } 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 { datePickerFormat, 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 @@ -51,6 +53,7 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
const { orderConfigObject } = useOrderConfig();
const { substitutionTypeValueSet } = useSubstitutionTypeValueSet(config.valueSets.substitutionType.uuid);
const { substitutionReasonValueSet } = useSubstitutionReasonValueSet(config.valueSets.substitutionReason.uuid);
const providers = useProviders();

const allowEditing = config.dispenseBehavior.allowModifyingPrescription;

Expand Down Expand Up @@ -155,6 +158,9 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
config.refreshInterval,
);

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

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

<DatePicker
datePickerType="single"
dateFormat={datePickerFormat}
minDate={dayjs(prescriptionDate).startOf('day').toDate()}
maxDate={dayjs().toDate()}
onChange={([date]) => {
updateMedicationDispense({
...medicationDispense,
whenHandedOver: dayjs(date).format(),
});
}}
value={dayjs(medicationDispense.whenHandedOver).toDate()}>
<DatePickerInput id="dispenseDate" labelText={t('dispenseDate', 'Date of Dispense')}></DatePickerInput>
</DatePicker>

<ComboBox
id="dispenser"
light={isTablet}
initialSelectedItem={
providers
? 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}
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
6 changes: 3 additions & 3 deletions src/medication-dispense/medication-dispense.resource.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MedicationDispenseStatus,
MedicationRequestStatus,
} from '../types';
import dayjs from 'dayjs';

jest.mock('@openmrs/esm-framework', () => {
const originalModule = jest.requireActual('@openmrs/esm-framework');
Expand Down Expand Up @@ -102,7 +103,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 @@ -227,8 +227,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
34 changes: 15 additions & 19 deletions src/medication-dispense/medication-dispense.resource.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { fhirBaseUrl, openmrsFetch, type Session } from '@openmrs/esm-framework';
import { fhirBaseUrl, restBaseUrl, openmrsFetch, type Session } from '@openmrs/esm-framework';
import dayjs from 'dayjs';
import useSWR from 'swr';
import {
type MedicationDispense,
MedicationDispenseStatus,
type MedicationDispenseStatus,
type MedicationRequest,
type OrderConfig,
type ProviderRequestResponse,
type ValueSet,
} from '../types';

Expand All @@ -23,21 +24,9 @@ export function saveMedicationDispense(

medicationDispense.status = medicationDispenseStatus;

// timestamp if needed
if (medicationDispenseStatus === MedicationDispenseStatus.completed) {
if (medicationDispense.whenHandedOver === null) {
medicationDispense.whenHandedOver = dayjs();
}
}
// TODO for now we don't support a different prepared and handed over date, so just set the handed over to the prepared date
medicationDispense.whenPrepared = medicationDispense.whenHandedOver;

if (
// medicationDispenseStatus === MedicationDispenseStatus.in_progress || NOT YET IMPLEMENTED
medicationDispenseStatus === MedicationDispenseStatus.completed
) {
if (medicationDispense.whenPrepared === null) {
medicationDispense.whenPrepared = dayjs();
}
}
return openmrsFetch(url, {
method: method,
signal: abortController.signal,
Expand All @@ -56,7 +45,7 @@ export function deleteMedicationDispense(medicationDispenseUuid: string) {

export function useOrderConfig() {
const { data, error, isValidating } = useSWR<{ data: OrderConfig }, Error>(
`/ws/rest/v1/orderentryconfig`,
`${restBaseUrl}/orderentryconfig`,
openmrsFetch,
);
return {
Expand All @@ -67,6 +56,14 @@ export function useOrderConfig() {
};
}

export function useProviders() {
const { data } = useSWR<{ data: ProviderRequestResponse }, Error>(
`${restBaseUrl}/provider?v=custom:(uuid,person:(display))`,
openmrsFetch,
);
return data?.data?.results.sort((a, b) => a.person?.display.localeCompare(b.person?.display));
}

export function useReasonForPauseValueSet(uuid: string) {
const valueSet = useValueSet(uuid);
return { reasonForPauseValueSet: valueSet };
Expand Down Expand Up @@ -120,6 +117,7 @@ export function initiateMedicationDispenseBody(
location: {
reference: session?.sessionLocation ? `Location/${session.sessionLocation.uuid}` : '',
},
whenHandedOver: dayjs().format(),
};

if (populateDispenseInformation) {
Expand All @@ -131,8 +129,6 @@ export function initiateMedicationDispenseBody(
unit: medicationRequest.dispenseRequest?.quantity?.unit,
system: medicationRequest.dispenseRequest?.quantity?.system,
},
whenPrepared: null,
whenHandedOver: null,
dosageInstruction: [
{
// see https://openmrs.atlassian.net/browse/O3-3791 for an explanation for the reason for the below
Expand Down
10 changes: 5 additions & 5 deletions src/medication-request/medication-request.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
getMedicationReferenceOrCodeableConcept,
getPrescriptionTableActiveMedicationRequestsEndpoint,
getPrescriptionTableAllMedicationRequestsEndpoint,
sortMedicationDispensesByDateRecorded,
sortMedicationDispensesByWhenHandedOver,
computePrescriptionStatusMessageCode,
getAssociatedMedicationDispenses,
} from '../utils';
Expand Down Expand Up @@ -60,7 +60,7 @@ export function usePrescriptionsTable(
const medicationDispenses = entries
.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
.map((entry) => entry.resource as MedicationDispense)
.sort(sortMedicationDispensesByDateRecorded);
.sort(sortMedicationDispensesByWhenHandedOver);
prescriptionsTableRows = encounters.map((encounter) => {
const medicationRequestsForEncounter = medicationRequests.filter(
(medicationRequest) => medicationRequest.encounter.reference == 'Encounter/' + encounter.id,
Expand Down Expand Up @@ -123,7 +123,7 @@ function buildPrescriptionsTableRow(
};
}

export function usePrescriptionDetails(encounterUuid: string, refreshInterval) {
export function usePrescriptionDetails(encounterUuid: string, refreshInterval = null) {
const medicationRequestBundles: Array<MedicationRequestBundle> = [];
let prescriptionDate: Date;
let isLoading = true;
Expand Down Expand Up @@ -152,13 +152,13 @@ export function usePrescriptionDetails(encounterUuid: string, refreshInterval) {
const medicationDispenses = results
?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
.map((entry) => entry.resource as MedicationDispense)
.sort(sortMedicationDispensesByDateRecorded);
.sort(sortMedicationDispensesByWhenHandedOver);

medicationRequests.every((medicationRequest) =>
medicationRequestBundles.push({
request: medicationRequest,
dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
sortMedicationDispensesByDateRecorded,
sortMedicationDispensesByWhenHandedOver,
),
}),
);
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,17 @@ export interface Person {
uuid: string;
}

export interface Provider {
uuid: string;
person: {
display: string;
};
}

export interface ProviderRequestResponse {
results: Array<Provider>;
}

// represents a row in the main table
export interface PrescriptionsTableRow {
id: string;
Expand Down
Loading

0 comments on commit 35c1e00

Please sign in to comment.