@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.
Files changed (109) hide show
  1. package/.husky/pre-commit +4 -1
  2. package/.husky/pre-push +2 -0
  3. package/__mocks__/react-i18next.js +8 -9
  4. package/dist/10.js +1 -1
  5. package/dist/10.js.map +1 -1
  6. package/dist/119.js +1 -1
  7. package/dist/119.js.map +1 -1
  8. package/dist/14.js +1 -1
  9. package/dist/14.js.map +1 -1
  10. package/dist/172.js +1 -1
  11. package/dist/172.js.map +1 -1
  12. package/dist/20.js +1 -1
  13. package/dist/20.js.map +1 -1
  14. package/dist/290.js +1 -1
  15. package/dist/290.js.map +1 -1
  16. package/dist/33.js +1 -0
  17. package/dist/33.js.map +1 -0
  18. package/dist/467.js +1 -1
  19. package/dist/467.js.map +1 -1
  20. package/dist/574.js +1 -1
  21. package/dist/606.js +1 -1
  22. package/dist/606.js.map +1 -1
  23. package/dist/642.js +1 -1
  24. package/dist/642.js.map +1 -1
  25. package/dist/675.js +1 -1
  26. package/dist/675.js.map +1 -1
  27. package/dist/727.js +1 -1
  28. package/dist/727.js.map +1 -1
  29. package/dist/842.js +1 -1
  30. package/dist/842.js.map +1 -1
  31. package/dist/93.js +1 -1
  32. package/dist/93.js.map +1 -1
  33. package/dist/main.js +1 -1
  34. package/dist/main.js.map +1 -1
  35. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +70 -70
  36. package/dist/routes.json +1 -1
  37. package/jest.config.js +6 -3
  38. package/package.json +1 -1
  39. package/src/core/components/table/table.component.tsx +2 -2
  40. package/src/index.ts +5 -5
  41. package/src/stock-items/add-bulk-stock-item/add-stock-items-bulk-import-action-button.component.tsx +3 -3
  42. package/src/stock-items/add-bulk-stock-item/{stock-items-bulk-import.component.tsx → stock-items-bulk-import.modal.tsx} +20 -19
  43. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.resource.ts +1 -1
  44. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.test.tsx +59 -59
  45. package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +6 -6
  46. package/src/stock-items/add-stock-item/add-stock-item.component.tsx +6 -4
  47. package/src/stock-items/add-stock-item/add-stock-item.scss +5 -0
  48. package/src/stock-items/add-stock-item/add-stock-item.test.tsx +28 -43
  49. package/src/stock-items/add-stock-item/packaging-units/packaging-units-delete-modal.component.tsx +3 -4
  50. package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +9 -10
  51. package/src/stock-items/add-stock-item/packaging-units/packaging-units.scss +4 -4
  52. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +27 -19
  53. package/src/stock-items/add-stock-item/stock-item-references/stock-item-references.scss +4 -4
  54. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +15 -9
  55. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +1 -0
  56. package/src/stock-items/add-stock-item/stock-item-rules/delete-stock-rule-modal.component.tsx +2 -1
  57. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.component.tsx +14 -16
  58. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.scss +7 -3
  59. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-bincard-preview.modal.tsx +14 -6
  60. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-stockcard-preview.modal.tsx +14 -8
  61. package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
  62. package/src/stock-items/stock-item.utils.tsx +3 -5
  63. package/src/stock-items/stock-items-table.component.tsx +47 -45
  64. package/src/stock-items/stock-items-table.resource.ts +2 -2
  65. package/src/stock-items/stock-items-table.scss +5 -1
  66. package/src/stock-items/stock-items-table.test.tsx +106 -65
  67. package/src/stock-locations/location-admin-form.component.tsx +5 -4
  68. package/src/stock-locations/stock-locations-table.component.tsx +10 -8
  69. package/src/stock-lookups/stock-lookups.resource.ts +18 -17
  70. package/src/stock-operations/stock-operations-dialog/stock-operations-dialog.component.tsx +2 -2
  71. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +11 -11
  72. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +115 -25
  73. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +107 -65
  74. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +9 -9
  75. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +35 -153
  76. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +82 -29
  77. package/src/stock-operations/stock-operations-forms/step1.test.tsx +204 -69
  78. package/src/stock-operations/stock-operations-forms/step2.test.tsx +140 -63
  79. package/src/stock-operations/stock-operations-forms/step3.test.tsx +79 -60
  80. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +6 -5
  81. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +12 -11
  82. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +1 -0
  83. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +20 -12
  84. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +1 -0
  85. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +1 -3
  86. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +2 -1
  87. package/src/stock-reports/generate-report/create-stock-report.scss +3 -2
  88. package/src/stock-reports/generate-report/create-stock-report.workspace.tsx +32 -25
  89. package/src/stock-reports/report-list/stock-report-parameters.component.tsx +1 -1
  90. package/src/stock-reports/report-list/stock-report-status.component.tsx +1 -1
  91. package/src/stock-reports/report-list/stock-reports.component.tsx +24 -25
  92. package/src/stock-reports/report-list/stock-reports.scss +10 -2
  93. package/src/stock-sources/add-stock-sources/add-stock-sources.scss +11 -4
  94. package/src/stock-sources/add-stock-sources/add-stock-sources.test.tsx +38 -36
  95. package/src/stock-sources/add-stock-sources/add-stock-sources.workspace.tsx +35 -30
  96. package/src/stock-sources/delete-stock-modal.component.tsx +2 -1
  97. package/src/stock-sources/stock-sources-delete/stock-sources-delete.test.tsx +27 -36
  98. package/src/stock-sources/stock-sources-filter/stock-sources-filter.component.tsx +36 -21
  99. package/src/stock-sources/stock-sources-items-table.component.tsx +16 -17
  100. package/src/stock-sources/stock-sources-items-table.resource.ts +8 -6
  101. package/src/stock-sources/stock-sources-items-table.test.tsx +79 -36
  102. package/src/stock-sources/stock-sources.scss +6 -2
  103. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.scss +5 -13
  104. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.workspace.tsx +2 -2
  105. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.component.tsx +2 -1
  106. package/translations/en.json +8 -6
  107. package/tsconfig.json +4 -0
  108. package/dist/627.js +0 -1
  109. 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 StockItemsTableComponent from './stock-items-table.component';
5
- import { useStockItemsPages } from './stock-items-table.resource';
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 { launchAddOrStockItemWorkspace } from './stock-item.utils';
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
- launchAddOrStockItemWorkspace: jest.fn(),
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
- jest.clearAllMocks();
55
- (useStockItemsPages as jest.Mock).mockReturnValue(mockUseStockItemsPages);
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
- await screen.findByText('Refresh');
67
+ render(<StockItemsTableComponent />);
68
68
 
69
- expect(screen.getByText('type')).toBeInTheDocument();
70
- expect(screen.getByText('genericName')).toBeInTheDocument();
71
- expect(screen.getByText('commonName')).toBeInTheDocument();
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
- (useStockItemsPages as jest.Mock).mockReturnValue({ ...mockUseStockItemsPages, isLoading: true });
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
- expect(screen.getByText('Test Item 0')).toBeInTheDocument();
83
- expect(screen.getAllByText('drug').length).toBeGreaterThan(0);
84
- expect(screen.getAllByText('other').length).toBeGreaterThan(0);
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
- const nextPageButton = screen.getByLabelText('Next page');
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('Items per page:');
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('Refresh');
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 launchAddOrEditDialog when edit button is clicked', async () => {
129
- render(<StockItemsTableComponent />);
168
+ it('triggers launchAddOrEditStockItemWorkspace when edit button is clicked', async () => {
130
169
  const user = userEvent.setup();
131
- const editButtons = screen.getAllByLabelText('Edit Stock Item');
170
+ render(<StockItemsTableComponent />);
171
+
172
+ const editButtons = screen.getAllByLabelText(/edit stock item/i);
132
173
  await user.click(editButtons[0]);
133
174
 
134
- expect(launchAddOrStockItemWorkspace).toHaveBeenCalledWith(
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
- {t('cancel', 'Cancel')}
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 styles from '../stock-items/stock-items-table.scss';
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}>Refresh</TableToolbarAction>
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 { useMemo } from 'react';
21
- import { uniqBy } from 'lodash-es';
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
- {t('cancel', 'Cancel')}
138
+ {getCoreTranslation('cancel')}
139
139
  </Button>
140
140
  {isApproving ? <InlineLoading /> : <Button type="submit">{t('submit', 'Submit')}</Button>}
141
141
  </ModalFooter>
@@ -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
- invalid={error}
67
- invalidText={error}
65
+ selectedItem={initialSelectedItem}
66
+ style={{ flexGrow: '1' }}
67
+ titleText={t('batchNo', 'Batch no.')}
68
68
  />
69
69
  );
70
70
  };
@@ -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.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource');
12
- jest.mock('react-i18next', () => ({
13
- useTranslation: () => ({ t: (key: string) => key }),
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 = useStockItemBatchNumbers as jest.Mock;
17
- const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
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
- { uuid: '1', batchNo: 'BATCH-001', quantity: 10, expiration: mockExpiration },
30
- { uuid: '2', batchNo: 'BATCH-002', quantity: 20, expiration: mockExpiration },
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({ isLoading: true, stockItemBatchNos: [] });
44
- mockUseStockItemBatchInformationHook.mockReturnValue({ isLoading: true, items: [], setStockItemUuid: jest.fn() });
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('batchNo')).toBeInTheDocument();
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 userEvent.click(combobox);
59
- await userEvent.type(combobox, 'BATCH-001');
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
- await userEvent.click(option);
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
- { uuid: '1', batchNo: 'BATCH-001', quantity: 10 },
78
- { uuid: '2', batchNo: 'BATCH-002', quantity: 0 },
79
- { uuid: '3', batchNo: 'BATCH-003', quantity: undefined },
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 userEvent.click(combobox);
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();