Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(fix) O3-2807: Quantity Units should be required when a quantity to dispense is specified #1636

Merged
merged 19 commits into from
Feb 13, 2024

Conversation

mccarthyaaron
Copy link
Contributor

@mccarthyaaron mccarthyaaron commented Feb 3, 2024

Requirements

Summary

In the drug-order-form component, the dispensing quantity units will default to the dosage units if the quantity to be dispensed is greater than 0 while still giving the user the option to select their preferred quantity units. I attempt to achieve the result in two ways. Firstly changing the dosage units will trigger a corresponsing change in the quantity units but only if the quantity to be dispensed is not 0. Secondly, a change in the quantity to be dispensed to a value greater than 0 will trigger the quantity units to take on the value of the dosage units at the time. In both cases the user can still change the quantity units to a value other than the one defined in the dosing units.

Related Issue

https://openmrs.atlassian.net/browse/O3-2807

Screenshots

drug-order-form.mp4

image

@vasharma05
Copy link
Member

only if the quantity to be dispensed is not 0.
Any specific reason why we are looking into quantity here?
Thanks!

@vasharma05
Copy link
Member

The Dose unit and Quantity units don't need to always be the same. E.g. the Dose unit can be Tablets, but the quantity units might be 3 boxes of Tablet, so it's wrong to assume that these will always be the same. So, the right condition I feel should be: If dose unit === quantityUnit then only the quantityUnit should be updated with the new doseUnit, else it shouldn't.

@vasharma05
Copy link
Member

Also, the ticket is not understood properly:
The title says: Quantity Units should be required when a quantity to dispense is specified, which is more about the form validation, which is not taken care of here.

@denniskigen
Copy link
Member

denniskigen commented Feb 6, 2024

Could you add a test?

@vasharma05
Copy link
Member

Hi, @denniskigen!
Currently, there are no tests written for the drug order form right now.
I can write the tests in a separate file.
Thanks!

@ibacher
Copy link
Member

ibacher commented Feb 6, 2024

If dose unit === quantityUnit then only the quantityUnit should be updated with the new doseUnit, else it shouldn't.

In general, the way I would expect this to work is:

  • By default neither dose unit nor quantity unit is set (unless specified in an order template)
  • If the dose unit is set (including by order template), but the quantity unit is unset, the quantity unit is set to the dose unit
  • In no other circumstance do we automatically change either the dose unit or quantity unit
  • Dispensing instructions are optional, so validation of anything here should only happen if some quantity to dispense is specified (it's ok to update the quantity unit as long as this won't trigger validation).

@mccarthyaaron
Copy link
Contributor Author

@ibacher could you kindly take a look at the changes I have a pushed and suggest any modifications

@vasharma05
Copy link
Member

Hi @ibacher!

By default neither dose unit nor quantity unit is set (unless specified in an order template)

No, it's also defined in the drug's object, we define the default dose unit from drug's dosageForm:

@@ -158,13 +171,29 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
duration: initialOrderBasketItem?.duration,
durationUnit: initialOrderBasketItem?.durationUnit,
pillsDispensed: initialOrderBasketItem?.pillsDispensed,
quantityUnits: initialOrderBasketItem?.quantityUnits,
quantityUnits: initialOrderBasketItem?.quantityUnits ?? initialOrderBasketItem?.unit,
Copy link
Member

@vasharma05 vasharma05 Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -177,8 +177,8 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
newValue: MedicationOrderFormData['unit'],
prevValue: MedicationOrderFormData['unit'],
) => {
if (prevValue?.valueCoded === watch('quantityUnits')?.valueCoded) {
setValue('quantityUnits', newValue);
if (!watch('quantityUnits')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this condition changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If the dose unit is set (including by order template), but the quantity unit is unset, the quantity unit is set to the dose unit
  • In no other circumstance do we automatically change either the dose unit or quantity unit

@vasharma05 with the previous condition, if the dose unit and quantity unit were the same, then changing the dose units was also changing the quantity unit to the new value of the dose unit. Yet according to @ibacher the dose unit should only change the quantity unit if the quantity unit is unset. Also bullet 2 above emphasizes it again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vasharma05 @ibacher could you kindly offer some clarity on this; should a change in the dose unit trigger a change in the quantity unit if they the values of both are the same? Which happens to be what @vasharma05 wants. What I understood from @ibacher is that the dose unit should only change the quantity unit if the quantity unit is not set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dosing units are only rarely dispensing units. For example, a relatively common in-patient order might be for 1000 ml of saline, which is commonly dispensed as either 1 or 2 bags, depending on availability. Here the dosing is 1000 ml, but the dispensing units are in "bags". Similarly, for paracetamol, you might be given a dose of 650 mg, but that's dispensed in the form of 2 325 mg tablets per dose. Again, the dosing is in mg, but the dispensing is in tablets. There are a few narrow cases, though, where the dose ordered is the same units as the thing dispensed. This feature is targeted at those narrow cases.

So, my opinion is that once a user has selected a dispensing unit, we should not change that, because in most cases, the dispensing unit is not going to be the same as the dosing unit. Prior to the user making that choice, however, its fine to automatically update, as for a few cases this will make people's lives easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Thanks

@vasharma05 vasharma05 changed the title (fix) O3-2807: Quantity units default to dose unit when the quantity is not zero (fix) O3-2807: Quantity Units should be required when a quantity to dispense is specified Feb 9, 2024
@denniskigen
Copy link
Member

denniskigen commented Feb 9, 2024

Tangential to this, but the forms inputs ought to have an error outline when validation fails, which is not the case here:

CleanShot 2024-02-09 at 2  44 38@2x

Additionally, the error messages need a bit of breathing room. They're too close to the inputs.

Could you help ticket that out under the bug fix epic, @vasharma05?

return true;
},
{
message: 'Please select Quantity unit',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
message: 'Please select Quantity unit',
message: 'Please select a quantity unit',

@vasharma05, could you guide @mccarthyaaron on how to make these error messages translatable? We should probably add an example to our Coding Conventions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be eager to learn how to do that. Thanks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mccarthyaaron!
You can refer #1652.
Thanks!

prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
) => void;
control: Control<MedicationOrderFormData>;
[x: string]: any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a better type here than any? any completely bails out of the type system.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly typing this requires a discriminated union, since the values of restProps depends on the type. Something like:

type ControlledFieldInputProps = BaseControlledFieldInputProps & (
  ToggleFieldProps | CheckboxFieldProps | NumberFieldProps | TextAreaProps | TextInputProps | ComboBoxProps
)

interface BaseControlledFieldInputProps {
  name: keyof MedicationOrderFormData;
	handleAfterChange?: (
	   newValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	   prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	 ) => void;
   control: Control<MedicationOrderFormData>;
}

interface ToggleFieldProps extends ToggleProps /* part of Carbon */ {
  type: 'toggle',
}

// repeat for other types

And that should give us something fully typed.

Copy link
Member

@denniskigen denniskigen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, @mccarthyaaron!

@ibacher I've tested things out locally against the functional requirements you laid out, and they seem to work OK for me. Do you have any qualms about the approach?

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mostly fine with the logic. A few small nits and one edge-case requirement. Thanks for the great work @mccarthyaaron!

@@ -143,7 +156,7 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
return initialOrderBasketItem?.startDate as Date;
}, [initialOrderBasketItem?.startDate]);

const { handleSubmit, control, watch, setValue } = useForm<MedicationOrderFormData>({
const methods = useForm<MedicationOrderFormData>({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the methods variable?

newValue: MedicationOrderFormData['unit'],
prevValue: MedicationOrderFormData['unit'],
) => {
if (prevValue?.valueCoded === watch('quantityUnits')?.valueCoded) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be substituted (more correctly) with getValues() (also returned from useForm(). watch() is intended to subscribe to input changes, but here it's used to get a point-in-time value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is passed as a prop to a component, it would be best to wrap this in useCallback(). Otherwise, each render will re-render the ControlledFieldInput.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I don't think the logic here avoids the edge-case where the user has selected a quantityUnits value that happens to be the same as the dosingUnits, but then changes the dosingUnits. I'm ok with automatically changing the value as long as its been automatically determined, but user input (especially for medication instructions) needs to always override automatic determinations.

prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
) => void;
control: Control<MedicationOrderFormData>;
[x: string]: any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly typing this requires a discriminated union, since the values of restProps depends on the type. Something like:

type ControlledFieldInputProps = BaseControlledFieldInputProps & (
  ToggleFieldProps | CheckboxFieldProps | NumberFieldProps | TextAreaProps | TextInputProps | ComboBoxProps
)

interface BaseControlledFieldInputProps {
  name: keyof MedicationOrderFormData;
	handleAfterChange?: (
	   newValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	   prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	 ) => void;
   control: Control<MedicationOrderFormData>;
}

interface ToggleFieldProps extends ToggleProps /* part of Carbon */ {
  type: 'toggle',
}

// repeat for other types

And that should give us something fully typed.

@mccarthyaaron
Copy link
Contributor Author

@ibacher pushed the requested changes. anymore recommended changes?

@ibacher
Copy link
Member

ibacher commented Feb 9, 2024

@mccarthyaaron Could you take a look into the Typescript thing?

@mccarthyaaron
Copy link
Contributor Author

@mccarthyaaron Could you take a look into the Typescript thing?

@ibacher could you kindly direct me to a resource with the carbon types if possible?

@ibacher
Copy link
Member

ibacher commented Feb 9, 2024

@mccarthyaaron The Carbon source code is quite readable and has the types. Just make sure you're looking at the v11.37.0 tag.

@mccarthyaaron
Copy link
Contributor Author

@mccarthyaaron The Carbon source code is quite readable and has the types. Just make sure you're looking at the v11.37.0 tag.

For educative purposes, is there a reason it has to be that specific tag?

@ibacher
Copy link
Member

ibacher commented Feb 9, 2024

Is the version of Carbon we're currently using. The current version of Carbon may have any number of differences.

@mccarthyaaron
Copy link
Contributor Author

mccarthyaaron commented Feb 9, 2024

@ibacher I am running into this error: Cannot use namespace 'ToggleProps' as a type. I think I imported the correct type because the import statement bringing it in isn't throwing any errors. I don't know whats causing that. Does it need downloading the @types/carbon-components-react package

@mccarthyaaron
Copy link
Contributor Author

@ibacher I have something for the types thing. Feels makeshift-ish especially with the importing of the types. For some reason I can't use the carbon types imported from '@carbon/react'. I get the error Cannot use namespace as type so I imported each of them individualy in a format like this;
import { type ComboBoxProps } from '@carbon/react/lib/components/ComboBox/ComboBox';
There are some other details that would need some polishing and feedback from you. Can I push the saidchanges here?

Copy link
Member

@vasharma05 vasharma05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@vasharma05 vasharma05 merged commit a659a8e into openmrs:main Feb 13, 2024
6 checks passed
@ibacher
Copy link
Member

ibacher commented Feb 13, 2024

@mccarthyaaron Oh right... we kind of tell TS to ignore the types from Carbon... Although the preferred fallback solution would've been to mark those properties as unknown in this case, I think.

@mccarthyaaron mccarthyaaron deleted the drug-order-form branch March 6, 2024 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants