@openmrs/esm-stock-management-app 1.0.1-pre.783 → 1.0.1-pre.788
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/index.ts +1 -0
- package/__mocks__/operation-type.mock.ts +532 -0
- package/dist/155.js +1 -0
- package/dist/155.js.map +1 -0
- package/dist/172.js +1 -1
- package/dist/20.js +1 -1
- package/dist/290.js +1 -1
- package/dist/493.js +2 -0
- package/dist/493.js.map +1 -0
- package/dist/606.js +1 -1
- package/dist/627.js +1 -1
- package/dist/922.js +1 -0
- package/dist/922.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-stock-management-app.js +1 -1
- package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +75 -51
- package/dist/openmrs-esm-stock-management-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +6 -0
- package/src/core/utils/utils.ts +29 -0
- package/src/index.ts +4 -0
- package/src/routes.json +9 -0
- package/src/stock-items/add-stock-item/transactions/printout/transactions-stockcard-printout.component.tsx +8 -12
- package/src/stock-items/add-stock-item/transactions/transactions.component.tsx +8 -12
- package/src/stock-items/stock-items.resource.ts +5 -5
- package/src/stock-lookups/stock-lookups.resource.ts +2 -2
- package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.scss +34 -0
- package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.tsx +111 -0
- package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.component.tsx +87 -0
- package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.scss +31 -0
- package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operations-status.tsx +45 -0
- package/src/stock-operations/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +41 -16
- package/src/stock-operations/stock-operation-reference.component.tsx +64 -0
- package/src/stock-operations/stock-operation-status/stock-operation-status-row.tsx +77 -0
- package/src/stock-operations/stock-operation-status/stock-operation-status.scss +32 -0
- package/src/stock-operations/stock-operation-status/stock-operation-status.tsx +45 -0
- package/src/stock-operations/stock-operation-types-selector/stock-operation-types-selector.component.tsx +30 -29
- package/src/stock-operations/stock-operation.utils.tsx +16 -79
- package/src/stock-operations/stock-operations-dialog/stock-operations-issue-stock-button.component.tsx +27 -39
- package/src/stock-operations/stock-operations-dialog/stock-operations-print-button.component.tsx +51 -59
- package/src/stock-operations/{stock-item-selector/stock-item-selector.resource.tsx → stock-operations-forms/hooks/useFilterableStockItems.ts} +4 -4
- package/src/stock-operations/stock-operations-forms/hooks/useFilteredOperationTypesByRoles.ts +30 -0
- package/src/stock-operations/stock-operations-forms/hooks/useOperationTypePermisions.ts +29 -0
- package/src/stock-operations/stock-operations-forms/hooks/useParties.ts +73 -0
- package/src/stock-operations/{users-selector/users-selector.resource.tsx → stock-operations-forms/hooks/useSearchUser.ts} +9 -7
- package/src/stock-operations/{batch-no-selector/batch-no-selector.resource.tsx → stock-operations-forms/hooks/useStockItemBatchNumbers.ts} +3 -3
- package/src/stock-operations/stock-operations-forms/hooks/useStockOperationLinks.ts +20 -0
- package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +72 -0
- package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +90 -0
- package/src/stock-operations/{add-stock-operation/stock-item-search/stock-item-search.scss → stock-operations-forms/input-components/input-components-styles.scss} +2 -2
- package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +157 -0
- package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +53 -0
- package/src/stock-operations/stock-operations-forms/input-components/stock-item-search.component.tsx +79 -0
- package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.component.tsx +59 -0
- package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +216 -0
- package/src/stock-operations/{batch-no-selector → stock-operations-forms/input-components}/unique-batch-no-entry-input.component.tsx +12 -7
- package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +110 -0
- package/src/stock-operations/stock-operations-forms/input-components/users-selector.component.tsx +111 -0
- package/src/stock-operations/stock-operations-forms/step1.test.tsx +303 -0
- package/src/stock-operations/stock-operations-forms/step2.test.tsx +254 -0
- package/src/stock-operations/stock-operations-forms/step3.test.tsx +223 -0
- package/src/stock-operations/stock-operations-forms/steps/base-operation-details-form-step.tsx +241 -0
- package/src/stock-operations/stock-operations-forms/steps/quantity-uom-cell.component.tsx +33 -0
- package/src/stock-operations/stock-operations-forms/steps/received-items.component.tsx +110 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-availability-cell.component.tsx +51 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-batch-no-cell.component.tsx +40 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-cell.component.tsx +50 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-expiry-cell.component.tsx +41 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +281 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.scc.scss +64 -0
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +243 -0
- package/src/stock-operations/stock-operations-forms/stock-issue-form-initializer-with-related-requisition-operation.component.tsx +55 -0
- package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +41 -0
- package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +211 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-form-header.component.tsx +166 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-form.component.tsx +205 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +111 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-related-link.component.tsx +45 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +41 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +52 -0
- package/src/stock-operations/stock-operations-forms/stock-operations-form-utils.ts +32 -0
- package/src/stock-operations/stock-operations-table.component.tsx +57 -92
- package/src/stock-operations/stock-operations.resource.ts +16 -13
- package/src/stock-operations/validation-schema.ts +72 -14
- package/dist/766.js +0 -2
- package/dist/766.js.map +0 -1
- package/dist/822.js +0 -1
- package/dist/822.js.map +0 -1
- package/src/stock-operations/add-stock-operation/add-stock-operation.component.tsx +0 -349
- package/src/stock-operations/add-stock-operation/add-stock-operation.resource.tsx +0 -27
- package/src/stock-operations/add-stock-operation/add-stock-operation.scss +0 -60
- package/src/stock-operations/add-stock-operation/add-stock-operation.test.tsx +0 -192
- package/src/stock-operations/add-stock-operation/add-stock-operation.utils.tsx +0 -152
- package/src/stock-operations/add-stock-operation/add-stock-utils.ts +0 -103
- package/src/stock-operations/add-stock-operation/base-operation-details.component.tsx +0 -439
- package/src/stock-operations/add-stock-operation/base-operation-details.scss +0 -30
- package/src/stock-operations/add-stock-operation/received-items.component.tsx +0 -93
- package/src/stock-operations/add-stock-operation/stock-item-search/stock-item-search.component.tsx +0 -70
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.component.tsx +0 -357
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.resource.tsx +0 -0
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.scss +0 -12
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.test.tsx +0 -10
- package/src/stock-operations/add-stock-operation/stock-items-addition.component.scss +0 -17
- package/src/stock-operations/add-stock-operation/stock-items-addition.component.tsx +0 -254
- package/src/stock-operations/add-stock-operation/stock-operation-context/useStockOperationContext.tsx +0 -16
- package/src/stock-operations/add-stock-operation/stock-operation-reference.component.tsx +0 -39
- package/src/stock-operations/add-stock-operation/stock-operation-related-link.component.tsx +0 -38
- package/src/stock-operations/add-stock-operation/stock-operation-status.component.tsx +0 -170
- package/src/stock-operations/add-stock-operation/stock-operation-submission.component.tsx +0 -189
- package/src/stock-operations/add-stock-operation/stock-operation-submission.test.tsx +0 -138
- package/src/stock-operations/add-stock-operation/types.ts +0 -55
- package/src/stock-operations/add-stock-operation/validationSchema.ts +0 -54
- package/src/stock-operations/batch-no-selector/batch-no-selector.component.tsx +0 -114
- package/src/stock-operations/batch-no-selector/batch-no-selector.scss +0 -0
- package/src/stock-operations/batch-no-selector/batch-no-selector.test.tsx +0 -101
- package/src/stock-operations/party-selector/party-selector.component.tsx +0 -59
- package/src/stock-operations/qty-uom-selector/qty-uom-selector.component.tsx +0 -65
- package/src/stock-operations/qty-uom-selector/qty-uom-selector.resource.tsx +0 -0
- package/src/stock-operations/qty-uom-selector/qty-uom-selector.scss +0 -0
- package/src/stock-operations/qty-uom-selector/qty-uom-selector.test.tsx +0 -10
- package/src/stock-operations/stock-item-selector/stock-item-selector.component.tsx +0 -69
- package/src/stock-operations/stock-item-selector/stock-item-selector.scss +0 -0
- package/src/stock-operations/stock-item-selector/stock-item-selector.test.tsx +0 -10
- package/src/stock-operations/stock-operation-reason-selector/stock-operation-reason-selector.component.tsx +0 -62
- package/src/stock-operations/users-selector/users-selector.component.tsx +0 -75
- /package/dist/{766.js.LICENSE.txt → 493.js.LICENSE.txt} +0 -0
package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
import { ComboBox, SelectSkeleton } from '@carbon/react';
|
2
|
+
import React, { useEffect, useMemo } from 'react';
|
3
|
+
import { useTranslation } from 'react-i18next';
|
4
|
+
import { StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
|
5
|
+
import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
|
+
import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
|
7
|
+
import { formatForDatePicker } from '../../../constants';
|
8
|
+
|
9
|
+
interface BatchNoSelectorProps {
|
10
|
+
stockItemUuid: string;
|
11
|
+
initialValue?: string;
|
12
|
+
onValueChange?: (value: string) => void;
|
13
|
+
error?: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
const BatchNoSelector: React.FC<BatchNoSelectorProps> = ({ stockItemUuid, error, initialValue, onValueChange }) => {
|
17
|
+
const { isLoading, stockItemBatchNos } = useStockItemBatchNumbers(stockItemUuid);
|
18
|
+
const { t } = useTranslation();
|
19
|
+
|
20
|
+
const { items, setStockItemUuid, isLoading: isLoadingBatchinfo } = useStockItemBatchInformationHook();
|
21
|
+
|
22
|
+
useEffect(() => {
|
23
|
+
setStockItemUuid(stockItemUuid);
|
24
|
+
}, [stockItemUuid, setStockItemUuid]);
|
25
|
+
|
26
|
+
const stockItemBatchesInfo = useMemo(() => {
|
27
|
+
return stockItemBatchNos?.map((item) => {
|
28
|
+
const matchingBatch = items?.find((batch) => batch.batchNumber === item.batchNo);
|
29
|
+
if (matchingBatch) {
|
30
|
+
return {
|
31
|
+
...item,
|
32
|
+
quantity: matchingBatch.quantity ?? '',
|
33
|
+
};
|
34
|
+
}
|
35
|
+
return item;
|
36
|
+
});
|
37
|
+
}, [stockItemBatchNos, items]);
|
38
|
+
|
39
|
+
const filteredBatches = useMemo(() => {
|
40
|
+
return stockItemBatchesInfo?.filter((s) => s.quantity !== undefined && s.quantity !== 0);
|
41
|
+
}, [stockItemBatchesInfo]);
|
42
|
+
const initialSelectedItem = useMemo(
|
43
|
+
() => filteredBatches?.find((s) => s.uuid === initialValue),
|
44
|
+
[filteredBatches, initialValue],
|
45
|
+
);
|
46
|
+
|
47
|
+
if (isLoading || isLoadingBatchinfo) return <SelectSkeleton role="progressbar" />;
|
48
|
+
|
49
|
+
return (
|
50
|
+
<ComboBox
|
51
|
+
style={{ flexGrow: '1' }}
|
52
|
+
titleText={t('batchNo', 'Batch')}
|
53
|
+
name={'stockBatchUuid'}
|
54
|
+
id={'stockBatchUuid'}
|
55
|
+
items={filteredBatches || []}
|
56
|
+
onChange={(data: { selectedItem?: StockBatchDTO }) => {
|
57
|
+
onValueChange(data.selectedItem?.uuid);
|
58
|
+
}}
|
59
|
+
selectedItem={initialSelectedItem}
|
60
|
+
itemToString={(s: StockBatchDTO) =>
|
61
|
+
s?.batchNo
|
62
|
+
? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
|
63
|
+
: ''
|
64
|
+
}
|
65
|
+
placeholder={t('filter', "'Filter") + '...'}
|
66
|
+
invalid={error}
|
67
|
+
invalidText={error}
|
68
|
+
/>
|
69
|
+
);
|
70
|
+
};
|
71
|
+
|
72
|
+
export default BatchNoSelector;
|
package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
2
|
+
import userEvent from '@testing-library/user-event';
|
3
|
+
import React from 'react';
|
4
|
+
import { StockItemInventory } from '../../../core/api/types/stockItem/StockItemInventory';
|
5
|
+
import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
|
+
import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
|
7
|
+
import BatchNoSelector from './batch-no-selector.component';
|
8
|
+
import { formatForDatePicker } from '../../../constants';
|
9
|
+
|
10
|
+
jest.mock('../hooks/useStockItemBatchNumbers');
|
11
|
+
jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource');
|
12
|
+
jest.mock('react-i18next', () => ({
|
13
|
+
useTranslation: () => ({ t: (key: string) => key }),
|
14
|
+
}));
|
15
|
+
|
16
|
+
const mockUseStockItemBatchNumbers = useStockItemBatchNumbers as jest.Mock;
|
17
|
+
const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
|
18
|
+
|
19
|
+
describe('BatchNoSelector', () => {
|
20
|
+
const mockOnValueChange = jest.fn();
|
21
|
+
const mockStockItemUuid = 'test-uuid';
|
22
|
+
const mockExpiration = new Date();
|
23
|
+
beforeEach(() => {
|
24
|
+
jest.clearAllMocks();
|
25
|
+
|
26
|
+
mockUseStockItemBatchNumbers.mockReturnValue({
|
27
|
+
isLoading: false,
|
28
|
+
stockItemBatchNos: [
|
29
|
+
{ uuid: '1', batchNo: 'BATCH-001', quantity: 10, expiration: mockExpiration },
|
30
|
+
{ uuid: '2', batchNo: 'BATCH-002', quantity: 20, expiration: mockExpiration },
|
31
|
+
],
|
32
|
+
});
|
33
|
+
mockUseStockItemBatchInformationHook.mockReturnValue({
|
34
|
+
items: [
|
35
|
+
{ batchNumber: 'BATCH-001', quantity: 10 },
|
36
|
+
{ batchNumber: 'BATCH-002', quantity: 20 },
|
37
|
+
] as StockItemInventory[],
|
38
|
+
setStockItemUuid: jest.fn(),
|
39
|
+
});
|
40
|
+
});
|
41
|
+
|
42
|
+
it('should render loading skeleton when isLoading is true', () => {
|
43
|
+
mockUseStockItemBatchNumbers.mockReturnValue({ isLoading: true, stockItemBatchNos: [] });
|
44
|
+
mockUseStockItemBatchInformationHook.mockReturnValue({ isLoading: true, items: [], setStockItemUuid: jest.fn() });
|
45
|
+
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
46
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
47
|
+
});
|
48
|
+
|
49
|
+
it('should render combobox with batch numbers', async () => {
|
50
|
+
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
51
|
+
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
52
|
+
expect(screen.getByText('batchNo')).toBeInTheDocument();
|
53
|
+
});
|
54
|
+
|
55
|
+
it('should handle batch selection', async () => {
|
56
|
+
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
57
|
+
const combobox = screen.getByRole('combobox');
|
58
|
+
await userEvent.click(combobox);
|
59
|
+
await userEvent.type(combobox, 'BATCH-001');
|
60
|
+
const option = screen.getByText(`BATCH-001 | Qty: 10 | Expiry: ${formatForDatePicker(mockExpiration)}`);
|
61
|
+
await userEvent.click(option);
|
62
|
+
expect(mockOnValueChange).toHaveBeenCalledWith('1');
|
63
|
+
});
|
64
|
+
|
65
|
+
it('should display error message when error prop is provided', () => {
|
66
|
+
const errorMessage = 'This is an error';
|
67
|
+
render(
|
68
|
+
<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />,
|
69
|
+
);
|
70
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
71
|
+
});
|
72
|
+
|
73
|
+
it('should filter out batches with zero or undefined quantity', async () => {
|
74
|
+
mockUseStockItemBatchNumbers.mockReturnValue({
|
75
|
+
isLoading: false,
|
76
|
+
stockItemBatchNos: [
|
77
|
+
{ uuid: '1', batchNo: 'BATCH-001', quantity: 10 },
|
78
|
+
{ uuid: '2', batchNo: 'BATCH-002', quantity: 0 },
|
79
|
+
{ uuid: '3', batchNo: 'BATCH-003', quantity: undefined },
|
80
|
+
],
|
81
|
+
});
|
82
|
+
|
83
|
+
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
84
|
+
const combobox = screen.getByRole('combobox');
|
85
|
+
await userEvent.click(combobox);
|
86
|
+
|
87
|
+
expect(screen.queryByText('BATCH-002')).not.toBeInTheDocument();
|
88
|
+
expect(screen.queryByText('BATCH-003')).not.toBeInTheDocument();
|
89
|
+
});
|
90
|
+
});
|
@@ -1,6 +1,6 @@
|
|
1
1
|
@use '@carbon/colors';
|
2
2
|
@use '@carbon/type';
|
3
|
-
@use '@carbon/
|
3
|
+
@use '@carbon/layout';
|
4
4
|
@use '@openmrs/esm-styleguide/src/vars' as *;
|
5
5
|
|
6
6
|
// Patient List Table
|
@@ -25,7 +25,7 @@
|
|
25
25
|
.stockItemSearchContainer {
|
26
26
|
position: relative;
|
27
27
|
flex-direction: column;
|
28
|
-
margin-bottom:
|
28
|
+
margin-bottom: layout.$spacing-01;
|
29
29
|
}
|
30
30
|
|
31
31
|
.searchResults {
|
package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
2
|
+
import userEvent from '@testing-library/user-event';
|
3
|
+
import React from 'react';
|
4
|
+
import { useStockItem } from '../../../stock-items/stock-items.resource';
|
5
|
+
import QtyUomSelector from './quantity-uom-selector.component';
|
6
|
+
|
7
|
+
jest.mock('../../../stock-items/stock-items.resource');
|
8
|
+
jest.mock('react-i18next', () => ({
|
9
|
+
useTranslation: () => ({ t: (key: string) => key }),
|
10
|
+
}));
|
11
|
+
|
12
|
+
const mockUseStockItem = useStockItem as jest.Mock;
|
13
|
+
|
14
|
+
describe('QtyUOMSelector', () => {
|
15
|
+
const mockOnValueChange = jest.fn();
|
16
|
+
const mockStockItemUuid = 'test-uuid';
|
17
|
+
const mockitem = {
|
18
|
+
uuid: '33225466-93c8-4720-b110-4f445f3764c6',
|
19
|
+
drugUuid: '1776AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
|
20
|
+
drugName: 'Ibuprofen Injection solution 5mg/mL ',
|
21
|
+
conceptUuid: '77897AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
22
|
+
conceptName: 'IBUPROFEN',
|
23
|
+
hasExpiration: true,
|
24
|
+
preferredVendorUuid: 'b7e481ef-06e7-46ba-8a7c-ab077aa3355f',
|
25
|
+
preferredVendorName: 'Kenya Medical Supplies Authority',
|
26
|
+
purchasePrice: 100.0,
|
27
|
+
purchasePriceUoMUuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
|
28
|
+
purchasePriceUoMName: 'Box',
|
29
|
+
purchasePriceUoMFactor: 10.0,
|
30
|
+
dispensingUnitName: 'Tablet',
|
31
|
+
dispensingUnitUuid: '1513AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
32
|
+
dispensingUnitPackagingUoMUuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
|
33
|
+
dispensingUnitPackagingUoMName: 'Box',
|
34
|
+
dispensingUnitPackagingUoMFactor: 10.0,
|
35
|
+
defaultStockOperationsUoMUuid: null,
|
36
|
+
defaultStockOperationsUoMName: null,
|
37
|
+
defaultStockOperationsUoMFactor: null,
|
38
|
+
categoryUuid: '183f9497-b72e-4ebe-ae17-5dcf110ff3b6',
|
39
|
+
categoryName: 'Other Lymph Node Comments',
|
40
|
+
commonName: 'Ibuprofen 5 MG/ML Injectable Solution',
|
41
|
+
acronym: null,
|
42
|
+
reorderLevel: null,
|
43
|
+
reorderLevelUoMUuid: null,
|
44
|
+
reorderLevelUoMName: null,
|
45
|
+
reorderLevelUoMFactor: null,
|
46
|
+
dateCreated: '2024-09-03T09:46:47.000+0300',
|
47
|
+
creatorGivenName: 'Mark',
|
48
|
+
creatorFamilyName: 'Miller',
|
49
|
+
voided: false,
|
50
|
+
expiryNotice: 180,
|
51
|
+
permission: {
|
52
|
+
canView: true,
|
53
|
+
canEdit: true,
|
54
|
+
},
|
55
|
+
packagingUnits: [
|
56
|
+
{
|
57
|
+
uuid: '8023ef7d-56da-442f-bc74-319f000840b4',
|
58
|
+
factor: 1.0,
|
59
|
+
packagingUomUuid: '162382AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
60
|
+
packagingUomName: 'Vial',
|
61
|
+
stockItemUuid: '33225466-93c8-4720-b110-4f445f3764c6',
|
62
|
+
isDefaultStockOperationsUoM: false,
|
63
|
+
isDispensingUnit: false,
|
64
|
+
links: [
|
65
|
+
{
|
66
|
+
rel: 'full',
|
67
|
+
uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitempackaginguom/8023ef7d-56da-442f-bc74-319f000840b4?v=full',
|
68
|
+
resourceAlias: 'stockitempackaginguom',
|
69
|
+
},
|
70
|
+
],
|
71
|
+
resourceVersion: '1.8',
|
72
|
+
},
|
73
|
+
{
|
74
|
+
uuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
|
75
|
+
factor: 10.0,
|
76
|
+
packagingUomUuid: '162396AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
77
|
+
packagingUomName: 'Box',
|
78
|
+
stockItemUuid: '33225466-93c8-4720-b110-4f445f3764c6',
|
79
|
+
isDefaultStockOperationsUoM: false,
|
80
|
+
isDispensingUnit: true,
|
81
|
+
links: [
|
82
|
+
{
|
83
|
+
rel: 'full',
|
84
|
+
uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitempackaginguom/65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee?v=full',
|
85
|
+
resourceAlias: 'stockitempackaginguom',
|
86
|
+
},
|
87
|
+
],
|
88
|
+
resourceVersion: '1.8',
|
89
|
+
},
|
90
|
+
],
|
91
|
+
references: [],
|
92
|
+
links: [
|
93
|
+
{
|
94
|
+
rel: 'self',
|
95
|
+
uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitem/33225466-93c8-4720-b110-4f445f3764c6',
|
96
|
+
resourceAlias: 'stockitem',
|
97
|
+
},
|
98
|
+
],
|
99
|
+
resourceVersion: '1.8',
|
100
|
+
};
|
101
|
+
|
102
|
+
beforeEach(() => {
|
103
|
+
jest.clearAllMocks();
|
104
|
+
mockUseStockItem.mockReturnValue({
|
105
|
+
isLoading: false,
|
106
|
+
item: mockitem,
|
107
|
+
});
|
108
|
+
});
|
109
|
+
it('should render Skeleton when loading stock item', () => {
|
110
|
+
mockUseStockItem.mockReturnValue({ isLoading: true });
|
111
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
112
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
113
|
+
});
|
114
|
+
it('should render Inline notification error when error ocuured while fetching item', () => {
|
115
|
+
const errorMessage = 'error loading stock item';
|
116
|
+
mockUseStockItem.mockReturnValue({ isLoading: false, error: new Error(errorMessage) });
|
117
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
118
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
119
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
120
|
+
});
|
121
|
+
it('should render Inline notification error when error ocuured while fetching item', () => {
|
122
|
+
const errorMessage = 'error loading stock item';
|
123
|
+
mockUseStockItem.mockReturnValue({ isLoading: false, error: new Error(errorMessage) });
|
124
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
125
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
126
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
127
|
+
});
|
128
|
+
|
129
|
+
it('should display error message when error prop is provided', () => {
|
130
|
+
const errorMessage = 'This is an error';
|
131
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />);
|
132
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
133
|
+
});
|
134
|
+
it('should display error message when error prop is provided', () => {
|
135
|
+
const errorMessage = 'This is an error';
|
136
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />);
|
137
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
138
|
+
});
|
139
|
+
|
140
|
+
it('should render packing uom options', async () => {
|
141
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
142
|
+
const combobox = screen.getByRole('combobox');
|
143
|
+
await userEvent.click(combobox);
|
144
|
+
mockitem.packagingUnits.forEach(({ factor, packagingUomName }) => {
|
145
|
+
expect(screen.getByText(`${packagingUomName} - ${factor}`)).toBeInTheDocument();
|
146
|
+
});
|
147
|
+
});
|
148
|
+
it('should packing uom selection', async () => {
|
149
|
+
const itemToSelect = mockitem.packagingUnits[1];
|
150
|
+
render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
151
|
+
const combobox = screen.getByRole('combobox');
|
152
|
+
await userEvent.click(combobox);
|
153
|
+
const option = screen.getByText(`${itemToSelect.packagingUomName} - ${itemToSelect.factor}`);
|
154
|
+
await userEvent.click(option);
|
155
|
+
expect(mockOnValueChange).toHaveBeenCalledWith(itemToSelect.uuid);
|
156
|
+
});
|
157
|
+
});
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { ComboBox, InlineNotification, SkeletonText } from '@carbon/react';
|
2
|
+
import React, { useMemo } from 'react';
|
3
|
+
import { useTranslation } from 'react-i18next';
|
4
|
+
import { StockItemPackagingUOMDTO } from '../../../core/api/types/stockItem/StockItemPackagingUOM';
|
5
|
+
import { useStockItem } from '../../../stock-items/stock-items.resource';
|
6
|
+
|
7
|
+
interface QtyUomSelectorProps {
|
8
|
+
stockItemUuid: string;
|
9
|
+
intiallvalue?: string;
|
10
|
+
onValueChange?: (value: string) => void;
|
11
|
+
error?: string;
|
12
|
+
}
|
13
|
+
|
14
|
+
const QtyUomSelector: React.FC<QtyUomSelectorProps> = ({ stockItemUuid, error, intiallvalue, onValueChange }) => {
|
15
|
+
const { t } = useTranslation();
|
16
|
+
const { isLoading, error: stockItemError, item } = useStockItem(stockItemUuid);
|
17
|
+
const initialSelectedItem = useMemo<StockItemPackagingUOMDTO | null>(
|
18
|
+
() => (item?.packagingUnits ?? []).find((u) => u.uuid === intiallvalue),
|
19
|
+
[item?.packagingUnits, intiallvalue],
|
20
|
+
);
|
21
|
+
|
22
|
+
if (isLoading) return <SkeletonText role="progressbar" />;
|
23
|
+
|
24
|
+
if (stockItemError)
|
25
|
+
return (
|
26
|
+
<InlineNotification
|
27
|
+
kind="error"
|
28
|
+
title={t('packagingUomError', 'Error loading Stock item')}
|
29
|
+
subtitle={stockItemError?.message}
|
30
|
+
/>
|
31
|
+
);
|
32
|
+
|
33
|
+
return (
|
34
|
+
<ComboBox
|
35
|
+
titleText={t('quantityUom', 'Qty UoM')}
|
36
|
+
name={'stockItemPackagingUOMUuid'}
|
37
|
+
id={'stockItemPackagingUOMUuid'}
|
38
|
+
items={item?.packagingUnits ?? []}
|
39
|
+
onChange={(data: { selectedItem?: StockItemPackagingUOMDTO }) => {
|
40
|
+
onValueChange?.(data.selectedItem?.uuid);
|
41
|
+
}}
|
42
|
+
initialSelectedItem={initialSelectedItem}
|
43
|
+
itemToString={(s: StockItemPackagingUOMDTO) =>
|
44
|
+
s.packagingUomName ? `${s?.packagingUomName} - ${s?.factor} ` : ''
|
45
|
+
}
|
46
|
+
placeholder={t('filter', 'Filter') + '...'}
|
47
|
+
invalid={error}
|
48
|
+
invalidText={error}
|
49
|
+
/>
|
50
|
+
);
|
51
|
+
};
|
52
|
+
|
53
|
+
export default QtyUomSelector;
|
package/src/stock-operations/stock-operations-forms/input-components/stock-item-search.component.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
import { ClickableTile, Search } from '@carbon/react';
|
2
|
+
import { useConfig, useDebounce } from '@openmrs/esm-framework';
|
3
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
import { StockItemDTO } from '../../../core/api/types/stockItem/StockItem';
|
6
|
+
import { useFilterableStockItems } from '../hooks/useFilterableStockItems';
|
7
|
+
import styles from './input-components-styles.scss';
|
8
|
+
import { ConfigObject } from '../../../config-schema';
|
9
|
+
|
10
|
+
type StockItemSearchProps = {
|
11
|
+
onSelectedItem?: (stockItem: StockItemDTO) => void;
|
12
|
+
};
|
13
|
+
|
14
|
+
const StockItemSearch: React.FC<StockItemSearchProps> = ({ onSelectedItem }) => {
|
15
|
+
const { t } = useTranslation();
|
16
|
+
const { isLoading, stockItemsList, setSearchString } = useFilterableStockItems({});
|
17
|
+
const [searchTerm, setSearchTerm] = useState('');
|
18
|
+
const debouncedSearchTerm = useDebounce(searchTerm);
|
19
|
+
const { useItemCommonNameAsDisplay } = useConfig<ConfigObject>();
|
20
|
+
const getDrugName = useCallback(
|
21
|
+
(item: StockItemDTO) => {
|
22
|
+
if (useItemCommonNameAsDisplay) return;
|
23
|
+
const commonName = item?.commonName ? `(Common name: ${item.commonName})` : undefined;
|
24
|
+
return `${item?.drugName || t('noDrugNameAvailable', 'No drug name available') + (commonName ?? '')}`;
|
25
|
+
},
|
26
|
+
[useItemCommonNameAsDisplay, t],
|
27
|
+
);
|
28
|
+
|
29
|
+
const getCommonName = useCallback(
|
30
|
+
(item: StockItemDTO) => {
|
31
|
+
if (!useItemCommonNameAsDisplay) return;
|
32
|
+
const drugName = item?.drugName ? `(Drug name: ${item.drugName})` : undefined;
|
33
|
+
return `${item?.commonName || t('noCommonNameAvailable', 'No common name available') + (drugName ?? '')}`;
|
34
|
+
},
|
35
|
+
[useItemCommonNameAsDisplay, t],
|
36
|
+
);
|
37
|
+
useEffect(() => {
|
38
|
+
if (debouncedSearchTerm?.length !== 0) {
|
39
|
+
setSearchString(debouncedSearchTerm);
|
40
|
+
}
|
41
|
+
}, [debouncedSearchTerm, setSearchString]);
|
42
|
+
|
43
|
+
const handleOnSearchResultClick = (stockItem: StockItemDTO) => {
|
44
|
+
onSelectedItem?.(stockItem);
|
45
|
+
setSearchTerm('');
|
46
|
+
};
|
47
|
+
|
48
|
+
return (
|
49
|
+
<div className={styles.stockItemSearchContainer}>
|
50
|
+
<div style={{ display: 'flex' }}>
|
51
|
+
<Search
|
52
|
+
size="lg"
|
53
|
+
placeholder={t('findItems', 'Find your items')}
|
54
|
+
labelText={t('search', 'Search')}
|
55
|
+
closeButtonLabelText={t('clearSearch', 'Clear search input')}
|
56
|
+
value={searchTerm}
|
57
|
+
id="search-stock-operation-item"
|
58
|
+
name="search-stock-operation-item"
|
59
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
60
|
+
/>
|
61
|
+
</div>
|
62
|
+
{searchTerm && stockItemsList?.length > 0 && (
|
63
|
+
<div className={styles.searchResults}>
|
64
|
+
{stockItemsList?.slice(0, 5).map((stockItem) => {
|
65
|
+
const commonName = getCommonName(stockItem);
|
66
|
+
const drugName = getDrugName(stockItem);
|
67
|
+
return (
|
68
|
+
<ClickableTile onClick={() => handleOnSearchResultClick(stockItem)} key={stockItem?.uuid}>
|
69
|
+
{useItemCommonNameAsDisplay ? commonName : drugName}
|
70
|
+
</ClickableTile>
|
71
|
+
);
|
72
|
+
})}
|
73
|
+
</div>
|
74
|
+
)}
|
75
|
+
</div>
|
76
|
+
);
|
77
|
+
};
|
78
|
+
|
79
|
+
export default StockItemSearch;
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { ComboBox, InlineNotification, SelectSkeleton } from '@carbon/react';
|
2
|
+
import { useConfig } from '@openmrs/esm-framework';
|
3
|
+
import React from 'react';
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
import { ConfigObject } from '../../../config-schema';
|
7
|
+
import { Concept } from '../../../core/api/types/concept/Concept';
|
8
|
+
import { useConcept } from '../../../stock-lookups/stock-lookups.resource';
|
9
|
+
|
10
|
+
const StockOperationReasonSelector = () => {
|
11
|
+
const { stockAdjustmentReasonUUID } = useConfig<ConfigObject>();
|
12
|
+
const form = useFormContext<{ reasonUuid: string }>();
|
13
|
+
const {
|
14
|
+
isLoading,
|
15
|
+
error,
|
16
|
+
items: { answers: reasons },
|
17
|
+
} = useConcept(stockAdjustmentReasonUUID);
|
18
|
+
const { t } = useTranslation();
|
19
|
+
if (isLoading) return <SelectSkeleton role="progressbar" />;
|
20
|
+
|
21
|
+
if (error)
|
22
|
+
return (
|
23
|
+
<InlineNotification
|
24
|
+
lowContrast
|
25
|
+
kind="error"
|
26
|
+
title={t('reasonError', 'Error loading reasons concepts')}
|
27
|
+
subtitle={error?.message}
|
28
|
+
/>
|
29
|
+
);
|
30
|
+
|
31
|
+
return (
|
32
|
+
<Controller
|
33
|
+
control={form.control}
|
34
|
+
name={'reasonUuid'}
|
35
|
+
render={({ field, fieldState: { error } }) => (
|
36
|
+
<ComboBox
|
37
|
+
readOnly={field.disabled}
|
38
|
+
titleText={t('reason', 'Reason')}
|
39
|
+
placeholder={t('chooseAReason', 'Choose a reason')}
|
40
|
+
name={'reasonUuid'}
|
41
|
+
id={'reasonUuid'}
|
42
|
+
size={'xl'}
|
43
|
+
items={reasons}
|
44
|
+
initialSelectedItem={reasons?.find((p) => p.uuid === field.value)}
|
45
|
+
selectedItem={reasons.find((p) => p.uuid === field.value)}
|
46
|
+
itemToString={(item?: Concept) => (item && item?.display ? `${item?.display}` : '')}
|
47
|
+
onChange={(data: { selectedItem?: Concept }) => {
|
48
|
+
field.onChange(data?.selectedItem?.uuid);
|
49
|
+
}}
|
50
|
+
ref={field.ref}
|
51
|
+
invalid={error?.message}
|
52
|
+
invalidText={error?.message}
|
53
|
+
/>
|
54
|
+
)}
|
55
|
+
/>
|
56
|
+
);
|
57
|
+
};
|
58
|
+
|
59
|
+
export default StockOperationReasonSelector;
|