diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx
index c2c827e11a..5f9cafbaa0 100644
--- a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx
+++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx
@@ -59,6 +59,7 @@ describe('LabResultsForm', () => {
lowCritical: 40,
lowNormal: 50,
units: 'mg/dL',
+ allowDecimal: false,
} as LabOrderConcept,
isLoading: false,
error: null,
@@ -96,6 +97,58 @@ describe('LabResultsForm', () => {
});
});
+ test('validate when we have a concept with allowDecimal set to true', async () => {
+ const user = userEvent.setup();
+ // if allowDecimal is true, we should allow decimal values
+ mockUseOrderConceptByUuid.mockReturnValue({
+ concept: {
+ uuid: 'concept-uuid',
+ display: 'Test Concept',
+ setMembers: [],
+ datatype: { display: 'Numeric', hl7Abbreviation: 'NM' },
+ hiAbsolute: 100,
+ lowAbsolute: null,
+ lowCritical: null,
+ lowNormal: null,
+ hiCritical: null,
+ hiNormal: null,
+ units: 'mg/dL',
+ allowDecimal: true,
+ } as LabOrderConcept,
+ isLoading: false,
+ error: null,
+ isValidating: false,
+ mutate: jest.fn(),
+ });
+ render();
+
+ const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
+ await user.type(input, '50.5');
+
+ const saveButton = screen.getByRole('button', { name: /Save and close/i });
+ await user.click(saveButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText('Test Concept must be a whole number')).not.toBeInTheDocument();
+ });
+ });
+
+ test('validate when we have a concept with allowDecimal set to null', async () => {
+ const user = userEvent.setup();
+ render();
+
+ const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`);
+ await user.type(input, '50.5');
+
+ const saveButton = screen.getByRole('button', { name: /Save and close/i });
+ await user.click(saveButton);
+
+ // if allowDecimal is null or false, we should not allow decimal values
+ await waitFor(() => {
+ expect(screen.getByText('Test Concept must be a whole number')).toBeInTheDocument();
+ });
+ });
+
test('validate numeric input with negative value', async () => {
const user = userEvent.setup();
render();
diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts b/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts
index f42209bc1b..569dc1ae6b 100644
--- a/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts
+++ b/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts
@@ -10,8 +10,8 @@ const labEncounterRepresentation =
'obs:(uuid,obsDatetime,voided,groupMembers,formFieldNamespace,formFieldPath,order:(uuid,display),concept:(uuid,name:(uuid,name)),' +
'value:(uuid,display,name:(uuid,name),names:(uuid,conceptNameType,name))))';
const labConceptRepresentation =
- 'custom:(uuid,display,name,datatype,set,answers,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,' +
- 'setMembers:(uuid,display,answers,datatype,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units))';
+ 'custom:(uuid,display,name,datatype,set,answers,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal,' +
+ 'setMembers:(uuid,display,answers,datatype,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal))';
const conceptObsRepresentation = 'custom:(uuid,display,concept:(uuid,display),groupMembers,value)';
type NullableNumber = number | null | undefined;
@@ -33,6 +33,7 @@ export interface LabOrderConcept {
lowNormal?: NullableNumber;
lowAbsolute?: NullableNumber;
lowCritical?: NullableNumber;
+ allowDecimal?: boolean | null;
units?: string;
}
@@ -126,26 +127,26 @@ export async function updateOrderResult(
orderPayload: OrderDiscontinuationPayload,
abortController: AbortController,
) {
- const updateOrderCall = await openmrsFetch(`${restBaseUrl}/order`, {
+ const saveEncounter = await openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal,
- body: orderPayload,
+ body: obsPayload,
});
- if (updateOrderCall.status === 201) {
- const saveEncounter = await openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, {
+ if (saveEncounter.ok) {
+ const updateOrderCall = await openmrsFetch(`${restBaseUrl}/order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal,
- body: obsPayload,
+ body: orderPayload,
});
- if (saveEncounter.ok) {
+ if (updateOrderCall.status === 201) {
const fulfillOrder = await openmrsFetch(`${restBaseUrl}/order/${orderUuid}/fulfillerdetails/`, {
method: 'POST',
headers: {
diff --git a/packages/esm-patient-orders-app/src/lab-results/useLabResultsFormSchema.tsx b/packages/esm-patient-orders-app/src/lab-results/useLabResultsFormSchema.tsx
index 2a3ee3e4f0..8982e90eaf 100644
--- a/packages/esm-patient-orders-app/src/lab-results/useLabResultsFormSchema.tsx
+++ b/packages/esm-patient-orders-app/src/lab-results/useLabResultsFormSchema.tsx
@@ -105,16 +105,33 @@ const createNumericSchema = (
upperLimit: number | null | undefined,
lowerLimit: number | null | undefined,
): z.ZodType => {
+ const { allowDecimal, display } = labOrderConcept;
+ const processNumber = (val: unknown) => {
+ if (val === '' || val === null || val === undefined) return undefined;
+ const parsed = Number(val);
+ if (isNaN(parsed)) return undefined;
+ return parsed;
+ };
+
let baseSchema = z
- .preprocess((val) => {
- if (val === '' || val === null || val === undefined) return undefined;
- const parsed = Number(val);
- return isNaN(parsed) ? undefined : parsed;
- }, z.number().optional())
+ .preprocess(processNumber, z.number().optional())
.refine((val) => val === undefined || !isNaN(val), {
- message: `${labOrderConcept.display} must be a valid number`,
- });
-
+ message: `${display} must be a valid number`,
+ })
+ .refine(
+ (val) => {
+ if (val === undefined) return true;
+ if (!allowDecimal) {
+ return Number.isInteger(val);
+ }
+ return true;
+ },
+ {
+ message: !allowDecimal ? `${display} must be a whole number` : `${display} must be a valid number`,
+ },
+ );
+
+ // Add range validations
const hasLowerLimit = lowerLimit !== null && lowerLimit !== undefined;
const hasUpperLimit = upperLimit !== null && upperLimit !== undefined;
@@ -124,19 +141,19 @@ const createNumericSchema = (
if (hasLowerLimit && hasUpperLimit) {
return baseSchema.refine((val) => val === undefined || (val >= lowerLimit && val <= upperLimit), {
- message: `${labOrderConcept.display} must be between ${lowerLimit} and ${upperLimit}`,
+ message: `${display} must be between ${lowerLimit} and ${upperLimit}`,
});
}
if (hasLowerLimit) {
return baseSchema.refine((val) => val === undefined || val >= lowerLimit, {
- message: `${labOrderConcept.display} must be greater than or equal to ${lowerLimit}`,
+ message: `${display} must be greater than or equal to ${lowerLimit}`,
});
}
if (hasUpperLimit) {
return baseSchema.refine((val) => val === undefined || val <= upperLimit, {
- message: `${labOrderConcept.display} must be less than or equal to ${upperLimit}`,
+ message: `${display} must be less than or equal to ${upperLimit}`,
});
}
};