@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
@@ -0,0 +1,216 @@
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
2
|
+
import userEvent from '@testing-library/user-event';
|
3
|
+
import React from 'react';
|
4
|
+
|
5
|
+
import { useConfig } from '@openmrs/esm-framework';
|
6
|
+
import { useFormContext } from 'react-hook-form';
|
7
|
+
import { useConcept } from '../../../stock-lookups/stock-lookups.resource';
|
8
|
+
import StockOperationReasonSelector from './stock-operation-reason-selector.component';
|
9
|
+
|
10
|
+
// Mock the hooks
|
11
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
12
|
+
useConfig: jest.fn(),
|
13
|
+
}));
|
14
|
+
|
15
|
+
jest.mock('react-hook-form', () => ({
|
16
|
+
useFormContext: jest.fn(),
|
17
|
+
Controller: ({ render }) => render({ field: {}, fieldState: {} }),
|
18
|
+
}));
|
19
|
+
jest.mock('../../../stock-lookups/stock-lookups.resource');
|
20
|
+
jest.mock('react-i18next', () => ({
|
21
|
+
useTranslation: () => ({ t: (key: string) => key }),
|
22
|
+
}));
|
23
|
+
|
24
|
+
const mockUseConcept = useConcept as jest.Mock;
|
25
|
+
const mockUseConfig = useConfig as jest.Mock;
|
26
|
+
const mockUseFormContext = useFormContext as jest.Mock;
|
27
|
+
|
28
|
+
describe('StockoperationReasonSelector', () => {
|
29
|
+
const mockConcepts = {
|
30
|
+
uuid: '3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25',
|
31
|
+
display: 'Stock Adjustment Reason',
|
32
|
+
name: {
|
33
|
+
display: 'Stock Adjustment Reason',
|
34
|
+
uuid: '4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
|
35
|
+
name: 'Stock Adjustment Reason',
|
36
|
+
locale: 'en',
|
37
|
+
localePreferred: true,
|
38
|
+
conceptNameType: 'FULLY_SPECIFIED',
|
39
|
+
links: [
|
40
|
+
{
|
41
|
+
rel: 'self',
|
42
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
|
43
|
+
resourceAlias: 'name',
|
44
|
+
},
|
45
|
+
{
|
46
|
+
rel: 'full',
|
47
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc?v=full',
|
48
|
+
resourceAlias: 'name',
|
49
|
+
},
|
50
|
+
],
|
51
|
+
resourceVersion: '1.9',
|
52
|
+
},
|
53
|
+
datatype: {
|
54
|
+
uuid: '8d4a48b6-c2cc-11de-8d13-0010c6dffd0f',
|
55
|
+
display: 'Coded',
|
56
|
+
links: [
|
57
|
+
{
|
58
|
+
rel: 'self',
|
59
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/conceptdatatype/8d4a48b6-c2cc-11de-8d13-0010c6dffd0f',
|
60
|
+
resourceAlias: 'conceptdatatype',
|
61
|
+
},
|
62
|
+
],
|
63
|
+
},
|
64
|
+
conceptClass: {
|
65
|
+
uuid: '8d491e50-c2cc-11de-8d13-0010c6dffd0f',
|
66
|
+
display: 'Question',
|
67
|
+
links: [
|
68
|
+
{
|
69
|
+
rel: 'self',
|
70
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/conceptclass/8d491e50-c2cc-11de-8d13-0010c6dffd0f',
|
71
|
+
resourceAlias: 'conceptclass',
|
72
|
+
},
|
73
|
+
],
|
74
|
+
},
|
75
|
+
set: false,
|
76
|
+
version: null,
|
77
|
+
retired: false,
|
78
|
+
names: [
|
79
|
+
{
|
80
|
+
uuid: '4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
|
81
|
+
display: 'Stock Adjustment Reason',
|
82
|
+
links: [
|
83
|
+
{
|
84
|
+
rel: 'self',
|
85
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
|
86
|
+
resourceAlias: 'name',
|
87
|
+
},
|
88
|
+
],
|
89
|
+
},
|
90
|
+
{
|
91
|
+
uuid: '36eb0855-c810-4816-9bed-1de5c615e702',
|
92
|
+
display: 'Stock Adjustment Reason',
|
93
|
+
links: [
|
94
|
+
{
|
95
|
+
rel: 'self',
|
96
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/36eb0855-c810-4816-9bed-1de5c615e702',
|
97
|
+
resourceAlias: 'name',
|
98
|
+
},
|
99
|
+
],
|
100
|
+
},
|
101
|
+
],
|
102
|
+
descriptions: [
|
103
|
+
{
|
104
|
+
uuid: '67311e07-1935-448b-8305-7d11abf0de63',
|
105
|
+
display: 'Stock Adjustment Reason',
|
106
|
+
links: [
|
107
|
+
{
|
108
|
+
rel: 'self',
|
109
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/description/67311e07-1935-448b-8305-7d11abf0de63',
|
110
|
+
resourceAlias: 'description',
|
111
|
+
},
|
112
|
+
],
|
113
|
+
},
|
114
|
+
],
|
115
|
+
mappings: [],
|
116
|
+
answers: [
|
117
|
+
{
|
118
|
+
uuid: '165420AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
119
|
+
display: 'Drug not available due to expired medication',
|
120
|
+
links: [
|
121
|
+
{
|
122
|
+
rel: 'self',
|
123
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/165420AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
124
|
+
resourceAlias: 'concept',
|
125
|
+
},
|
126
|
+
],
|
127
|
+
},
|
128
|
+
{
|
129
|
+
uuid: '160584AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
130
|
+
display: 'Lost or ran out of medication',
|
131
|
+
links: [
|
132
|
+
{
|
133
|
+
rel: 'self',
|
134
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/160584AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
135
|
+
resourceAlias: 'concept',
|
136
|
+
},
|
137
|
+
],
|
138
|
+
},
|
139
|
+
{
|
140
|
+
uuid: '122835AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
141
|
+
display: 'Work Shift Change',
|
142
|
+
links: [
|
143
|
+
{
|
144
|
+
rel: 'self',
|
145
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/122835AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
146
|
+
resourceAlias: 'concept',
|
147
|
+
},
|
148
|
+
],
|
149
|
+
},
|
150
|
+
{
|
151
|
+
uuid: '160561AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
152
|
+
display: 'New drug available',
|
153
|
+
links: [
|
154
|
+
{
|
155
|
+
rel: 'self',
|
156
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/160561AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
157
|
+
resourceAlias: 'concept',
|
158
|
+
},
|
159
|
+
],
|
160
|
+
},
|
161
|
+
],
|
162
|
+
setMembers: [],
|
163
|
+
attributes: [],
|
164
|
+
links: [
|
165
|
+
{
|
166
|
+
rel: 'self',
|
167
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25',
|
168
|
+
resourceAlias: 'concept',
|
169
|
+
},
|
170
|
+
{
|
171
|
+
rel: 'full',
|
172
|
+
uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25?v=full',
|
173
|
+
resourceAlias: 'concept',
|
174
|
+
},
|
175
|
+
],
|
176
|
+
resourceVersion: '2.0',
|
177
|
+
};
|
178
|
+
beforeEach(() => {
|
179
|
+
jest.clearAllMocks();
|
180
|
+
mockUseConfig.mockReturnValue({ stockAdjustmentReasonUUID: 'uuid' });
|
181
|
+
mockUseConcept.mockReturnValue({
|
182
|
+
isLoading: false,
|
183
|
+
items: mockConcepts,
|
184
|
+
});
|
185
|
+
});
|
186
|
+
|
187
|
+
it('should display loading state while loading reason concepts', () => {
|
188
|
+
mockUseFormContext.mockReturnValue({ control: {} });
|
189
|
+
mockUseConcept.mockReturnValue({
|
190
|
+
isLoading: true,
|
191
|
+
items: mockConcepts,
|
192
|
+
});
|
193
|
+
render(<StockOperationReasonSelector />);
|
194
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
195
|
+
});
|
196
|
+
it('should display error notification when error encountered while fetching concepts', () => {
|
197
|
+
const errorMessahe = 'Error message';
|
198
|
+
mockUseConcept.mockReturnValue({
|
199
|
+
isLoading: false,
|
200
|
+
items: mockConcepts,
|
201
|
+
error: new Error(errorMessahe),
|
202
|
+
});
|
203
|
+
render(<StockOperationReasonSelector />);
|
204
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
205
|
+
expect(screen.getByText(errorMessahe)).toBeInTheDocument();
|
206
|
+
});
|
207
|
+
|
208
|
+
it('renders ComboBox with reasons', async () => {
|
209
|
+
render(<StockOperationReasonSelector />);
|
210
|
+
const combobox = screen.getByRole('combobox');
|
211
|
+
await userEvent.click(combobox);
|
212
|
+
mockConcepts.answers.forEach((ans) => {
|
213
|
+
expect(screen.getByText(`${ans?.display}`)).toBeInTheDocument();
|
214
|
+
});
|
215
|
+
});
|
216
|
+
});
|
@@ -1,31 +1,35 @@
|
|
1
1
|
import { TextInput } from '@carbon/react';
|
2
2
|
import React, { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
3
|
-
import { useStockItemBatchNos } from './batch-no-selector.resource';
|
4
3
|
import { TextInputSkeleton } from '@carbon/react';
|
4
|
+
import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
5
6
|
|
6
7
|
type UniqueBatchNoEntryInputProps = {
|
7
8
|
defaultValue?: string;
|
8
9
|
onValueChange?: (value: string) => void;
|
9
10
|
error?: string;
|
10
11
|
stockItemUuid: string;
|
12
|
+
stockOperationItemUuid: string;
|
11
13
|
};
|
12
14
|
const UniqueBatchNoEntryInput: React.FC<UniqueBatchNoEntryInputProps> = ({
|
13
15
|
defaultValue,
|
14
16
|
onValueChange,
|
15
17
|
error,
|
16
18
|
stockItemUuid,
|
19
|
+
stockOperationItemUuid,
|
17
20
|
}) => {
|
18
|
-
const { isLoading, stockItemBatchNos } =
|
21
|
+
const { isLoading, stockItemBatchNos } = useStockItemBatchNumbers(stockItemUuid);
|
19
22
|
const [value, setValue] = useState(defaultValue);
|
20
23
|
const [_error, setError] = useState<string>();
|
21
|
-
|
24
|
+
const { t } = useTranslation();
|
25
|
+
const isNewItem = useMemo(() => stockOperationItemUuid.startsWith('new-item'), [stockOperationItemUuid]);
|
22
26
|
const batchNoAlreadyUsed = useMemo(
|
23
|
-
() => stockItemBatchNos?.findIndex((batchNo) => batchNo.batchNo === value) !== -1,
|
24
|
-
[stockItemBatchNos, value],
|
27
|
+
() => isNewItem && stockItemBatchNos?.findIndex((batchNo) => batchNo.batchNo === value) !== -1,
|
28
|
+
[stockItemBatchNos, value, isNewItem],
|
25
29
|
);
|
26
30
|
|
27
31
|
useEffect(() => {
|
28
|
-
if (defaultValue) setValue(defaultValue);
|
32
|
+
if (defaultValue) setValue(defaultValue ?? '');
|
29
33
|
}, [defaultValue]);
|
30
34
|
|
31
35
|
useEffect(() => {
|
@@ -41,12 +45,13 @@ const UniqueBatchNoEntryInput: React.FC<UniqueBatchNoEntryInputProps> = ({
|
|
41
45
|
|
42
46
|
return (
|
43
47
|
<TextInput
|
44
|
-
size="sm"
|
45
48
|
maxLength={50}
|
46
49
|
onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
|
47
50
|
value={value}
|
48
51
|
invalidText={_error ?? error}
|
49
52
|
invalid={_error ?? error}
|
53
|
+
placeholder={t('batchNumber', 'Batch Number')}
|
54
|
+
labelText={t('batchNumber', 'Batch Number')}
|
50
55
|
/>
|
51
56
|
);
|
52
57
|
};
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
2
|
+
import React from 'react';
|
3
|
+
import { useFormContext } from 'react-hook-form';
|
4
|
+
import { useUser } from '../../../stock-lookups/stock-lookups.resource';
|
5
|
+
import useSearchUser from '../hooks/useSearchUser';
|
6
|
+
import UsersSelector from './users-selector.component';
|
7
|
+
import { otherUser } from '../../../core/utils/utils';
|
8
|
+
import userEvent from '@testing-library/user-event';
|
9
|
+
|
10
|
+
jest.mock('../hooks/useSearchUser');
|
11
|
+
jest.mock('../../../stock-lookups/stock-lookups.resource');
|
12
|
+
jest.mock('react-hook-form', () => ({
|
13
|
+
useFormContext: jest.fn(),
|
14
|
+
Controller: ({ render }) => render({ field: {}, fieldState: {} }),
|
15
|
+
}));
|
16
|
+
jest.mock('react-i18next', () => ({
|
17
|
+
useTranslation: () => ({ t: (key: string) => key }),
|
18
|
+
}));
|
19
|
+
|
20
|
+
const mockUseSearchUser = useSearchUser as jest.Mock;
|
21
|
+
const mockUseUser = useUser as jest.Mock;
|
22
|
+
const mockUseFormContext = useFormContext as jest.Mock;
|
23
|
+
|
24
|
+
describe('UsersSelector', () => {
|
25
|
+
beforeEach(() => {
|
26
|
+
jest.clearAllMocks();
|
27
|
+
mockUseFormContext.mockReturnValue({
|
28
|
+
control: {},
|
29
|
+
watch: jest.fn().mockImplementation((field) => {
|
30
|
+
if (field === 'responsiblePersonUuid') return 'responsibleperson.uuid';
|
31
|
+
if (field === 'responsiblePersonOther') return 'responsiblepersonother.uuid';
|
32
|
+
return '';
|
33
|
+
}),
|
34
|
+
resetField: jest.fn(),
|
35
|
+
});
|
36
|
+
});
|
37
|
+
|
38
|
+
it('renders loading state', async () => {
|
39
|
+
mockUseSearchUser.mockReturnValue({ isLoading: true, userList: [], setSearchString: jest.fn() });
|
40
|
+
mockUseUser.mockReturnValue({ isLoading: true, data: null, error: null });
|
41
|
+
|
42
|
+
render(<UsersSelector />);
|
43
|
+
|
44
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
45
|
+
});
|
46
|
+
|
47
|
+
it('renders error state', () => {
|
48
|
+
const errorMessage = 'Error message';
|
49
|
+
mockUseSearchUser.mockReturnValue({ isLoading: false, userList: [], setSearchString: jest.fn() });
|
50
|
+
mockUseUser.mockReturnValue({ isLoading: false, data: null, error: new Error(errorMessage) });
|
51
|
+
render(<UsersSelector />);
|
52
|
+
expect(screen.getByText('responsiblePersonError')).toBeInTheDocument();
|
53
|
+
expect(screen.getByText(errorMessage)).toBeInTheDocument();
|
54
|
+
});
|
55
|
+
|
56
|
+
it('renders ComboBox with user list', async () => {
|
57
|
+
mockUseSearchUser.mockReturnValue({
|
58
|
+
isLoading: false,
|
59
|
+
userList: [
|
60
|
+
{ uuid: '1', person: { display: 'User 1' } },
|
61
|
+
{ uuid: '2', person: { display: 'User 2' } },
|
62
|
+
],
|
63
|
+
setSearchString: jest.fn(),
|
64
|
+
});
|
65
|
+
|
66
|
+
mockUseUser.mockReturnValue({ isLoading: false, data: null, error: null });
|
67
|
+
render(<UsersSelector />);
|
68
|
+
expect(screen.getByText('responsiblePerson')).toBeInTheDocument();
|
69
|
+
const combobox = screen.getByRole('combobox');
|
70
|
+
fireEvent.click(combobox);
|
71
|
+
expect(screen.getByText('User 1')).toBeInTheDocument();
|
72
|
+
expect(screen.getByText('User 2')).toBeInTheDocument();
|
73
|
+
});
|
74
|
+
|
75
|
+
it('renders TextInput for other user', async () => {
|
76
|
+
mockUseSearchUser.mockReturnValue({ isLoading: false, userList: [], setSearchString: jest.fn() });
|
77
|
+
mockUseFormContext.mockReturnValue({
|
78
|
+
control: {},
|
79
|
+
watch: jest.fn().mockImplementation((field) => {
|
80
|
+
if (field === 'responsiblePersonUuid') return otherUser.uuid;
|
81
|
+
if (field === 'responsiblePersonOther') return '';
|
82
|
+
return '';
|
83
|
+
}),
|
84
|
+
resetField: jest.fn(),
|
85
|
+
});
|
86
|
+
mockUseUser.mockReturnValue({ isLoading: false, data: null, error: null });
|
87
|
+
|
88
|
+
render(<UsersSelector />);
|
89
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
90
|
+
expect(screen.getByPlaceholderText('pleaseSpecify')).toBeInTheDocument();
|
91
|
+
});
|
92
|
+
|
93
|
+
it('calls setSearchString on input change after delay simulating debounce timout', async () => {
|
94
|
+
const setSearchString = jest.fn();
|
95
|
+
mockUseSearchUser.mockReturnValue({
|
96
|
+
isLoading: false,
|
97
|
+
userList: [],
|
98
|
+
setSearchString,
|
99
|
+
});
|
100
|
+
|
101
|
+
mockUseUser.mockReturnValue({ isLoading: false, data: null, error: null });
|
102
|
+
|
103
|
+
render(<UsersSelector />);
|
104
|
+
const combobox = screen.getByRole('combobox');
|
105
|
+
await userEvent.click(combobox);
|
106
|
+
await userEvent.type(combobox, 'test');
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate debounce
|
108
|
+
expect(setSearchString).toHaveBeenCalledWith('test');
|
109
|
+
});
|
110
|
+
});
|
package/src/stock-operations/stock-operations-forms/input-components/users-selector.component.tsx
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
import { ComboBox, InlineNotification, SelectSkeleton } from '@carbon/react';
|
2
|
+
import React, { useEffect } from 'react';
|
3
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
import { User } from '../../../core/api/types/identity/User';
|
6
|
+
import { useDebounce } from '../../../core/hooks/debounce-hook';
|
7
|
+
import { otherUser } from '../../../core/utils/utils';
|
8
|
+
import { useUser } from '../../../stock-lookups/stock-lookups.resource';
|
9
|
+
import useSearchUser from '../hooks/useSearchUser';
|
10
|
+
import { Column } from '@carbon/react';
|
11
|
+
import { TextInput } from '@carbon/react';
|
12
|
+
|
13
|
+
const UsersSelector = () => {
|
14
|
+
const { isLoading, userList, setSearchString } = useSearchUser();
|
15
|
+
const { t } = useTranslation();
|
16
|
+
const debouncedSearch = useDebounce((query: string) => {
|
17
|
+
setSearchString(query);
|
18
|
+
}, 1000);
|
19
|
+
|
20
|
+
const form = useFormContext<{ responsiblePersonUuid: string; responsiblePersonOther: string }>();
|
21
|
+
const observableresponsiblePersonUuid = form.watch('responsiblePersonUuid');
|
22
|
+
const observableresponsiblePersonOther = form.watch('responsiblePersonOther');
|
23
|
+
const {
|
24
|
+
data,
|
25
|
+
isLoading: isLoadingUser,
|
26
|
+
error: userError,
|
27
|
+
} = useUser(observableresponsiblePersonUuid ?? null, 'custom:(uuid,display,person:(uuid,display))');
|
28
|
+
useEffect(() => {
|
29
|
+
// Whenever person uuid changes and not equal to other person, the other is reset to initial default value
|
30
|
+
if (observableresponsiblePersonUuid && observableresponsiblePersonUuid !== otherUser.uuid) {
|
31
|
+
form.resetField('responsiblePersonOther');
|
32
|
+
}
|
33
|
+
}, [observableresponsiblePersonUuid, form]);
|
34
|
+
|
35
|
+
if (isLoadingUser && observableresponsiblePersonUuid !== otherUser.uuid && observableresponsiblePersonUuid)
|
36
|
+
return <SelectSkeleton role="progressbar" />;
|
37
|
+
|
38
|
+
if (observableresponsiblePersonUuid && observableresponsiblePersonUuid !== otherUser.uuid && userError)
|
39
|
+
return (
|
40
|
+
<InlineNotification
|
41
|
+
lowContrast
|
42
|
+
title={t('responsiblePersonError', 'Error loading responsible person')}
|
43
|
+
subtitle={userError?.message}
|
44
|
+
/>
|
45
|
+
);
|
46
|
+
|
47
|
+
return (
|
48
|
+
<React.Fragment>
|
49
|
+
<Column>
|
50
|
+
<Controller
|
51
|
+
name={'responsiblePersonUuid'}
|
52
|
+
control={form.control}
|
53
|
+
render={({ field, fieldState: { error } }) => (
|
54
|
+
<ComboBox
|
55
|
+
readOnly={field.disabled}
|
56
|
+
titleText={t('responsiblePerson', 'Responsible Person')}
|
57
|
+
name={'responsiblePersonUuid'}
|
58
|
+
id={'responsiblePersonUuid'}
|
59
|
+
size={'xl'}
|
60
|
+
items={[...(userList || []), otherUser]}
|
61
|
+
onChange={(data: { selectedItem: User }) => {
|
62
|
+
field.onChange(data.selectedItem?.uuid);
|
63
|
+
}}
|
64
|
+
initialSelectedItem={
|
65
|
+
field.value
|
66
|
+
? field.value === otherUser.uuid
|
67
|
+
? otherUser
|
68
|
+
: data
|
69
|
+
: observableresponsiblePersonOther && !observableresponsiblePersonUuid
|
70
|
+
? otherUser
|
71
|
+
: ''
|
72
|
+
}
|
73
|
+
itemToString={(item) => item?.person?.display || ''}
|
74
|
+
onInputChange={debouncedSearch}
|
75
|
+
placeholder={t('filter', 'Filter ...')}
|
76
|
+
invalid={error?.message}
|
77
|
+
invalidText={error?.message}
|
78
|
+
ref={field.ref}
|
79
|
+
/>
|
80
|
+
)}
|
81
|
+
/>
|
82
|
+
</Column>
|
83
|
+
{(observableresponsiblePersonUuid === otherUser.uuid ||
|
84
|
+
(observableresponsiblePersonOther && !observableresponsiblePersonUuid)) && (
|
85
|
+
<Column>
|
86
|
+
<Controller
|
87
|
+
control={form.control}
|
88
|
+
name="responsiblePersonOther"
|
89
|
+
render={({ field, fieldState: { error } }) => (
|
90
|
+
<TextInput
|
91
|
+
{...field}
|
92
|
+
readOnly={field.disabled}
|
93
|
+
disabled={false}
|
94
|
+
id="responsiblePersonOther"
|
95
|
+
name="responsiblePersonOther"
|
96
|
+
size={'xl'}
|
97
|
+
labelText={t('responsiblePerson', 'Responsible Person')}
|
98
|
+
placeholder={t('pleaseSpecify', 'Please Specify')}
|
99
|
+
invalid={error?.message}
|
100
|
+
invalidText={error?.message}
|
101
|
+
/>
|
102
|
+
)}
|
103
|
+
/>
|
104
|
+
</Column>
|
105
|
+
)}
|
106
|
+
</React.Fragment>
|
107
|
+
// {isLoading && <InlineLoading status="active" iconDescription="Searching" description="Searching..." />}
|
108
|
+
);
|
109
|
+
};
|
110
|
+
|
111
|
+
export default UsersSelector;
|