diff --git a/packages/fhir-group-management/src/components/CommodityAddEdit/Default/utils.ts b/packages/fhir-group-management/src/components/CommodityAddEdit/Default/utils.ts index 2ade8eb21..1c06565b1 100644 --- a/packages/fhir-group-management/src/components/CommodityAddEdit/Default/utils.ts +++ b/packages/fhir-group-management/src/components/CommodityAddEdit/Default/utils.ts @@ -22,8 +22,9 @@ import { snomedCodeSystem, supplyMgSnomedCode, } from '../../../helpers/utils'; -import { TypeOfGroup, UnitOfMeasure } from '../../ProductForm/utils'; +import { UnitOfMeasure } from '../../ProductForm/utils'; import { GroupFormFields } from '../../ProductForm/types'; +import { R4GroupTypeCodes } from '@opensrp/fhir-helpers'; export const defaultCharacteristic = { code: { @@ -55,7 +56,7 @@ export const validationRulesFactory = (t: TFunction) => { { required: true, message: t('Required') }, ] as Rule[], [active]: [{ type: 'boolean' }, { required: true, message: t('Required') }] as Rule[], - [type]: [{ type: 'enum', enum: Object.values(TypeOfGroup), required: true }] as Rule[], + [type]: [{ type: 'enum', enum: Object.values(R4GroupTypeCodes), required: true }] as Rule[], [unitOfMeasure]: [ { type: 'enum', enum: Object.values(UnitOfMeasure), required: true }, ] as Rule[], @@ -133,7 +134,7 @@ export const generateGroupPayload = ( ]; if (type) { - payload.type = type as TypeOfGroup; + payload.type = type as R4GroupTypeCodes; } if (unitOfMeasure) { diff --git a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/index.tsx b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/index.tsx index 79dacf419..94dfb28b4 100644 --- a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/index.tsx +++ b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Helmet } from 'react-helmet'; import { CommodityForm } from '../../ProductForm'; import { useParams } from 'react-router'; -import { LIST_COMMODITY_URL, unitOfMeasure } from '../../../constants'; +import { LIST_COMMODITY_URL, type, unitOfMeasure } from '../../../constants'; import { Spin } from 'antd'; import { BodyLayout } from '@opensrp/react-utils'; import { BrokenPage } from '@opensrp/react-utils'; @@ -73,7 +73,7 @@ export const CommodityAddEdit = (props: GroupAddEditProps) => { { group: IGroup; binary?: IBinary; binaryChanged: boolean }, EusmGroupFormFields > - hidden={[unitOfMeasure]} + hidden={[unitOfMeasure, type]} fhirBaseUrl={fhirBaseUrl} initialValues={initialValues} cancelUrl={LIST_COMMODITY_URL} diff --git a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/tests/__snapshots__/index.test.tsx.snap b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/tests/__snapshots__/index.test.tsx.snap index 0d1af58e0..ba340f077 100644 --- a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/tests/__snapshots__/index.test.tsx.snap +++ b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/tests/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders correctly: A catch all 1`] = `"Create commodityCommodity IdIdentifierEnter commodity nameMaterial numberSelect commodity statusActiveDisabledSelect commodity TypeSelect commodity typeSelect the unit of measureSelect the unit of measureAttractive itemYesNoIs it thereIs it in good conditionIs it being used appropriatelyAccountability period (in months)Photo of the productUploadsaveCancel"`; +exports[`renders correctly: A catch all 1`] = `"Create commodityCommodity IdIdentifierEnter commodity nameMaterial numberSelect commodity statusActiveDisabledSelect commodity TypeSubstanceSelect the unit of measureSelect the unit of measureAttractive itemYesNoIs it thereIs it in good conditionIs it being used appropriatelyAccountability period (in months)Photo of the productUploadsaveCancel"`; exports[`renders correctly: active radio button 1`] = ` ({ __esModule: true, @@ -193,7 +192,7 @@ test('form validation works', async () => { const errorNodes = [...document.querySelectorAll('.ant-form-item-explain-error')]; const errorMsgs = errorNodes.map((node) => node.textContent); - expect(errorMsgs).toEqual(['Required', 'Required', "'type' is required", 'Required']); + expect(errorMsgs).toEqual(['Required', 'Required', 'Required']); }); it('can create new commodity', async () => { @@ -264,16 +263,6 @@ it('can create new commodity', async () => { // confirm upload button is no longer visible await waitForElementToBeRemoved(screen.getByTestId('upload-button')); - // simulate value selection for type - const groupTypeSelectConfig = { - selectId: 'type', - searchOptionText: 'sub', - fullOptionText: 'Substance', - beforeFilterOptions: ['Medication', 'Device', 'Substance'], - afterFilterOptions: ['Substance'], - }; - fillSearchableSelect(groupTypeSelectConfig); - fireEvent.click(screen.getByRole('button', { name: /Save/i })); await waitFor(() => { diff --git a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/utils.ts b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/utils.ts index 020c18419..207a1fbf4 100644 --- a/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/utils.ts +++ b/packages/fhir-group-management/src/components/CommodityAddEdit/Eusm/utils.ts @@ -48,12 +48,12 @@ import { unitOfMeasureCharacteristic, smartRegisterCodeSystem, } from '../../../helpers/utils'; -import { TypeOfGroup } from '../../ProductForm/utils'; import { GroupFormFields } from '../../ProductForm/types'; import { GroupCharacteristic } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/groupCharacteristic'; import { IBinary } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBinary'; import { UploadFile } from 'antd'; import { Coding } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/coding'; +import { R4GroupTypeCodes } from '@opensrp/fhir-helpers'; export type EusmGroupFormFields = GroupFormFields<{ group: IGroup; binary?: IBinary }>; @@ -91,7 +91,7 @@ export const validationRulesFactory = (t: TFunction) => { { required: true, message: t('Required') }, ] as Rule[], [active]: [{ type: 'boolean' }, { required: true, message: t('Required') }] as Rule[], - [type]: [{ type: 'enum', enum: Object.values(TypeOfGroup), required: true }] as Rule[], + [type]: [{ type: 'enum', enum: Object.values(R4GroupTypeCodes), required: true }] as Rule[], [isAttractiveItem]: [{ type: 'boolean' }] as Rule[], [availability]: [{ type: 'string' }, { required: true, message: t('Required') }] as Rule[], [condition]: [{ type: 'string' }] as Rule[], @@ -333,7 +333,11 @@ export async function getProductImagePayload( */ export const getGroupFormFields = (obj?: IGroup, binary?: IBinary): EusmGroupFormFields => { if (!obj) { - return { initialObject: { group: { code: defaultCode } }, active: true } as EusmGroupFormFields; + return { + initialObject: { group: { code: defaultCode } }, + active: true, + type: R4GroupTypeCodes.SUBSTANCE, + } as EusmGroupFormFields; } const { id, name, active, identifier, type } = obj; @@ -391,7 +395,7 @@ export const getGroupFormFields = (obj?: IGroup, binary?: IBinary): EusmGroupFor materialNumber: get(identifierObj, '0.value'), active, name, - type, + type: type ?? R4GroupTypeCodes.SUBSTANCE, ...formFieldsFromCharacteristics, productImage: productImageFromUrl, }; @@ -471,7 +475,7 @@ export const generateGroupPayload = async ( } if (type) { - payload.type = type as TypeOfGroup; + payload.type = type as R4GroupTypeCodes; } const existingCharacteristics = initialGroupObject?.characteristic ?? []; diff --git a/packages/fhir-group-management/src/components/LocationInventory/tests/fixtures.ts b/packages/fhir-group-management/src/components/LocationInventory/tests/fixtures.ts index 0f7bee993..2f9917b2f 100644 --- a/packages/fhir-group-management/src/components/LocationInventory/tests/fixtures.ts +++ b/packages/fhir-group-management/src/components/LocationInventory/tests/fixtures.ts @@ -112,7 +112,7 @@ export const productCharacteristics = [ coding: [ { code: '33467722', - display: 'Quantity ', + display: 'Quantity', system: 'http://smartregister.org/codes', }, ], @@ -1309,7 +1309,7 @@ export const createdInventoryGroup1 = { { code: { coding: [ - { system: 'http://smartregister.org/codes', code: '33467722', display: 'Quantity ' }, + { system: 'http://smartregister.org/codes', code: '33467722', display: 'Quantity' }, ], }, valueQuantity: { value: '20' }, diff --git a/packages/fhir-group-management/src/components/LocationInventory/tests/utils.test.tsx b/packages/fhir-group-management/src/components/LocationInventory/tests/utils.test.tsx index e308c938c..b7b53dbfd 100644 --- a/packages/fhir-group-management/src/components/LocationInventory/tests/utils.test.tsx +++ b/packages/fhir-group-management/src/components/LocationInventory/tests/utils.test.tsx @@ -49,13 +49,30 @@ describe('fhir-group-management/src/components/LocationInventory/utils', () => { const noAttractiveCharacteristic = commodity1.characteristic?.filter( (char) => char.code.coding?.[0].code !== attractiveCharacteristicCode ); - const newCommodity = { + const nonAttractiveItem = { ...commodity1, characteristic: noAttractiveCharacteristic, }; + const attractiveItem = { + ...commodity1, + characteristic: [ + { + code: { + coding: [ + { + system: 'http://smartregister.org/codes', + code: '23435363', + display: 'Attractive Item code', + }, + ], + }, + valueBoolean: true, + }, + ], + }; expect(isAttractiveProduct()).toEqual(false); - expect(isAttractiveProduct(commodity1)).toEqual(true); - expect(isAttractiveProduct(newCommodity)).toEqual(false); + expect(isAttractiveProduct(attractiveItem)).toEqual(true); + expect(isAttractiveProduct(nonAttractiveItem)).toEqual(false); }); it('get item accounterbility months works as expected', () => { diff --git a/packages/fhir-group-management/src/components/LocationInventory/utils.tsx b/packages/fhir-group-management/src/components/LocationInventory/utils.tsx index 56e68bd26..be0504fa2 100644 --- a/packages/fhir-group-management/src/components/LocationInventory/utils.tsx +++ b/packages/fhir-group-management/src/components/LocationInventory/utils.tsx @@ -25,8 +25,8 @@ import dayjs, { Dayjs } from 'dayjs'; import { TFunction } from '@opensrp/i18n'; import { Rule } from 'rc-field-form/lib/interface'; import { - attractiveCharacteristicCode, accountabilityCharacteristicCode, + attractiveCharacteristicCoding, } from '../../helpers/utils'; import { GroupCharacteristic } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/groupCharacteristic'; import { Identifier } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/identifier'; @@ -35,21 +35,17 @@ import { ILocation } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/ILocation'; import { getValueSetOptionsValue } from '@opensrp/react-utils'; import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle'; import { + donorCharacteristicCoding, + getCharacteristicWithCoding, + inventoryGroupCoding, + poNumberIdentifierCoding, + quantityCharacteristicCoding, + sectionCharacteristicCoding, + serialNumberIdentifierCoding, servicePointProfileInventoryListCoding, smartregisterSystemUri, } from '@opensrp/fhir-helpers'; -const codeSystem = 'http://smartregister.org/codes'; -const unicefCharacteristicCode = '98734231'; -const donorCharacteristicCode = '45981276'; -const quantityCharacteristicCode = '33467722'; -const supplyInventoryCode = '78991122'; -const poNumberDisplay = 'PO Number'; -const poNumberCode = 'PONUM'; -const serialNumberDisplay = 'Serial Number'; -const serialNumberCode = 'SERNUM'; -const listSupplyInventoryCode = '22138876'; - /** * Check if date in past * @@ -75,7 +71,7 @@ export const handleDisabledFutureDates = (current?: Dayjs) => { }; /** - * check if product is an atrractive item + * check if product is an attractive item * * @param product - product data */ @@ -83,10 +79,11 @@ export const isAttractiveProduct = (product?: IGroup) => { if (!product) { return false; } - const isAttractive = product.characteristic?.some( - (char) => char.code.coding?.[0]?.code === attractiveCharacteristicCode + const attractiveCharacteristic = getCharacteristicWithCoding( + product.characteristic ?? [], + attractiveCharacteristicCoding ); - return isAttractive as boolean; + return !!attractiveCharacteristic?.valueBoolean; }; /** @@ -168,9 +165,9 @@ export const generateCharacteristics = ( listResourceObj?: IGroup ): GroupCharacteristic[] => { const knownCodes = [ - unicefCharacteristicCode, - donorCharacteristicCode, - quantityCharacteristicCode, + sectionCharacteristicCoding.code, + donorCharacteristicCoding.code, + quantityCharacteristicCoding.code, ]; const unknownCharacteristics = listResourceObj?.characteristic?.filter((char) => { @@ -181,13 +178,7 @@ export const generateCharacteristics = ( ...unknownCharacteristics, { code: { - coding: [ - { - system: codeSystem, - code: unicefCharacteristicCode, - display: 'Unicef Section', - }, - ], + coding: [sectionCharacteristicCoding], }, valueCodeableConcept: { coding: [unicefSection], @@ -198,13 +189,7 @@ export const generateCharacteristics = ( if (donor) { characteristics.push({ code: { - coding: [ - { - system: codeSystem, - code: donorCharacteristicCode, - display: 'Donor', - }, - ], + coding: [donorCharacteristicCoding], }, valueCodeableConcept: { coding: [donor], @@ -215,13 +200,7 @@ export const generateCharacteristics = ( if (quantity) { characteristics.push({ code: { - coding: [ - { - system: codeSystem, - code: quantityCharacteristicCode, - display: 'Quantity ', - }, - ], + coding: [quantityCharacteristicCoding], }, valueQuantity: { value: quantity }, }); @@ -242,7 +221,7 @@ export const generateIdentifier = ( serialId?: string, listResourceObj?: IGroup ): Identifier[] => { - const knownCodes = [poNumberCode, serialNumberCode]; + const knownCodes = [poNumberIdentifierCoding.code, serialNumberIdentifierCoding.code]; const unknownIdentifiers = listResourceObj?.identifier?.filter((identifier) => { const code = identifier.type?.coding?.[0].code; @@ -252,14 +231,8 @@ export const generateIdentifier = ( { use: IdentifierUseCodes.SECONDARY, type: { - coding: [ - { - system: codeSystem, - code: poNumberCode, - display: poNumberDisplay, - }, - ], - text: poNumberDisplay, + coding: [poNumberIdentifierCoding], + text: poNumberIdentifierCoding.display, }, value: poId, }, @@ -269,14 +242,8 @@ export const generateIdentifier = ( identifiers.push({ use: IdentifierUseCodes.OFFICIAL, type: { - coding: [ - { - system: codeSystem, - code: serialNumberCode, - display: serialNumberDisplay, - }, - ], - text: serialNumberDisplay, + coding: [serialNumberIdentifierCoding], + text: serialNumberIdentifierCoding.display, }, value: serialId, }); @@ -309,13 +276,7 @@ export const getLocationInventoryPayload = ( member: getMember(values.product, values.deliveryDate, values.accountabilityEndDate), characteristic: generateCharacteristics(unicefSection, donor, values.quantity, listResourceObj), code: { - coding: [ - { - system: codeSystem, - code: supplyInventoryCode, - display: 'Supply Inventory', - }, - ], + coding: [inventoryGroupCoding], }, }; if (editMode) { @@ -476,14 +437,8 @@ function createCommonListResource(id: string): IList { ], status: 'current', code: { - coding: [ - { - system: codeSystem, - code: listSupplyInventoryCode, - display: 'Supply Inventory List', - }, - ], - text: 'Supply Inventory List', + coding: [servicePointProfileInventoryListCoding], + text: servicePointProfileInventoryListCoding.display, }, }; } @@ -528,14 +483,8 @@ export function createUpdateLocationInventoryList( const stringDate = now.toISOString(); const newEntry: ListEntry = { flag: { - coding: [ - { - system: codeSystem, - code: listSupplyInventoryCode, - display: 'Supply Inventory List', - }, - ], - text: 'Supply Inventory List', + coding: [servicePointProfileInventoryListCoding], + text: servicePointProfileInventoryListCoding.display, }, // eslint-disable-next-line @typescript-eslint/no-explicit-any date: stringDate as any, @@ -567,28 +516,28 @@ export const getInventoryInitialValues = (inventory: IGroup): GroupFormFields => } as GroupFormFields; inventory.identifier?.forEach((identifier) => { const code = identifier.type?.coding?.[0].code; - if (code === poNumberCode) { + if (code === poNumberIdentifierCoding.code) { initialValues.poNumber = identifier.value as string; } - if (code === serialNumberCode) { + if (code === serialNumberIdentifierCoding.code) { initialValues.serialNumber = identifier.value as string; } }); inventory.characteristic?.forEach((characteristic) => { const code = characteristic.code.coding?.[0].code; - if (code === unicefCharacteristicCode) { + if (code === sectionCharacteristicCoding.code) { const coding = characteristic.valueCodeableConcept?.coding?.[0]; if (coding) { initialValues.unicefSection = getValueSetOptionsValue(coding) as string; } } - if (code === donorCharacteristicCode) { + if (code === donorCharacteristicCoding.code) { const coding = characteristic.valueCodeableConcept?.coding?.[0]; if (coding) { initialValues.donor = getValueSetOptionsValue(coding); } } - if (code === quantityCharacteristicCode) { + if (code === quantityCharacteristicCoding.code) { initialValues.quantity = characteristic.valueQuantity?.value; } }); diff --git a/packages/fhir-group-management/src/components/ProductForm/utils.tsx b/packages/fhir-group-management/src/components/ProductForm/utils.tsx index ef206e43b..e10ddfea2 100644 --- a/packages/fhir-group-management/src/components/ProductForm/utils.tsx +++ b/packages/fhir-group-management/src/components/ProductForm/utils.tsx @@ -17,6 +17,7 @@ import { } from '../../constants'; import { capitalize, values } from 'lodash'; import { UploadChangeParam, UploadFile } from 'antd/es/upload'; +import { R4GroupTypeCodes } from '@opensrp/fhir-helpers'; export enum UnitOfMeasure { Pieces = 'Pieces', @@ -30,12 +31,6 @@ export enum UnitOfMeasure { Straps = 'Straps', } -export enum TypeOfGroup { - Medication = 'medication', - Decive = 'device', - Substance = 'substance', -} - /** * extract file from an input event * @@ -61,7 +56,7 @@ export function defaultValidationRulesFactory(t: TFunction) { [identifier]: [{ type: 'string' }] as Rule[], [name]: [{ type: 'string', message: t('Must be a valid string') }] as Rule[], [active]: [{ type: 'boolean' }] as Rule[], - [type]: [{ type: 'enum', enum: Object.values(TypeOfGroup) }] as Rule[], + [type]: [{ type: 'enum', enum: Object.values(R4GroupTypeCodes) }] as Rule[], [unitOfMeasure]: [{ type: 'enum', enum: Object.values(UnitOfMeasure) }] as Rule[], [materialNumber]: [{ type: 'string' }] as Rule[], [isAttractiveItem]: [{ type: 'boolean' }] as Rule[], @@ -83,7 +78,7 @@ export interface SelectOption { * */ export const getGroupTypeOptions = () => { - return values(TypeOfGroup).map((group) => { + return values(R4GroupTypeCodes).map((group) => { return { value: group, label: capitalize(group), diff --git a/packages/fhir-helpers/src/constants/valueSetEnums.ts b/packages/fhir-helpers/src/constants/valueSetEnums.ts index ba428a51b..f5407c1dc 100644 --- a/packages/fhir-helpers/src/constants/valueSetEnums.ts +++ b/packages/fhir-helpers/src/constants/valueSetEnums.ts @@ -4,3 +4,13 @@ export enum PhysicalTypeCodes { JURISDICTION = 'jdn', BUILDING = 'bu', } + +/** + * https://www.hl7.org/fhir/R4/valueset-group-type.html + */ +export const R4GroupTypeCodesValueSetUrl = 'http://hl7.org/fhir/ValueSet/group-type'; +export enum R4GroupTypeCodes { + MEDICATION = 'medication', + DEVICE = 'device', + SUBSTANCE = 'substance', +}