@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,223 @@
|
|
1
|
+
import { render, waitFor, screen, fireEvent } from '@testing-library/react';
|
2
|
+
import { showSnackbar, useConfig, ErrorState, launchWorkspace } from '@openmrs/esm-framework';
|
3
|
+
import { useStockOperationTypes, useUser } from '../../stock-lookups/stock-lookups.resource';
|
4
|
+
import { getStockOperationLinks, useStockOperation } from '../stock-operations.resource';
|
5
|
+
import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
|
6
|
+
import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
|
7
|
+
import { useStockOperations } from '../stock-operations.resource';
|
8
|
+
import { closeOverlay } from '../../core/components/overlay/hook';
|
9
|
+
import StockOperationForm from './stock-operation-form.component';
|
10
|
+
import useParties from './hooks/useParties';
|
11
|
+
import userEvent from '@testing-library/user-event';
|
12
|
+
import { StockItemDTO } from '../../core/api/types/stockItem/StockItem';
|
13
|
+
import { useStockItem, useStockItems } from '../../stock-items/stock-items.resource';
|
14
|
+
import { initialStockOperationValue } from '../../core/utils/utils';
|
15
|
+
import { useForm, useFormContext, Controller, FormProvider } from 'react-hook-form';
|
16
|
+
import { BaseStockOperationItemFormData, StockOperationItemFormData } from '../validation-schema';
|
17
|
+
import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
18
|
+
import { useFilterableStockItems } from './hooks/useFilterableStockItems';
|
19
|
+
import { formatForDatePicker } from '../../constants';
|
20
|
+
import React from 'react';
|
21
|
+
import { receiptOperationTypeMock, returnOperationTypeMock, stockIssueOperationtypeMock } from '../../../__mocks__';
|
22
|
+
jest.mock('react-i18next', () => ({
|
23
|
+
useTranslation: jest.fn().mockReturnValue({ t: (key) => key }),
|
24
|
+
}));
|
25
|
+
|
26
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
27
|
+
ActionMenu: jest.fn(() => null),
|
28
|
+
showSnackbar: jest.fn(),
|
29
|
+
useDebounce: jest.fn((x) => x),
|
30
|
+
getGlobalStore: jest.fn(() => ({
|
31
|
+
getState: jest.fn(),
|
32
|
+
subscribe: jest.fn(),
|
33
|
+
setState: jest.fn(),
|
34
|
+
})),
|
35
|
+
parseDate: jest.fn((date) => new Date(date)),
|
36
|
+
showNotification: jest.fn(),
|
37
|
+
usePagination: jest.fn(() => ({ currentPage: 1, setPage: jest.fn() })),
|
38
|
+
useSession: jest.fn(() => ({ user: { display: 'Test User' } })),
|
39
|
+
useConfig: jest.fn(),
|
40
|
+
ErrorState: jest.fn(({ error }: { error: any }) => <div>{error}</div>),
|
41
|
+
launchWorkspace: jest.fn(),
|
42
|
+
}));
|
43
|
+
|
44
|
+
jest.mock('../../stock-lookups/stock-lookups.resource', () => ({
|
45
|
+
useStockOperationTypes: jest.fn(),
|
46
|
+
useUsers: jest.fn().mockReturnValue({ items: { results: [] }, isLoading: false }),
|
47
|
+
useUser: jest.fn().mockReturnValue({ data: { display: 'Test User' }, isLoading: false, error: null }),
|
48
|
+
}));
|
49
|
+
|
50
|
+
jest.mock('../stock-operations.resource', () => ({
|
51
|
+
operationStatusColor: jest.fn(() => 'some-color'),
|
52
|
+
getStockOperationLinks: jest.fn(),
|
53
|
+
useStockOperations: jest.fn().mockReturnValue({
|
54
|
+
items: { results: [] },
|
55
|
+
isLoading: false,
|
56
|
+
error: null,
|
57
|
+
}),
|
58
|
+
useStockOperation: jest.fn().mockReturnValue({
|
59
|
+
items: undefined,
|
60
|
+
isLoading: false,
|
61
|
+
error: null,
|
62
|
+
}),
|
63
|
+
}));
|
64
|
+
|
65
|
+
jest.mock('../../core/components/overlay/hook', () => ({
|
66
|
+
closeOverlay: jest.fn(),
|
67
|
+
}));
|
68
|
+
|
69
|
+
jest.mock('../../stock-items/stock-items.resource', () => ({
|
70
|
+
useStockItem: jest.fn(),
|
71
|
+
useStockItems: jest.fn().mockReturnValue({
|
72
|
+
isLoading: false,
|
73
|
+
error: null,
|
74
|
+
items: {},
|
75
|
+
}),
|
76
|
+
}));
|
77
|
+
jest.mock('./hooks/useFilterableStockItems', () => ({
|
78
|
+
useFilterableStockItems: jest.fn().mockReturnValue({
|
79
|
+
stockItemsList: [],
|
80
|
+
setLimit: jest.fn(),
|
81
|
+
setRepresentation: jest.fn(),
|
82
|
+
setSearchString: jest.fn(),
|
83
|
+
isLoading: false,
|
84
|
+
}),
|
85
|
+
}));
|
86
|
+
jest.mock('./hooks/useParties', () => jest.fn());
|
87
|
+
jest.mock('react-hook-form', () => ({
|
88
|
+
useForm: jest.fn().mockReturnValue({
|
89
|
+
watch: jest.fn(),
|
90
|
+
formState: {
|
91
|
+
errors: {},
|
92
|
+
},
|
93
|
+
resetField: jest.fn(),
|
94
|
+
getValues: jest.fn(),
|
95
|
+
setValue: jest.fn(),
|
96
|
+
handleSubmit: jest.fn(),
|
97
|
+
}),
|
98
|
+
useFormContext: jest.fn().mockReturnValue({
|
99
|
+
watch: jest.fn(),
|
100
|
+
formState: {
|
101
|
+
errors: {},
|
102
|
+
},
|
103
|
+
resetField: jest.fn(),
|
104
|
+
getValues: jest.fn(),
|
105
|
+
setValue: jest.fn(),
|
106
|
+
handleSubmit: jest.fn(),
|
107
|
+
}),
|
108
|
+
Controller: ({ render }) => render({ field: {}, fieldState: {} }),
|
109
|
+
FormProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
110
|
+
}));
|
111
|
+
|
112
|
+
jest.mock('../../stock-items/add-stock-item/batch-information/batch-information.resource', () => ({
|
113
|
+
useStockItemBatchInformationHook: jest.fn().mockReturnValue({
|
114
|
+
items: [],
|
115
|
+
totalCount: 0,
|
116
|
+
currentPage: 1,
|
117
|
+
currentPageSize: 10,
|
118
|
+
setCurrentPage: jest.fn(),
|
119
|
+
setPageSize: jest.fn(),
|
120
|
+
pageSizes: [],
|
121
|
+
isLoading: false,
|
122
|
+
error: undefined,
|
123
|
+
setSearchString: jest.fn(),
|
124
|
+
setStockItemUuid: jest.fn(),
|
125
|
+
setLocationUuid: jest.fn(),
|
126
|
+
setPartyUuid: jest.fn(),
|
127
|
+
setStockBatchUuid: jest.fn(),
|
128
|
+
}),
|
129
|
+
}));
|
130
|
+
|
131
|
+
describe('Stock Operation form step 3 (stock submision)', () => {
|
132
|
+
beforeEach(() => {
|
133
|
+
const mockStockOperationTypes = { results: [] };
|
134
|
+
(useStockOperationTypes as jest.Mock).mockReturnValue(mockStockOperationTypes);
|
135
|
+
(useStockOperations as jest.Mock).mockReturnValue({ items: { results: [] }, isLoading: false, error: null });
|
136
|
+
(useConfig as jest.Mock).mockReturnValue({ autoPopulateResponsiblePerson: true });
|
137
|
+
(useParties as jest.Mock).mockReturnValue({
|
138
|
+
destinationParties: [],
|
139
|
+
sourceParties: [],
|
140
|
+
isLoading: false,
|
141
|
+
error: undefined,
|
142
|
+
sourceTags: [],
|
143
|
+
destinationTags: [],
|
144
|
+
});
|
145
|
+
});
|
146
|
+
|
147
|
+
it('should have previous btn and not next btn', async () => {
|
148
|
+
render(<StockOperationForm stockOperationType={receiptOperationTypeMock as any} />);
|
149
|
+
// MOVE TO STEP 2
|
150
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
151
|
+
// MOVE TO STEP3
|
152
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
153
|
+
|
154
|
+
expect(screen.queryByRole('button', { name: /Next/i })).not.toBeInTheDocument();
|
155
|
+
expect(screen.getByRole('button', { name: /previous/i })).toBeInTheDocument();
|
156
|
+
});
|
157
|
+
|
158
|
+
it('should render require approval radio button and save button', async () => {
|
159
|
+
render(<StockOperationForm stockOperationType={receiptOperationTypeMock as any} />);
|
160
|
+
// MOVE TO STEP 2
|
161
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
162
|
+
// MOVE TO STEP3
|
163
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
164
|
+
|
165
|
+
// Shouls have save button
|
166
|
+
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
|
167
|
+
expect(screen.getAllByRole('radio', { name: /yes|no/i })).toHaveLength(2);
|
168
|
+
});
|
169
|
+
it('should render submitForReview button when require aprroval radion button is checked yes', async () => {
|
170
|
+
render(<StockOperationForm stockOperationType={receiptOperationTypeMock as any} />);
|
171
|
+
// MOVE TO STEP 2
|
172
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
173
|
+
// MOVE TO STEP3
|
174
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
175
|
+
|
176
|
+
const yesRadioButton = screen.getByRole('radio', { name: /yes/i });
|
177
|
+
expect(yesRadioButton).toBeInTheDocument();
|
178
|
+
// Submit for review shouldnt be in doc
|
179
|
+
expect(screen.queryByRole('button', { name: /submitForReview/i })).not.toBeInTheDocument();
|
180
|
+
await userEvent.click(yesRadioButton);
|
181
|
+
// On require aprooval should now show
|
182
|
+
expect(screen.getByRole('button', { name: /submitForReview/i })).toBeInTheDocument();
|
183
|
+
});
|
184
|
+
it('should render complete button when require aprroval radion button is checked no', async () => {
|
185
|
+
render(<StockOperationForm stockOperationType={receiptOperationTypeMock as any} />);
|
186
|
+
// MOVE TO STEP 2
|
187
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
188
|
+
// MOVE TO STEP3
|
189
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
190
|
+
|
191
|
+
const noRadioButton = screen.getByRole('radio', { name: /no/i });
|
192
|
+
expect(noRadioButton).toBeInTheDocument();
|
193
|
+
await userEvent.click(noRadioButton);
|
194
|
+
// On require aprooval should now show complete btn
|
195
|
+
expect(screen.getByRole('button', { name: /complete/i })).toBeInTheDocument();
|
196
|
+
});
|
197
|
+
it('should render dispatch btn for stock return operation and dont require aproval', async () => {
|
198
|
+
render(<StockOperationForm stockOperationType={returnOperationTypeMock as any} />);
|
199
|
+
// MOVE TO STEP 2
|
200
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
201
|
+
// MOVE TO STEP3
|
202
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
203
|
+
|
204
|
+
const noRadioButton = screen.getByRole('radio', { name: /no/i });
|
205
|
+
expect(noRadioButton).toBeInTheDocument();
|
206
|
+
await userEvent.click(noRadioButton);
|
207
|
+
// On require aprooval should now show complete btn
|
208
|
+
expect(screen.getByRole('button', { name: /dispatch/i })).toBeInTheDocument();
|
209
|
+
});
|
210
|
+
it('should render dispatch btn for stock issue operation and dont require aproval', async () => {
|
211
|
+
render(<StockOperationForm stockOperationType={stockIssueOperationtypeMock as any} />);
|
212
|
+
// MOVE TO STEP 2
|
213
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
214
|
+
// MOVE TO STEP3
|
215
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/i }));
|
216
|
+
|
217
|
+
const noRadioButton = screen.getByRole('radio', { name: /no/i });
|
218
|
+
expect(noRadioButton).toBeInTheDocument();
|
219
|
+
await userEvent.click(noRadioButton);
|
220
|
+
// On require aprooval should now show complete btn
|
221
|
+
expect(screen.getByRole('button', { name: /dispatch/i })).toBeInTheDocument();
|
222
|
+
});
|
223
|
+
});
|
package/src/stock-operations/stock-operations-forms/steps/base-operation-details-form-step.tsx
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
import { Button, Column, ComboBox, DatePicker, DatePickerInput, InlineLoading, Stack, TextArea } from '@carbon/react';
|
2
|
+
import { ErrorState } from '@openmrs/esm-framework';
|
3
|
+
import React, { ChangeEvent, FC, useEffect, useMemo } from 'react';
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
import { DATE_PICKER_CONTROL_FORMAT, DATE_PICKER_FORMAT, MAIN_STORE_LOCATION_TAG } from '../../../constants';
|
7
|
+
import { Party } from '../../../core/api/types/Party';
|
8
|
+
import { StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
|
9
|
+
import { OperationType, StockOperationType } from '../../../core/api/types/stockOperation/StockOperationType';
|
10
|
+
import { StockOperationItemDtoSchema } from '../../validation-schema';
|
11
|
+
import useOperationTypePermisions from '../hooks/useOperationTypePermisions';
|
12
|
+
import useParties from '../hooks/useParties';
|
13
|
+
import StockOperationReasonSelector from '../input-components/stock-operation-reason-selector.component';
|
14
|
+
import UsersSelector from '../input-components/users-selector.component';
|
15
|
+
import styles from '../stock-operation-form.scss';
|
16
|
+
import { TextInput } from '@carbon/react';
|
17
|
+
|
18
|
+
type BaseOperationDetailsFormStepProps = {
|
19
|
+
stockOperation?: StockOperationDTO;
|
20
|
+
stockOperationType: StockOperationType;
|
21
|
+
onNext?: () => void;
|
22
|
+
};
|
23
|
+
|
24
|
+
const BaseOperationDetailsFormStep: FC<BaseOperationDetailsFormStepProps> = ({
|
25
|
+
stockOperation,
|
26
|
+
stockOperationType,
|
27
|
+
onNext,
|
28
|
+
}) => {
|
29
|
+
const { t } = useTranslation();
|
30
|
+
const operationTypePermision = useOperationTypePermisions(stockOperationType);
|
31
|
+
const {
|
32
|
+
destinationParties,
|
33
|
+
sourceParties,
|
34
|
+
isLoading: isPartiesLoading,
|
35
|
+
error: patiesError,
|
36
|
+
sourceTags,
|
37
|
+
destinationTags,
|
38
|
+
} = useParties(stockOperationType);
|
39
|
+
const form = useFormContext<StockOperationItemDtoSchema>();
|
40
|
+
const isStockIssueOperation = useMemo(
|
41
|
+
() => OperationType.STOCK_ISSUE_OPERATION_TYPE === stockOperationType.operationType,
|
42
|
+
[stockOperationType],
|
43
|
+
);
|
44
|
+
// initialize location fields
|
45
|
+
useEffect(() => {
|
46
|
+
// Prefill default locaton with current location if is a new operation
|
47
|
+
if (!stockOperation) {
|
48
|
+
if (stockOperationType?.hasSource) {
|
49
|
+
const shouldLockSource = sourceTags.length === 1 && sourceTags[0] === MAIN_STORE_LOCATION_TAG;
|
50
|
+
if (shouldLockSource && sourceParties?.length) {
|
51
|
+
const party = sourceParties[0];
|
52
|
+
form.setValue('sourceUuid', party.uuid);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
if (stockOperationType?.hasDestination) {
|
57
|
+
const shouldLockDestination = destinationTags.length === 1 && destinationTags[0] === MAIN_STORE_LOCATION_TAG;
|
58
|
+
if (shouldLockDestination && destinationParties?.length) {
|
59
|
+
const party = destinationParties[0];
|
60
|
+
form.setValue('destinationUuid', party.uuid);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}, [sourceParties, destinationParties, stockOperation, stockOperationType, form, destinationTags, sourceTags]);
|
65
|
+
|
66
|
+
if (isPartiesLoading)
|
67
|
+
return (
|
68
|
+
<InlineLoading
|
69
|
+
status="active"
|
70
|
+
role="progressbar"
|
71
|
+
iconDescription="Loading"
|
72
|
+
description={t('loadingData', 'Loading data...')}
|
73
|
+
/>
|
74
|
+
);
|
75
|
+
|
76
|
+
if (patiesError)
|
77
|
+
return (
|
78
|
+
<ErrorState error={patiesError} headerTitle={t('partieserror', 'Error launching base operation details form')} />
|
79
|
+
);
|
80
|
+
|
81
|
+
return (
|
82
|
+
<Stack gap={4} className={styles.grid}>
|
83
|
+
<div className={styles.heading}>
|
84
|
+
<h4>{`${stockOperationType.name} ${t('details', 'Details')}`}</h4>
|
85
|
+
<div className={styles.btnSet}>
|
86
|
+
{typeof onNext === 'function' && (
|
87
|
+
<Button kind="primary" onClick={onNext} role="button">
|
88
|
+
{t('next', 'Next')}
|
89
|
+
</Button>
|
90
|
+
)}
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
<Column>
|
94
|
+
<Controller
|
95
|
+
control={form.control}
|
96
|
+
name="operationDate"
|
97
|
+
render={({ field, fieldState: { error } }) => (
|
98
|
+
<DatePicker
|
99
|
+
readOnly={field.disabled}
|
100
|
+
id={`operationDate`}
|
101
|
+
datePickerType="single"
|
102
|
+
locale="en"
|
103
|
+
className={styles.datePickerInput}
|
104
|
+
{...field}
|
105
|
+
value={field.value}
|
106
|
+
dateFormat={DATE_PICKER_CONTROL_FORMAT}
|
107
|
+
onChange={([newDate]) => {
|
108
|
+
field.onChange(newDate);
|
109
|
+
}}
|
110
|
+
>
|
111
|
+
<DatePickerInput
|
112
|
+
autoComplete="off"
|
113
|
+
id={`operationDate-input`}
|
114
|
+
name="operationDate-input"
|
115
|
+
placeholder={DATE_PICKER_FORMAT}
|
116
|
+
labelText={t('operationDate', 'Operation Date')}
|
117
|
+
invalid={error?.message}
|
118
|
+
invalidText={error?.message}
|
119
|
+
size="xl"
|
120
|
+
/>
|
121
|
+
</DatePicker>
|
122
|
+
)}
|
123
|
+
/>
|
124
|
+
</Column>
|
125
|
+
{stockOperation?.operationNumber && (
|
126
|
+
<TextInput
|
127
|
+
id="operationNoLbl"
|
128
|
+
value={stockOperation?.operationNumber}
|
129
|
+
readOnly={true}
|
130
|
+
labelText={t('operationNumber', 'Operation Number')}
|
131
|
+
/>
|
132
|
+
)}
|
133
|
+
<Column>
|
134
|
+
<Controller
|
135
|
+
control={form.control}
|
136
|
+
name="sourceUuid"
|
137
|
+
render={({ field, fieldState: { error } }) => (
|
138
|
+
<ComboBox
|
139
|
+
titleText={
|
140
|
+
isStockIssueOperation
|
141
|
+
? t('destination', 'destination')
|
142
|
+
: stockOperationType?.hasDestination || stockOperation?.destinationUuid
|
143
|
+
? t('from', 'From')
|
144
|
+
: t('location', 'Location')
|
145
|
+
}
|
146
|
+
readOnly={field.disabled}
|
147
|
+
name={'sourceUuid'}
|
148
|
+
id={'sourceUuid'}
|
149
|
+
size={'xl'}
|
150
|
+
items={sourceParties}
|
151
|
+
onChange={(data: { selectedItem: Party }) => {
|
152
|
+
field.onChange(data.selectedItem?.uuid);
|
153
|
+
}}
|
154
|
+
initialSelectedItem={sourceParties.find((p) => p.uuid === field.value)}
|
155
|
+
selectedItem={sourceParties.find((p) => p.uuid === field.value)}
|
156
|
+
itemToString={(item?: Party) => (item && item?.name ? `${item?.name}` : '')}
|
157
|
+
shouldFilterItem={() => true}
|
158
|
+
placeholder={
|
159
|
+
stockOperationType.hasDestination || stockOperation?.destinationUuid
|
160
|
+
? t('chooseASource', 'Choose a source')
|
161
|
+
: t('chooseALocation', 'Choose a location')
|
162
|
+
}
|
163
|
+
ref={field.ref}
|
164
|
+
invalid={error?.message}
|
165
|
+
invalidText={error?.message}
|
166
|
+
/>
|
167
|
+
)}
|
168
|
+
/>
|
169
|
+
</Column>
|
170
|
+
{(stockOperationType.hasDestination || stockOperation?.destinationUuid) && (
|
171
|
+
<Column>
|
172
|
+
<Controller
|
173
|
+
control={form.control}
|
174
|
+
name="destinationUuid"
|
175
|
+
render={({ field, fieldState: { error } }) => (
|
176
|
+
<ComboBox
|
177
|
+
readOnly={field.disabled}
|
178
|
+
titleText={
|
179
|
+
isStockIssueOperation
|
180
|
+
? t('source', 'Source')
|
181
|
+
: stockOperationType?.hasSource || stockOperation?.atLocationUuid
|
182
|
+
? t('to', 'To')
|
183
|
+
: t('location', 'Location')
|
184
|
+
}
|
185
|
+
name={'destinationUuid'}
|
186
|
+
id={'destinationUuid'}
|
187
|
+
size={'xl'}
|
188
|
+
items={destinationParties}
|
189
|
+
onChange={(data: { selectedItem: Party }) => {
|
190
|
+
field.onChange(data.selectedItem?.uuid);
|
191
|
+
}}
|
192
|
+
initialSelectedItem={destinationParties.find((p) => p.uuid === field.value)}
|
193
|
+
selectedItem={destinationParties.find((p) => p.uuid === field.value)}
|
194
|
+
itemToString={(item?: Party) => (item && item?.name ? `${item?.name}` : '')}
|
195
|
+
shouldFilterItem={() => true}
|
196
|
+
placeholder={
|
197
|
+
stockOperationType?.hasSource || stockOperation?.atLocationUuid
|
198
|
+
? t('chooseADestination', 'Choose a destination')
|
199
|
+
: t('location', 'Location')
|
200
|
+
}
|
201
|
+
ref={field.ref}
|
202
|
+
invalid={error?.message}
|
203
|
+
invalidText={error?.message}
|
204
|
+
/>
|
205
|
+
)}
|
206
|
+
/>
|
207
|
+
</Column>
|
208
|
+
)}
|
209
|
+
<UsersSelector />
|
210
|
+
{operationTypePermision.requiresStockAdjustmentReason && (
|
211
|
+
<Column>
|
212
|
+
<StockOperationReasonSelector />
|
213
|
+
</Column>
|
214
|
+
)}
|
215
|
+
<Column>
|
216
|
+
<Controller
|
217
|
+
control={form.control}
|
218
|
+
name="remarks"
|
219
|
+
render={({ field, fieldState: { error } }) => (
|
220
|
+
<TextArea
|
221
|
+
{...field}
|
222
|
+
readOnly={field.disabled}
|
223
|
+
disabled={false}
|
224
|
+
maxCount={250}
|
225
|
+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
|
226
|
+
field.onChange(e.target.value);
|
227
|
+
}}
|
228
|
+
placeholder={t('enterRemarks', 'Enter remarks') + ' ...'}
|
229
|
+
id={'remarks'}
|
230
|
+
labelText={t('remarks', 'Remarks')}
|
231
|
+
invalid={error?.message}
|
232
|
+
invalidText={error?.message}
|
233
|
+
/>
|
234
|
+
)}
|
235
|
+
/>
|
236
|
+
</Column>
|
237
|
+
</Stack>
|
238
|
+
);
|
239
|
+
};
|
240
|
+
|
241
|
+
export default BaseOperationDetailsFormStep;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import React, { useEffect, useMemo } from 'react';
|
2
|
+
import { useStockItem } from '../../../stock-items/stock-items.resource';
|
3
|
+
import { InlineLoading } from '@carbon/react';
|
4
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
|
7
|
+
type QuantityUomCellProps = {
|
8
|
+
stockItemPackagingUOMUuid: string;
|
9
|
+
stockItemUuid: string;
|
10
|
+
};
|
11
|
+
|
12
|
+
const QuantityUomCell: React.FC<QuantityUomCellProps> = ({ stockItemPackagingUOMUuid, stockItemUuid }) => {
|
13
|
+
const { isLoading, error, item } = useStockItem(stockItemUuid);
|
14
|
+
const { t } = useTranslation();
|
15
|
+
const uomName = useMemo(() => {
|
16
|
+
return item?.packagingUnits?.find((unit) => unit.uuid === stockItemPackagingUOMUuid)?.packagingUomName;
|
17
|
+
}, [item, stockItemPackagingUOMUuid]);
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
if (error) {
|
21
|
+
showSnackbar({
|
22
|
+
kind: 'error',
|
23
|
+
title: t('packagingUomError', 'Error loading stockItemPackagingUOM name'),
|
24
|
+
subtitle: error?.message,
|
25
|
+
});
|
26
|
+
}
|
27
|
+
}, [error, t]);
|
28
|
+
if (isLoading) return <InlineLoading status="active" iconDescription="Loading" />;
|
29
|
+
if (error) return <>--</>;
|
30
|
+
return <>{uomName}</>;
|
31
|
+
};
|
32
|
+
|
33
|
+
export default QuantityUomCell;
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useTranslation } from 'react-i18next';
|
3
|
+
import { StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
|
4
|
+
import {
|
5
|
+
DataTable,
|
6
|
+
Table,
|
7
|
+
TableBody,
|
8
|
+
TableContainer,
|
9
|
+
TableHead,
|
10
|
+
TableHeader,
|
11
|
+
TableRow,
|
12
|
+
TableCell,
|
13
|
+
DataTableSkeleton,
|
14
|
+
Button,
|
15
|
+
} from '@carbon/react';
|
16
|
+
import styles from './stock-operation-items-form-step.scc.scss';
|
17
|
+
|
18
|
+
const formatDate = (date: Date | string | null) => {
|
19
|
+
if (!date) return ' ';
|
20
|
+
const d = new Date(date);
|
21
|
+
const day = String(d.getDate()).padStart(2, '0');
|
22
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
23
|
+
const year = d.getFullYear();
|
24
|
+
return `${month}/${day}/${year}`;
|
25
|
+
};
|
26
|
+
|
27
|
+
interface ReceivedItemsProps {
|
28
|
+
stockOperation?: StockOperationDTO;
|
29
|
+
onPrevious?: () => void;
|
30
|
+
}
|
31
|
+
|
32
|
+
const ReceivedItems: React.FC<ReceivedItemsProps> = ({ stockOperation, onPrevious }) => {
|
33
|
+
const { t } = useTranslation();
|
34
|
+
|
35
|
+
const headers = [
|
36
|
+
{ key: 'item', header: t('item', 'Item') },
|
37
|
+
{ key: 'requested', header: t('requested', 'Requested') },
|
38
|
+
{ key: 'batch', header: t('batch', 'Batch No') },
|
39
|
+
{ key: 'expiry', header: t('expiry', 'Expiry Date') },
|
40
|
+
{ key: 'qtySent', header: t('quantitySent', 'Quantity Sent') },
|
41
|
+
{ key: 'qtyReceived', header: t('quantityReceived', 'Quantity Received') },
|
42
|
+
{
|
43
|
+
key: 'qtyUoM',
|
44
|
+
header: t('quantityUoM', 'Quantity Unit of Measurement(UoM)'),
|
45
|
+
},
|
46
|
+
];
|
47
|
+
|
48
|
+
const rows =
|
49
|
+
stockOperation?.stockOperationItems?.map((item) => ({
|
50
|
+
id: item.uuid,
|
51
|
+
item: item.stockItemName,
|
52
|
+
requested: item.quantityRequested || ' ',
|
53
|
+
batch: item.batchNo,
|
54
|
+
expiry: formatDate(item.expiration),
|
55
|
+
qtySent: item.quantity || ' ',
|
56
|
+
qtyReceived: item.quantityReceived || ' ',
|
57
|
+
qtyUoM: item.quantityReceivedPackagingUOMName,
|
58
|
+
})) || [];
|
59
|
+
|
60
|
+
if (!stockOperation) {
|
61
|
+
return <DataTableSkeleton role="progressbar" />;
|
62
|
+
}
|
63
|
+
|
64
|
+
const headerTitle = t('receivedItems', 'Received Items');
|
65
|
+
|
66
|
+
return (
|
67
|
+
<div style={{ margin: '10px' }}>
|
68
|
+
<div className={styles.tableContainer}>
|
69
|
+
<div className={styles.heading}>
|
70
|
+
<h4>{headerTitle}</h4>
|
71
|
+
<div className={styles.btnSet}>
|
72
|
+
{typeof onPrevious === 'function' && (
|
73
|
+
<Button kind="secondary" onClick={onPrevious}>
|
74
|
+
{t('previous', 'Previous')}
|
75
|
+
</Button>
|
76
|
+
)}
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
<DataTable rows={rows} headers={headers}>
|
80
|
+
{({ rows, headers, getHeaderProps, getTableProps, getRowProps }) => (
|
81
|
+
<TableContainer>
|
82
|
+
<Table {...getTableProps()}>
|
83
|
+
<TableHead>
|
84
|
+
<TableRow>
|
85
|
+
{headers.map((header) => (
|
86
|
+
<TableHeader {...getHeaderProps({ header })} key={header.key}>
|
87
|
+
{header.header}
|
88
|
+
</TableHeader>
|
89
|
+
))}
|
90
|
+
</TableRow>
|
91
|
+
</TableHead>
|
92
|
+
<TableBody>
|
93
|
+
{rows.map((row) => (
|
94
|
+
<TableRow {...getRowProps({ row })} key={row.id}>
|
95
|
+
{row.cells.map((cell) => (
|
96
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
97
|
+
))}
|
98
|
+
</TableRow>
|
99
|
+
))}
|
100
|
+
</TableBody>
|
101
|
+
</Table>
|
102
|
+
</TableContainer>
|
103
|
+
)}
|
104
|
+
</DataTable>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
);
|
108
|
+
};
|
109
|
+
|
110
|
+
export default ReceivedItems;
|
package/src/stock-operations/stock-operations-forms/steps/stock-availability-cell.component.tsx
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import { InlineLoading } from '@carbon/react';
|
2
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
3
|
+
import React, { useEffect, useMemo } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
|
+
import styles from './stock-operation-items-form-step.scc.scss';
|
7
|
+
const StockAvailability: React.FC<{ stockItemUuid: string }> = ({ stockItemUuid }) => {
|
8
|
+
const { items, isLoading, error } = useStockItemBatchInformationHook({
|
9
|
+
stockItemUuid: stockItemUuid,
|
10
|
+
includeBatchNo: true,
|
11
|
+
});
|
12
|
+
const { t } = useTranslation();
|
13
|
+
|
14
|
+
const totalQuantity = useMemo(() => {
|
15
|
+
if (!items?.length) return 0;
|
16
|
+
return items.reduce((total, batch) => {
|
17
|
+
return total + (Number(batch.quantity) || 0);
|
18
|
+
}, 0);
|
19
|
+
}, [items]);
|
20
|
+
const commonUOM = useMemo(() => {
|
21
|
+
if (!items?.length) return '';
|
22
|
+
return items[0]?.quantityUoM || '';
|
23
|
+
}, [items]);
|
24
|
+
|
25
|
+
useEffect(() => {
|
26
|
+
if (error) {
|
27
|
+
showSnackbar({
|
28
|
+
kind: 'error',
|
29
|
+
title: t('stockAvailabilityError', 'Error loading stock availability'),
|
30
|
+
subtitle: error?.message,
|
31
|
+
});
|
32
|
+
}
|
33
|
+
}, [error, t]);
|
34
|
+
|
35
|
+
if (isLoading) return <InlineLoading status="active" iconDescription="Loading" />;
|
36
|
+
if (error) return <>--</>;
|
37
|
+
|
38
|
+
return (
|
39
|
+
<div className={styles.availability}>
|
40
|
+
{totalQuantity > 0 ? (
|
41
|
+
<span>
|
42
|
+
Available: {totalQuantity.toLocaleString()} {commonUOM}
|
43
|
+
</span>
|
44
|
+
) : (
|
45
|
+
<span className={styles.outOfStock}>Out of Stock</span>
|
46
|
+
)}
|
47
|
+
</div>
|
48
|
+
);
|
49
|
+
};
|
50
|
+
|
51
|
+
export default StockAvailability;
|