@openmrs/esm-stock-management-app 3.0.1-pre.855 → 3.0.1-pre.865
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/.husky/pre-commit +4 -1
- package/.husky/pre-push +2 -0
- package/__mocks__/react-i18next.js +8 -9
- package/dist/10.js +1 -1
- package/dist/10.js.map +1 -1
- package/dist/119.js +1 -1
- package/dist/119.js.map +1 -1
- package/dist/14.js +1 -1
- package/dist/14.js.map +1 -1
- package/dist/172.js +1 -1
- package/dist/172.js.map +1 -1
- package/dist/20.js +1 -1
- package/dist/20.js.map +1 -1
- package/dist/290.js +1 -1
- package/dist/290.js.map +1 -1
- package/dist/33.js +1 -0
- package/dist/33.js.map +1 -0
- package/dist/467.js +1 -1
- package/dist/467.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/606.js +1 -1
- package/dist/606.js.map +1 -1
- package/dist/642.js +1 -1
- package/dist/642.js.map +1 -1
- package/dist/675.js +1 -1
- package/dist/675.js.map +1 -1
- package/dist/727.js +1 -1
- package/dist/727.js.map +1 -1
- package/dist/842.js +1 -1
- package/dist/842.js.map +1 -1
- package/dist/93.js +1 -1
- package/dist/93.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +70 -70
- package/dist/routes.json +1 -1
- package/jest.config.js +6 -3
- package/package.json +1 -1
- package/src/core/components/table/table.component.tsx +2 -2
- package/src/index.ts +5 -5
- package/src/stock-items/add-bulk-stock-item/add-stock-items-bulk-import-action-button.component.tsx +3 -3
- package/src/stock-items/add-bulk-stock-item/{stock-items-bulk-import.component.tsx → stock-items-bulk-import.modal.tsx} +20 -19
- package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.resource.ts +1 -1
- package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.test.tsx +59 -59
- package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +6 -6
- package/src/stock-items/add-stock-item/add-stock-item.component.tsx +6 -4
- package/src/stock-items/add-stock-item/add-stock-item.scss +5 -0
- package/src/stock-items/add-stock-item/add-stock-item.test.tsx +28 -43
- package/src/stock-items/add-stock-item/packaging-units/packaging-units-delete-modal.component.tsx +3 -4
- package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +9 -10
- package/src/stock-items/add-stock-item/packaging-units/packaging-units.scss +4 -4
- package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +27 -19
- package/src/stock-items/add-stock-item/stock-item-references/stock-item-references.scss +4 -4
- package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +15 -9
- package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +1 -0
- package/src/stock-items/add-stock-item/stock-item-rules/delete-stock-rule-modal.component.tsx +2 -1
- package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.component.tsx +14 -16
- package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.scss +7 -3
- package/src/stock-items/add-stock-item/transactions/printout/transactions-print-bincard-preview.modal.tsx +14 -6
- package/src/stock-items/add-stock-item/transactions/printout/transactions-print-stockcard-preview.modal.tsx +14 -8
- package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
- package/src/stock-items/stock-item.utils.tsx +3 -5
- package/src/stock-items/stock-items-table.component.tsx +47 -45
- package/src/stock-items/stock-items-table.resource.ts +2 -2
- package/src/stock-items/stock-items-table.scss +5 -1
- package/src/stock-items/stock-items-table.test.tsx +106 -65
- package/src/stock-locations/location-admin-form.component.tsx +5 -4
- package/src/stock-locations/stock-locations-table.component.tsx +10 -8
- package/src/stock-lookups/stock-lookups.resource.ts +18 -17
- package/src/stock-operations/stock-operations-dialog/stock-operations-dialog.component.tsx +2 -2
- package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +11 -11
- package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +115 -25
- package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +107 -65
- package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +9 -9
- package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +35 -153
- package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +82 -29
- package/src/stock-operations/stock-operations-forms/step1.test.tsx +204 -69
- package/src/stock-operations/stock-operations-forms/step2.test.tsx +140 -63
- package/src/stock-operations/stock-operations-forms/step3.test.tsx +79 -60
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +6 -5
- package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +12 -11
- package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +1 -0
- package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +20 -12
- package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +1 -0
- package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +1 -3
- package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +2 -1
- package/src/stock-reports/generate-report/create-stock-report.scss +3 -2
- package/src/stock-reports/generate-report/create-stock-report.workspace.tsx +32 -25
- package/src/stock-reports/report-list/stock-report-parameters.component.tsx +1 -1
- package/src/stock-reports/report-list/stock-report-status.component.tsx +1 -1
- package/src/stock-reports/report-list/stock-reports.component.tsx +24 -25
- package/src/stock-reports/report-list/stock-reports.scss +10 -2
- package/src/stock-sources/add-stock-sources/add-stock-sources.scss +11 -4
- package/src/stock-sources/add-stock-sources/add-stock-sources.test.tsx +38 -36
- package/src/stock-sources/add-stock-sources/add-stock-sources.workspace.tsx +35 -30
- package/src/stock-sources/delete-stock-modal.component.tsx +2 -1
- package/src/stock-sources/stock-sources-delete/stock-sources-delete.test.tsx +27 -36
- package/src/stock-sources/stock-sources-filter/stock-sources-filter.component.tsx +36 -21
- package/src/stock-sources/stock-sources-items-table.component.tsx +16 -17
- package/src/stock-sources/stock-sources-items-table.resource.ts +8 -6
- package/src/stock-sources/stock-sources-items-table.test.tsx +79 -36
- package/src/stock-sources/stock-sources.scss +6 -2
- package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.scss +5 -13
- package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.workspace.tsx +2 -2
- package/src/stock-user-role-scopes/delete-stock-user-scope-modal.component.tsx +2 -1
- package/translations/en.json +8 -6
- package/tsconfig.json +4 -0
- package/dist/627.js +0 -1
- package/dist/627.js.map +0 -1
@@ -1,10 +1,13 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { render, screen, waitFor, within } from '@testing-library/react';
|
3
2
|
import userEvent from '@testing-library/user-event';
|
4
|
-
import
|
5
|
-
import {
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
4
|
+
import { type StockItemDTO } from '../core/api/types/stockItem/StockItem';
|
6
5
|
import { handleMutate } from '../utils';
|
7
|
-
import {
|
6
|
+
import { launchAddOrEditStockItemWorkspace } from './stock-item.utils';
|
7
|
+
import { useStockItemsPages } from './stock-items-table.resource';
|
8
|
+
import StockItemsTableComponent from './stock-items-table.component';
|
9
|
+
|
10
|
+
const mockUseStockItemsPages = jest.mocked(useStockItemsPages);
|
8
11
|
|
9
12
|
jest.mock('./stock-items-table.resource', () => ({
|
10
13
|
useStockItemsPages: jest.fn(),
|
@@ -15,109 +18,146 @@ jest.mock('../utils', () => ({
|
|
15
18
|
}));
|
16
19
|
|
17
20
|
jest.mock('./stock-item.utils', () => ({
|
18
|
-
|
19
|
-
}));
|
20
|
-
|
21
|
-
jest.mock('react-i18next', () => ({
|
22
|
-
useTranslation: () => ({
|
23
|
-
t: (key: string) => key,
|
24
|
-
}),
|
21
|
+
launchAddOrEditStockItemWorkspace: jest.fn(),
|
25
22
|
}));
|
26
23
|
|
27
24
|
describe('StockItemsTableComponent', () => {
|
28
|
-
const mockUseStockItemsPages = {
|
29
|
-
isLoading: false,
|
30
|
-
items: Array(25)
|
31
|
-
.fill(null)
|
32
|
-
.map((_, index) => ({
|
33
|
-
uuid: `item-${index}`,
|
34
|
-
commonName: `Test Item ${index}`,
|
35
|
-
drugUuid: index % 2 === 0 ? `drug-${index}` : null,
|
36
|
-
conceptName: `Concept ${index}`,
|
37
|
-
dispensingUnitName: `Unit ${index}`,
|
38
|
-
defaultStockOperationsUoMName: `UoM ${index}`,
|
39
|
-
reorderLevel: index * 10,
|
40
|
-
reorderLevelUoMName: 'Units',
|
41
|
-
})),
|
42
|
-
totalCount: 25,
|
43
|
-
currentPageSize: 10,
|
44
|
-
setPageSize: jest.fn(),
|
45
|
-
pageSizes: [10, 20, 30],
|
46
|
-
currentPage: 1,
|
47
|
-
setCurrentPage: jest.fn(),
|
48
|
-
isDrug: '',
|
49
|
-
setDrug: jest.fn(),
|
50
|
-
setSearchString: jest.fn(),
|
51
|
-
};
|
52
|
-
|
53
25
|
beforeEach(() => {
|
54
|
-
|
55
|
-
|
26
|
+
mockUseStockItemsPages.mockReturnValue({
|
27
|
+
isLoading: false,
|
28
|
+
items: Array(25)
|
29
|
+
.fill(null)
|
30
|
+
.map((_, index) => ({
|
31
|
+
uuid: `item-${index}`,
|
32
|
+
commonName: `Test Item ${index}`,
|
33
|
+
drugUuid: index % 2 === 0 ? `drug-${index}` : null,
|
34
|
+
conceptName: `Concept ${index}`,
|
35
|
+
dispensingUnitName: `Unit ${index}`,
|
36
|
+
defaultStockOperationsUoMName: `UoM ${index}`,
|
37
|
+
reorderLevel: index * 10,
|
38
|
+
reorderLevelUoMName: 'Units',
|
39
|
+
})) as StockItemDTO[],
|
40
|
+
pagination: {
|
41
|
+
results: [],
|
42
|
+
totalPages: 3,
|
43
|
+
currentPage: 1,
|
44
|
+
paginated: true,
|
45
|
+
showNextButton: true,
|
46
|
+
showPreviousButton: false,
|
47
|
+
goTo: jest.fn(),
|
48
|
+
goToNext: jest.fn(),
|
49
|
+
goToPrevious: jest.fn(),
|
50
|
+
},
|
51
|
+
error: null,
|
52
|
+
totalCount: 25,
|
53
|
+
currentPageSize: 10,
|
54
|
+
setPageSize: jest.fn(),
|
55
|
+
pageSizes: [10, 20, 30],
|
56
|
+
currentPage: 1,
|
57
|
+
setCurrentPage: jest.fn(),
|
58
|
+
isDrug: '',
|
59
|
+
setDrug: jest.fn(),
|
60
|
+
setSearchString: jest.fn(),
|
61
|
+
});
|
56
62
|
});
|
57
63
|
|
58
64
|
it('renders initial state and UI elements correctly', async () => {
|
59
|
-
render(<StockItemsTableComponent />);
|
60
|
-
expect(screen.getByText('panelDescription')).toBeInTheDocument();
|
61
|
-
expect(screen.getByRole('searchbox')).toBeInTheDocument();
|
62
|
-
|
63
65
|
const user = userEvent.setup();
|
64
|
-
const menuButton = screen.getByTestId('stock-items-menu');
|
65
|
-
await user.click(menuButton);
|
66
66
|
|
67
|
-
|
67
|
+
render(<StockItemsTableComponent />);
|
68
68
|
|
69
|
-
expect(screen.
|
70
|
-
expect(screen.
|
71
|
-
expect(screen.
|
69
|
+
expect(screen.getByRole('radio', { name: /all/i })).toBeInTheDocument();
|
70
|
+
expect(screen.getByRole('radio', { name: /^pharmaceuticals$/i })).toBeInTheDocument();
|
71
|
+
expect(screen.getByRole('radio', { name: /non pharmaceuticals/i })).toBeInTheDocument();
|
72
|
+
expect(screen.getByRole('button', { name: /import/i })).toBeInTheDocument();
|
73
|
+
expect(screen.getByRole('button', { name: /settings/i })).toBeInTheDocument();
|
74
|
+
expect(screen.getByRole('searchbox', { name: /filter table/i })).toBeInTheDocument();
|
75
|
+
|
76
|
+
const settingsMenuButton = screen.getByRole('button', { name: /settings/i });
|
77
|
+
await user.click(settingsMenuButton);
|
78
|
+
await screen.findByText(/refresh/i);
|
79
|
+
|
80
|
+
expect(screen.getByRole('button', { name: /generic name/i })).toBeInTheDocument();
|
81
|
+
expect(screen.getByRole('button', { name: /common name/i })).toBeInTheDocument();
|
82
|
+
expect(screen.getByRole('button', { name: /dispensing uom/i })).toBeInTheDocument();
|
83
|
+
expect(screen.getByRole('button', { name: /bulk packaging/i })).toBeInTheDocument();
|
84
|
+
expect(screen.getByRole('button', { name: /reorder level/i })).toBeInTheDocument();
|
72
85
|
});
|
73
86
|
|
74
87
|
it('displays skeleton loader when isLoading is true', () => {
|
75
|
-
|
88
|
+
mockUseStockItemsPages.mockReturnValue({
|
89
|
+
...mockUseStockItemsPages,
|
90
|
+
isLoading: true,
|
91
|
+
items: [],
|
92
|
+
pagination: undefined,
|
93
|
+
totalCount: 0,
|
94
|
+
currentPageSize: 0,
|
95
|
+
currentPage: 0,
|
96
|
+
setCurrentPage: function (value: React.SetStateAction<number>): void {
|
97
|
+
throw new Error('Function not implemented.');
|
98
|
+
},
|
99
|
+
setPageSize: function (value: React.SetStateAction<number>): void {
|
100
|
+
throw new Error('Function not implemented.');
|
101
|
+
},
|
102
|
+
pageSizes: [],
|
103
|
+
error: undefined,
|
104
|
+
isDrug: '',
|
105
|
+
setDrug: undefined,
|
106
|
+
setSearchString: function (value: any): void {
|
107
|
+
throw new Error('Function not implemented.');
|
108
|
+
},
|
109
|
+
});
|
110
|
+
|
76
111
|
render(<StockItemsTableComponent />);
|
112
|
+
|
77
113
|
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
78
114
|
});
|
79
115
|
|
80
116
|
it('renders table rows correctly based on items prop', () => {
|
81
117
|
render(<StockItemsTableComponent />);
|
82
|
-
|
83
|
-
expect(screen.
|
84
|
-
expect(screen.
|
118
|
+
|
119
|
+
expect(screen.getByRole('cell', { name: /test item 0/i })).toBeInTheDocument();
|
120
|
+
expect(screen.getAllByRole('cell', { name: /drug/i }).length).toBeGreaterThan(0);
|
121
|
+
expect(screen.getAllByRole('cell', { name: /other/i }).length).toBeGreaterThan(0);
|
85
122
|
});
|
86
123
|
|
87
124
|
it('updates search input and triggers search function', async () => {
|
88
|
-
render(<StockItemsTableComponent />);
|
89
125
|
const user = userEvent.setup();
|
126
|
+
|
127
|
+
render(<StockItemsTableComponent />);
|
128
|
+
|
90
129
|
const searchInput = screen.getByRole('searchbox');
|
91
130
|
await user.type(searchInput, 'test search');
|
92
131
|
await waitFor(
|
93
132
|
() => {
|
94
|
-
expect(mockUseStockItemsPages.setSearchString).toHaveBeenCalledWith('test search');
|
133
|
+
expect(mockUseStockItemsPages().setSearchString).toHaveBeenCalledWith('test search');
|
95
134
|
},
|
96
135
|
{ timeout: 2000 },
|
97
136
|
);
|
98
137
|
});
|
99
138
|
|
100
139
|
it('updates pagination when page or page size changes', async () => {
|
101
|
-
render(<StockItemsTableComponent />);
|
102
140
|
const user = userEvent.setup();
|
103
|
-
|
141
|
+
render(<StockItemsTableComponent />);
|
142
|
+
|
143
|
+
const nextPageButton = screen.getByLabelText(/next page/i);
|
104
144
|
await user.click(nextPageButton);
|
105
|
-
expect(mockUseStockItemsPages.setCurrentPage).toHaveBeenCalled();
|
145
|
+
expect(mockUseStockItemsPages().setCurrentPage).toHaveBeenCalled();
|
106
146
|
|
107
|
-
const pageSizeSelect = screen.getByLabelText(
|
147
|
+
const pageSizeSelect = screen.getByLabelText(/items per page/i);
|
108
148
|
await user.selectOptions(pageSizeSelect, '20');
|
109
|
-
expect(mockUseStockItemsPages.setPageSize).toHaveBeenCalledWith(20);
|
149
|
+
expect(mockUseStockItemsPages().setPageSize).toHaveBeenCalledWith(20);
|
110
150
|
});
|
111
151
|
|
112
152
|
it('triggers handleRefresh when refresh button is clicked', async () => {
|
153
|
+
const user = userEvent.setup();
|
113
154
|
render(<StockItemsTableComponent />);
|
114
155
|
|
115
|
-
const user = userEvent.setup();
|
116
156
|
const menuButton = screen.getByTestId('stock-items-menu');
|
117
157
|
expect(menuButton).toBeInTheDocument();
|
118
158
|
await user.click(menuButton);
|
119
159
|
|
120
|
-
const refreshButton = await screen.findByText(
|
160
|
+
const refreshButton = await screen.findByText(/refresh/i);
|
121
161
|
expect(refreshButton).toBeInTheDocument();
|
122
162
|
await user.click(refreshButton);
|
123
163
|
await waitFor(() => {
|
@@ -125,13 +165,14 @@ describe('StockItemsTableComponent', () => {
|
|
125
165
|
});
|
126
166
|
});
|
127
167
|
|
128
|
-
it('triggers
|
129
|
-
render(<StockItemsTableComponent />);
|
168
|
+
it('triggers launchAddOrEditStockItemWorkspace when edit button is clicked', async () => {
|
130
169
|
const user = userEvent.setup();
|
131
|
-
|
170
|
+
render(<StockItemsTableComponent />);
|
171
|
+
|
172
|
+
const editButtons = screen.getAllByLabelText(/edit stock item/i);
|
132
173
|
await user.click(editButtons[0]);
|
133
174
|
|
134
|
-
expect(
|
175
|
+
expect(launchAddOrEditStockItemWorkspace).toHaveBeenCalledWith(
|
135
176
|
expect.any(Function),
|
136
177
|
expect.objectContaining({ uuid: 'item-0' }),
|
137
178
|
);
|
@@ -5,19 +5,20 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
5
5
|
import {
|
6
6
|
Button,
|
7
7
|
ComposedModal,
|
8
|
+
FilterableMultiSelect,
|
8
9
|
FormGroup,
|
10
|
+
InlineNotification,
|
9
11
|
ModalBody,
|
10
12
|
ModalFooter,
|
11
13
|
ModalHeader,
|
12
14
|
Stack,
|
13
15
|
TextInput,
|
14
|
-
InlineNotification,
|
15
|
-
FilterableMultiSelect,
|
16
16
|
} from '@carbon/react';
|
17
|
+
import { getCoreTranslation } from '@openmrs/esm-framework';
|
17
18
|
import { useTranslation } from 'react-i18next';
|
18
19
|
import { type locationData } from '../stock-items/types';
|
19
|
-
import styles from './stock-locations-table.scss';
|
20
20
|
import { useLocationTags } from './stock-locations-table.resource';
|
21
|
+
import styles from './stock-locations-table.scss';
|
21
22
|
|
22
23
|
const LocationAdministrationSchema = z.object({
|
23
24
|
name: z.string().max(255),
|
@@ -146,7 +147,7 @@ const LocationAdministrationForm: React.FC<LocationAdministrationFormProps> = ({
|
|
146
147
|
</ModalBody>
|
147
148
|
<ModalFooter>
|
148
149
|
<Button onClick={() => onModalChange(false)} kind="secondary">
|
149
|
-
{
|
150
|
+
{getCoreTranslation('cancel')}
|
150
151
|
</Button>
|
151
152
|
<Button disabled={!isDirty} type="submit">
|
152
153
|
<span>{t('save', 'Save')}</span>
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
|
-
import { useStockLocationPages } from './stock-locations-table.resource';
|
3
2
|
import {
|
4
3
|
Button,
|
5
4
|
DataTableSkeleton,
|
@@ -8,15 +7,16 @@ import {
|
|
8
7
|
TableToolbarSearch,
|
9
8
|
Tile,
|
10
9
|
} from '@carbon/react';
|
11
|
-
import
|
12
|
-
import { ResourceRepresentation } from '../core/api/api';
|
13
|
-
import DataList from '../core/components/table/table.component';
|
10
|
+
import { Add } from '@carbon/react/icons';
|
14
11
|
import { useTranslation } from 'react-i18next';
|
15
12
|
import { mutate } from 'swr';
|
16
|
-
import NewLocationForm from './add-locations-form.workspace';
|
17
|
-
import { Add } from '@carbon/react/icons';
|
18
|
-
import { handleMutate } from '../utils';
|
19
13
|
import { restBaseUrl } from '@openmrs/esm-framework';
|
14
|
+
import { handleMutate } from '../utils';
|
15
|
+
import { ResourceRepresentation } from '../core/api/api';
|
16
|
+
import { useStockLocationPages } from './stock-locations-table.resource';
|
17
|
+
import DataList from '../core/components/table/table.component';
|
18
|
+
import NewLocationForm from './add-locations-form.workspace';
|
19
|
+
import styles from '../stock-items/stock-items-table.scss';
|
20
20
|
|
21
21
|
interface StockLocationsTableProps {
|
22
22
|
status?: string;
|
@@ -46,7 +46,9 @@ const StockLocationsItems: React.FC<StockLocationsTableProps> = () => {
|
|
46
46
|
<>
|
47
47
|
<TableToolbarSearch persistent onChange={onInputChange} />
|
48
48
|
<TableToolbarMenu>
|
49
|
-
<TableToolbarAction onClick={handleRefresh}>
|
49
|
+
<TableToolbarAction className={styles.toolbarMenuAction} onClick={handleRefresh}>
|
50
|
+
{t('refresh', 'Refresh')}
|
51
|
+
</TableToolbarAction>
|
50
52
|
</TableToolbarMenu>
|
51
53
|
{showLocationModal ? (
|
52
54
|
<NewLocationForm onModalChange={setAddLocationModal} showModal={showLocationModal} mutate={mutate} />
|
@@ -1,3 +1,6 @@
|
|
1
|
+
import { useMemo } from 'react';
|
2
|
+
import { uniqBy } from 'lodash-es';
|
3
|
+
import useSWR from 'swr';
|
1
4
|
import {
|
2
5
|
type FetchResponse,
|
3
6
|
type OpenmrsResource,
|
@@ -6,30 +9,24 @@ import {
|
|
6
9
|
useSession,
|
7
10
|
restBaseUrl,
|
8
11
|
} from '@openmrs/esm-framework';
|
9
|
-
import { type ResourceFilterCriteria, toQueryParams } from '../core/api/api';
|
10
|
-
import { type PageableResult } from '../core/api/types/PageableResult';
|
11
|
-
import { type OpenMRSLocation, type OpenMRSLocationTag } from '../core/api/types/Location';
|
12
|
-
import useSWR from 'swr';
|
13
|
-
import { type Role } from '../core/api/types/identity/Role';
|
14
|
-
import { type User } from '../core/api/types/identity/User';
|
15
|
-
import { type StockOperationType } from '../core/api/types/stockOperation/StockOperationType';
|
16
12
|
import { type Concept } from '../core/api/types/concept/Concept';
|
17
|
-
import { type Party } from '../core/api/types/Party';
|
18
13
|
import { type Drug } from '../core/api/types/concept/Drug';
|
14
|
+
import { type OpenMRSLocation, type OpenMRSLocationTag } from '../core/api/types/Location';
|
15
|
+
import { type PageableResult } from '../core/api/types/PageableResult';
|
16
|
+
import { type Party } from '../core/api/types/Party';
|
19
17
|
import { type Patient } from '../core/api/types/identity/Patient';
|
20
|
-
import {
|
21
|
-
import {
|
18
|
+
import { type ResourceFilterCriteria, toQueryParams } from '../core/api/api';
|
19
|
+
import { type Role } from '../core/api/types/identity/Role';
|
20
|
+
import { type StockOperationType } from '../core/api/types/stockOperation/StockOperationType';
|
21
|
+
import { type User } from '../core/api/types/identity/User';
|
22
22
|
import { type UserRoleScope } from '../core/api/types/identity/UserRoleScope';
|
23
23
|
|
24
|
+
export type ConceptFilterCriteria = ResourceFilterCriteria;
|
25
|
+
export type DrugFilterCriteria = ResourceFilterCriteria;
|
26
|
+
export type LocationFilterCriteria = ResourceFilterCriteria;
|
24
27
|
export type PatientFilterCriteria = ResourceFilterCriteria;
|
25
|
-
|
26
28
|
export type UserFilterCriteria = ResourceFilterCriteria;
|
27
29
|
|
28
|
-
export type DrugFilterCriteria = ResourceFilterCriteria;
|
29
|
-
|
30
|
-
export type ConceptFilterCriteria = ResourceFilterCriteria;
|
31
|
-
|
32
|
-
export type LocationFilterCriteria = ResourceFilterCriteria;
|
33
30
|
interface FHIRResponse {
|
34
31
|
entry: Array<{ resource: fhir.Location }>;
|
35
32
|
total: number;
|
@@ -190,7 +187,10 @@ export function useConcept(conceptUuid: string) {
|
|
190
187
|
data: Concept;
|
191
188
|
},
|
192
189
|
Error
|
193
|
-
>(apiUrl, openmrsFetch
|
190
|
+
>(conceptUuid ? apiUrl : null, openmrsFetch, {
|
191
|
+
errorRetryCount: 2,
|
192
|
+
});
|
193
|
+
|
194
194
|
return {
|
195
195
|
items: data?.data || <Concept>{},
|
196
196
|
isLoading,
|
@@ -207,6 +207,7 @@ export function useParties() {
|
|
207
207
|
},
|
208
208
|
Error
|
209
209
|
>(apiUrl, openmrsFetch);
|
210
|
+
|
210
211
|
return {
|
211
212
|
items: data?.data || <PageableResult<Party>>{},
|
212
213
|
isLoading,
|
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
type StopOperationActionType,
|
9
9
|
} from '../../core/api/types/stockOperation/StockOperationAction';
|
10
10
|
import { executeStockOperationAction } from '../stock-operations.resource';
|
11
|
-
import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
|
11
|
+
import { getCoreTranslation, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
|
12
12
|
import { closeOverlay } from '../../core/components/overlay/hook';
|
13
13
|
import { extractErrorMessagesFromResponse } from '../../constants';
|
14
14
|
import { handleMutate } from '../../utils';
|
@@ -135,7 +135,7 @@ const StockOperationDialog: React.FC<StockOperationDialogProps> = ({ title, requ
|
|
135
135
|
</ModalBody>
|
136
136
|
<ModalFooter>
|
137
137
|
<Button kind="secondary" onClick={closeModal}>
|
138
|
-
{
|
138
|
+
{getCoreTranslation('cancel')}
|
139
139
|
</Button>
|
140
140
|
{isApproving ? <InlineLoading /> : <Button type="submit">{t('submit', 'Submit')}</Button>}
|
141
141
|
</ModalFooter>
|
package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
import { ComboBox, SelectSkeleton } from '@carbon/react';
|
2
1
|
import React, { useEffect, useMemo } from 'react';
|
3
2
|
import { useTranslation } from 'react-i18next';
|
3
|
+
import { ComboBox, SelectSkeleton } from '@carbon/react';
|
4
4
|
import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
|
5
|
+
import { formatForDatePicker } from '../../../constants';
|
5
6
|
import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
7
|
import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
|
7
|
-
import { formatForDatePicker } from '../../../constants';
|
8
8
|
|
9
9
|
interface BatchNoSelectorProps {
|
10
10
|
stockItemUuid: string;
|
@@ -48,23 +48,23 @@ const BatchNoSelector: React.FC<BatchNoSelectorProps> = ({ stockItemUuid, error,
|
|
48
48
|
|
49
49
|
return (
|
50
50
|
<ComboBox
|
51
|
-
style={{ flexGrow: '1' }}
|
52
|
-
titleText={t('batchNo', 'Batch')}
|
53
|
-
name={'stockBatchUuid'}
|
54
51
|
id={'stockBatchUuid'}
|
52
|
+
invalid={!!error}
|
53
|
+
invalidText={error}
|
55
54
|
items={filteredBatches || []}
|
56
|
-
onChange={(data: { selectedItem?: StockBatchDTO }) => {
|
57
|
-
onValueChange(data.selectedItem?.uuid);
|
58
|
-
}}
|
59
|
-
selectedItem={initialSelectedItem}
|
60
55
|
itemToString={(s: StockBatchDTO) =>
|
61
56
|
s?.batchNo
|
62
57
|
? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
|
63
58
|
: ''
|
64
59
|
}
|
60
|
+
name={'stockBatchUuid'}
|
61
|
+
onChange={(data: { selectedItem?: StockBatchDTO }) => {
|
62
|
+
onValueChange(data.selectedItem?.uuid);
|
63
|
+
}}
|
65
64
|
placeholder={t('filter', "'Filter") + '...'}
|
66
|
-
|
67
|
-
|
65
|
+
selectedItem={initialSelectedItem}
|
66
|
+
style={{ flexGrow: '1' }}
|
67
|
+
titleText={t('batchNo', 'Batch no.')}
|
68
68
|
/>
|
69
69
|
);
|
70
70
|
};
|
package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx
CHANGED
@@ -1,64 +1,127 @@
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
2
|
-
import userEvent from '@testing-library/user-event';
|
3
1
|
import React from 'react';
|
2
|
+
import userEvent from '@testing-library/user-event';
|
3
|
+
import { render, screen } from '@testing-library/react';
|
4
|
+
import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
|
4
5
|
import { type StockItemInventory } from '../../../core/api/types/stockItem/StockItemInventory';
|
6
|
+
import { formatForDatePicker } from '../../../constants';
|
5
7
|
import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
8
|
import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
|
7
9
|
import BatchNoSelector from './batch-no-selector.component';
|
8
|
-
import { formatForDatePicker } from '../../../constants';
|
9
10
|
|
10
|
-
jest.mock('../hooks/useStockItemBatchNumbers')
|
11
|
-
jest.
|
12
|
-
|
13
|
-
|
11
|
+
jest.mock('../hooks/useStockItemBatchNumbers', () => ({
|
12
|
+
useStockItemBatchNumbers: jest.fn(),
|
13
|
+
}));
|
14
|
+
|
15
|
+
jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource', () => ({
|
16
|
+
useStockItemBatchInformationHook: jest.fn(),
|
14
17
|
}));
|
15
18
|
|
16
|
-
const mockUseStockItemBatchNumbers =
|
17
|
-
const mockUseStockItemBatchInformationHook =
|
19
|
+
const mockUseStockItemBatchNumbers = jest.mocked(useStockItemBatchNumbers);
|
20
|
+
const mockUseStockItemBatchInformationHook = jest.mocked(useStockItemBatchInformationHook);
|
18
21
|
|
19
22
|
describe('BatchNoSelector', () => {
|
20
23
|
const mockOnValueChange = jest.fn();
|
21
24
|
const mockStockItemUuid = 'test-uuid';
|
22
25
|
const mockExpiration = new Date();
|
23
|
-
beforeEach(() => {
|
24
|
-
jest.clearAllMocks();
|
25
26
|
|
27
|
+
beforeEach(() => {
|
26
28
|
mockUseStockItemBatchNumbers.mockReturnValue({
|
27
29
|
isLoading: false,
|
28
30
|
stockItemBatchNos: [
|
29
|
-
{
|
30
|
-
|
31
|
+
{
|
32
|
+
uuid: '1',
|
33
|
+
batchNo: 'BATCH-001',
|
34
|
+
quantity: '10',
|
35
|
+
expiration: mockExpiration,
|
36
|
+
stockItemUuid: 'item1',
|
37
|
+
voided: false,
|
38
|
+
},
|
39
|
+
{
|
40
|
+
uuid: '2',
|
41
|
+
batchNo: 'BATCH-002',
|
42
|
+
quantity: '20',
|
43
|
+
expiration: mockExpiration,
|
44
|
+
stockItemUuid: 'item2',
|
45
|
+
voided: false,
|
46
|
+
},
|
31
47
|
],
|
48
|
+
setLimit: jest.fn(),
|
49
|
+
setRepresentation: jest.fn(),
|
50
|
+
setSearchString: jest.fn(),
|
32
51
|
});
|
52
|
+
|
33
53
|
mockUseStockItemBatchInformationHook.mockReturnValue({
|
34
54
|
items: [
|
35
55
|
{ batchNumber: 'BATCH-001', quantity: 10 },
|
36
56
|
{ batchNumber: 'BATCH-002', quantity: 20 },
|
37
57
|
] as StockItemInventory[],
|
58
|
+
totalCount: 2,
|
59
|
+
currentPage: 1,
|
60
|
+
currentPageSize: 10,
|
61
|
+
setCurrentPage: jest.fn(),
|
62
|
+
setStockBatchUuid: jest.fn(),
|
38
63
|
setStockItemUuid: jest.fn(),
|
64
|
+
setPageSize: jest.fn(),
|
65
|
+
pageSizes: [10, 20, 50],
|
66
|
+
isLoading: false,
|
67
|
+
error: null,
|
68
|
+
setLocationUuid: jest.fn(),
|
69
|
+
setPartyUuid: jest.fn(),
|
70
|
+
setSearchString: jest.fn(),
|
39
71
|
});
|
40
72
|
});
|
41
73
|
|
42
|
-
it('should render loading skeleton when isLoading is true', () => {
|
43
|
-
mockUseStockItemBatchNumbers.mockReturnValue({
|
44
|
-
|
74
|
+
it('should render a loading skeleton when "isLoading" is true', () => {
|
75
|
+
mockUseStockItemBatchNumbers.mockReturnValue({
|
76
|
+
isLoading: true,
|
77
|
+
stockItemBatchNos: [] as StockBatchDTO[],
|
78
|
+
setLimit: jest.fn(),
|
79
|
+
setRepresentation: jest.fn(),
|
80
|
+
setSearchString: jest.fn(),
|
81
|
+
});
|
82
|
+
|
83
|
+
mockUseStockItemBatchInformationHook.mockReturnValue({
|
84
|
+
isLoading: true,
|
85
|
+
items: [],
|
86
|
+
setStockItemUuid: jest.fn(),
|
87
|
+
totalCount: 0,
|
88
|
+
currentPage: 1,
|
89
|
+
currentPageSize: 10,
|
90
|
+
setCurrentPage: jest.fn(),
|
91
|
+
setStockBatchUuid: jest.fn(),
|
92
|
+
setPageSize: jest.fn(),
|
93
|
+
pageSizes: [10, 20, 50],
|
94
|
+
error: null,
|
95
|
+
setLocationUuid: jest.fn(),
|
96
|
+
setPartyUuid: jest.fn(),
|
97
|
+
setSearchString: jest.fn(),
|
98
|
+
});
|
99
|
+
|
45
100
|
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
101
|
+
|
46
102
|
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
47
103
|
});
|
48
104
|
|
49
|
-
it('should render combobox with batch numbers', async () => {
|
105
|
+
it('should render a combobox with batch numbers', async () => {
|
50
106
|
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
107
|
+
|
51
108
|
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
52
|
-
expect(screen.getByText(
|
109
|
+
expect(screen.getByText(/batch no./i)).toBeInTheDocument();
|
53
110
|
});
|
54
111
|
|
55
112
|
it('should handle batch selection', async () => {
|
113
|
+
const user = userEvent.setup();
|
114
|
+
|
56
115
|
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
116
|
+
|
57
117
|
const combobox = screen.getByRole('combobox');
|
58
|
-
await
|
59
|
-
await
|
118
|
+
await user.click(combobox);
|
119
|
+
await user.type(combobox, 'BATCH-001');
|
120
|
+
|
60
121
|
const option = screen.getByText(`BATCH-001 | Qty: 10 | Expiry: ${formatForDatePicker(mockExpiration)}`);
|
61
|
-
|
122
|
+
|
123
|
+
await user.click(option);
|
124
|
+
|
62
125
|
expect(mockOnValueChange).toHaveBeenCalledWith('1');
|
63
126
|
});
|
64
127
|
|
@@ -71,18 +134,45 @@ describe('BatchNoSelector', () => {
|
|
71
134
|
});
|
72
135
|
|
73
136
|
it('should filter out batches with zero or undefined quantity', async () => {
|
137
|
+
const user = userEvent.setup();
|
138
|
+
|
74
139
|
mockUseStockItemBatchNumbers.mockReturnValue({
|
75
140
|
isLoading: false,
|
76
141
|
stockItemBatchNos: [
|
77
|
-
{
|
78
|
-
|
79
|
-
|
142
|
+
{
|
143
|
+
uuid: '1',
|
144
|
+
batchNo: 'BATCH-001',
|
145
|
+
quantity: '10',
|
146
|
+
expiration: mockExpiration,
|
147
|
+
stockItemUuid: 'item1',
|
148
|
+
voided: false,
|
149
|
+
},
|
150
|
+
{
|
151
|
+
uuid: '2',
|
152
|
+
batchNo: 'BATCH-002',
|
153
|
+
quantity: '0',
|
154
|
+
expiration: mockExpiration,
|
155
|
+
stockItemUuid: 'item2',
|
156
|
+
voided: false,
|
157
|
+
},
|
158
|
+
{
|
159
|
+
uuid: '3',
|
160
|
+
batchNo: 'BATCH-003',
|
161
|
+
quantity: undefined,
|
162
|
+
expiration: mockExpiration,
|
163
|
+
stockItemUuid: 'item3',
|
164
|
+
voided: false,
|
165
|
+
},
|
80
166
|
],
|
167
|
+
setLimit: jest.fn(),
|
168
|
+
setRepresentation: jest.fn(),
|
169
|
+
setSearchString: jest.fn(),
|
81
170
|
});
|
82
171
|
|
83
172
|
render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
|
173
|
+
|
84
174
|
const combobox = screen.getByRole('combobox');
|
85
|
-
await
|
175
|
+
await user.click(combobox);
|
86
176
|
|
87
177
|
expect(screen.queryByText('BATCH-002')).not.toBeInTheDocument();
|
88
178
|
expect(screen.queryByText('BATCH-003')).not.toBeInTheDocument();
|