Skip to content

Commit

Permalink
(feat) O3-4117 : Add decimal point validation for numeric test result…
Browse files Browse the repository at this point in the history
…s fields (#2074)
  • Loading branch information
donaldkibet authored Oct 30, 2024
1 parent 9be78cc commit f9a6f9f
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('LabResultsForm', () => {
lowCritical: 40,
lowNormal: 50,
units: 'mg/dL',
allowDecimal: false,
} as LabOrderConcept,
isLoading: false,
error: null,
Expand Down Expand Up @@ -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(<LabResultsForm {...testProps} />);

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(<LabResultsForm {...testProps} />);

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(<LabResultsForm {...testProps} />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +33,7 @@ export interface LabOrderConcept {
lowNormal?: NullableNumber;
lowAbsolute?: NullableNumber;
lowCritical?: NullableNumber;
allowDecimal?: boolean | null;
units?: string;
}

Expand Down Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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}`,
});
}
};

0 comments on commit f9a6f9f

Please sign in to comment.