@openmrs/esm-stock-management-app 1.0.1-pre.777 → 1.0.1-pre.785
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/{400.js → 914.js} +1 -1
- package/dist/914.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 +81 -57
- package/dist/openmrs-esm-stock-management-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- 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/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +41 -16
- package/src/stock-operations/{add-stock-operation/received-items.component.tsx → received-items.component.tsx} +1 -1
- 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 +56 -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/stock-operations-forms/input-components/unique-batch-no-entry-input.component.tsx +59 -0
- 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 +250 -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/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 +38 -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 +236 -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 +197 -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 +200 -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 +20 -56
- package/src/stock-operations/stock-operations.resource.ts +16 -13
- package/src/stock-operations/validation-schema.ts +72 -14
- package/dist/400.js.map +0 -1
- package/dist/766.js +0 -2
- package/dist/766.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/stock-item-search/stock-item-search.component.tsx +0 -70
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.component.tsx +0 -360
- 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
@@ -1,189 +0,0 @@
|
|
1
|
-
import React, { useState } from 'react';
|
2
|
-
import { useTranslation } from 'react-i18next';
|
3
|
-
import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
|
4
|
-
import { SaveStockOperation, SaveStockOperationAction } from '../../stock-items/types';
|
5
|
-
import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
|
6
|
-
import { InitializeResult } from './types';
|
7
|
-
import { Button, InlineLoading, RadioButton, RadioButtonGroup, TextInput } from '@carbon/react';
|
8
|
-
import { Departure, ListChecked, Save, SendFilled, Undo } from '@carbon/react/icons';
|
9
|
-
|
10
|
-
interface StockOperationSubmissionProps {
|
11
|
-
isEditing?: boolean;
|
12
|
-
model?: StockOperationDTO;
|
13
|
-
onSave?: SaveStockOperation;
|
14
|
-
operation: StockOperationType;
|
15
|
-
setup: InitializeResult;
|
16
|
-
canEdit?: boolean;
|
17
|
-
locked?: boolean;
|
18
|
-
requiresDispatchAcknowledgement?: boolean;
|
19
|
-
actions: {
|
20
|
-
onGoBack: () => void;
|
21
|
-
onSave?: SaveStockOperation;
|
22
|
-
onComplete: SaveStockOperationAction;
|
23
|
-
onSubmit: SaveStockOperationAction;
|
24
|
-
onDispatch: SaveStockOperationAction;
|
25
|
-
};
|
26
|
-
}
|
27
|
-
|
28
|
-
const StockOperationSubmission: React.FC<StockOperationSubmissionProps> = ({
|
29
|
-
canEdit,
|
30
|
-
locked,
|
31
|
-
model,
|
32
|
-
requiresDispatchAcknowledgement,
|
33
|
-
actions,
|
34
|
-
isEditing,
|
35
|
-
}) => {
|
36
|
-
const { t } = useTranslation();
|
37
|
-
const [isSaving, setIsSaving] = useState(false);
|
38
|
-
const [approvalRequired, setApprovalRequired] = useState<boolean | null>(model?.approvalRequired);
|
39
|
-
|
40
|
-
const handleRadioButtonChange = (selectedItem: boolean) => {
|
41
|
-
setApprovalRequired(selectedItem);
|
42
|
-
};
|
43
|
-
|
44
|
-
return (
|
45
|
-
<div style={{ margin: '10px' }}>
|
46
|
-
{canEdit && !locked && (
|
47
|
-
<div style={{ margin: '10px' }}>
|
48
|
-
<RadioButtonGroup
|
49
|
-
name="rbgApprovelRequired"
|
50
|
-
legendText={t('doesThisTransactionRequireApproval', 'Does the transaction require approval ?')}
|
51
|
-
onChange={handleRadioButtonChange}
|
52
|
-
>
|
53
|
-
<RadioButton value={true} id="rbgApprovelRequired-true" labelText={t('yes', 'Yes')} />
|
54
|
-
<RadioButton value={false} id="rbgApprovelRequired-false" labelText={t('no', 'No')} />
|
55
|
-
</RadioButtonGroup>
|
56
|
-
</div>
|
57
|
-
)}
|
58
|
-
{!canEdit && (
|
59
|
-
<>
|
60
|
-
<TextInput
|
61
|
-
style={{ margin: '5px' }}
|
62
|
-
id="rbgApproveRequiredLbl"
|
63
|
-
value={approvalRequired ? t('yes', 'Yes') : t('no', 'No')}
|
64
|
-
readOnly={true}
|
65
|
-
labelText={t('doesThisTransactionRequireApproval', 'Does the transaction require approval ?')}
|
66
|
-
/>
|
67
|
-
</>
|
68
|
-
)}
|
69
|
-
|
70
|
-
{canEdit && !locked && (
|
71
|
-
<div className="stkpg-form-buttons" style={{ margin: '10px' }}>
|
72
|
-
{approvalRequired != null && (
|
73
|
-
<>
|
74
|
-
{!requiresDispatchAcknowledgement && !approvalRequired && (
|
75
|
-
<Button
|
76
|
-
name="complete"
|
77
|
-
type="button"
|
78
|
-
style={{ margin: '4px' }}
|
79
|
-
className="submitButton"
|
80
|
-
kind="primary"
|
81
|
-
onClick={async () => {
|
82
|
-
delete model?.dateCreated;
|
83
|
-
setIsSaving(true);
|
84
|
-
if (!isEditing) {
|
85
|
-
delete model.status;
|
86
|
-
await actions.onSave(model);
|
87
|
-
setIsSaving(false);
|
88
|
-
}
|
89
|
-
model.status = 'COMPLETED';
|
90
|
-
actions.onComplete(model);
|
91
|
-
setIsSaving(false);
|
92
|
-
}}
|
93
|
-
renderIcon={ListChecked}
|
94
|
-
>
|
95
|
-
{t('complete', 'Complete')}
|
96
|
-
</Button>
|
97
|
-
)}
|
98
|
-
{requiresDispatchAcknowledgement && !approvalRequired && (
|
99
|
-
<Button
|
100
|
-
name="dispatch"
|
101
|
-
type="button"
|
102
|
-
style={{ margin: '4px' }}
|
103
|
-
className="submitButton"
|
104
|
-
kind="primary"
|
105
|
-
onClick={async () => {
|
106
|
-
delete model?.dateCreated;
|
107
|
-
delete model?.status;
|
108
|
-
setIsSaving(true);
|
109
|
-
await actions.onSave(model).then(() => {
|
110
|
-
model.status = 'DISPATCHED';
|
111
|
-
actions.onDispatch(model);
|
112
|
-
setIsSaving(false);
|
113
|
-
});
|
114
|
-
}}
|
115
|
-
renderIcon={Departure}
|
116
|
-
>
|
117
|
-
{isSaving ? (
|
118
|
-
<InlineLoading description={t('dispatching', 'Dispatching')} />
|
119
|
-
) : (
|
120
|
-
t('dispatch', 'Dispatch')
|
121
|
-
)}
|
122
|
-
</Button>
|
123
|
-
)}
|
124
|
-
{approvalRequired && (
|
125
|
-
<Button
|
126
|
-
name="submit"
|
127
|
-
type="button"
|
128
|
-
style={{ margin: '4px' }}
|
129
|
-
className="submitButton"
|
130
|
-
kind="primary"
|
131
|
-
onClick={async () => {
|
132
|
-
delete model?.dateCreated;
|
133
|
-
delete model?.status;
|
134
|
-
setIsSaving(true);
|
135
|
-
await actions.onSave(model).then(() => {
|
136
|
-
model.status = 'SUBMITTED';
|
137
|
-
actions.onSubmit(model);
|
138
|
-
setIsSaving(false);
|
139
|
-
});
|
140
|
-
}}
|
141
|
-
renderIcon={SendFilled}
|
142
|
-
>
|
143
|
-
{isSaving ? (
|
144
|
-
<InlineLoading description={t('submittingForReview', 'Submitting for review')} />
|
145
|
-
) : (
|
146
|
-
t('submitForReview', 'Submit For Review')
|
147
|
-
)}
|
148
|
-
</Button>
|
149
|
-
)}
|
150
|
-
</>
|
151
|
-
)}
|
152
|
-
<Button
|
153
|
-
name="save"
|
154
|
-
type="button"
|
155
|
-
className="submitButton"
|
156
|
-
style={{ margin: '4px' }}
|
157
|
-
disabled={isSaving}
|
158
|
-
onClick={async () => {
|
159
|
-
delete model?.dateCreated;
|
160
|
-
delete model?.status;
|
161
|
-
setIsSaving(true);
|
162
|
-
model.approvalRequired = approvalRequired ? true : false;
|
163
|
-
await actions.onSave(model);
|
164
|
-
setIsSaving(false);
|
165
|
-
}}
|
166
|
-
kind="secondary"
|
167
|
-
renderIcon={Save}
|
168
|
-
>
|
169
|
-
{isSaving ? <InlineLoading /> : t('save', 'Save')}
|
170
|
-
</Button>
|
171
|
-
{!isSaving && (
|
172
|
-
<Button
|
173
|
-
type="button"
|
174
|
-
style={{ margin: '4px' }}
|
175
|
-
className="cancelButton"
|
176
|
-
kind="tertiary"
|
177
|
-
onClick={actions.onGoBack}
|
178
|
-
renderIcon={Undo}
|
179
|
-
>
|
180
|
-
{t('goBack', 'Go Back')}
|
181
|
-
</Button>
|
182
|
-
)}
|
183
|
-
</div>
|
184
|
-
)}
|
185
|
-
</div>
|
186
|
-
);
|
187
|
-
};
|
188
|
-
|
189
|
-
export default StockOperationSubmission;
|
@@ -1,138 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { render, waitFor, screen } from '@testing-library/react';
|
3
|
-
import StockOperationSubmission from './stock-operation-submission.component';
|
4
|
-
import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
|
5
|
-
import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
|
6
|
-
import { InitializeResult } from './types';
|
7
|
-
import userEvent from '@testing-library/user-event';
|
8
|
-
|
9
|
-
const mockOnGoBack = jest.fn();
|
10
|
-
const mockOnSave = jest.fn();
|
11
|
-
const mockOnComplete = jest.fn();
|
12
|
-
const mockOnSubmit = jest.fn();
|
13
|
-
const mockOnDispatch = jest.fn();
|
14
|
-
const defaultProps = {
|
15
|
-
canEdit: true,
|
16
|
-
locked: false,
|
17
|
-
model: { approvalRequired: null } as StockOperationDTO,
|
18
|
-
operation: {
|
19
|
-
name: 'Stock Issue',
|
20
|
-
description: 'Issuing stock',
|
21
|
-
operationType: 'stockissue',
|
22
|
-
hasSource: true,
|
23
|
-
sourceType: {},
|
24
|
-
hasDestination: true,
|
25
|
-
destinationType: {},
|
26
|
-
hasRecipient: false,
|
27
|
-
recipientRequired: false,
|
28
|
-
availableWhenReserved: false,
|
29
|
-
allowExpiredBatchNumbers: false,
|
30
|
-
stockOperationTypeLocationScopes: [],
|
31
|
-
} as StockOperationType,
|
32
|
-
setup: {
|
33
|
-
isNegativeQuantityAllowed: false,
|
34
|
-
requiresBatchUuid: false,
|
35
|
-
requiresActualBatchInfo: false,
|
36
|
-
isQuantityOptional: false,
|
37
|
-
} as InitializeResult,
|
38
|
-
actions: {
|
39
|
-
onGoBack: mockOnGoBack,
|
40
|
-
onSave: mockOnSave,
|
41
|
-
onComplete: mockOnComplete,
|
42
|
-
onSubmit: mockOnSubmit,
|
43
|
-
onDispatch: mockOnDispatch,
|
44
|
-
},
|
45
|
-
};
|
46
|
-
|
47
|
-
describe('StockOperationSubmission', () => {
|
48
|
-
let user;
|
49
|
-
|
50
|
-
beforeEach(() => {
|
51
|
-
jest.clearAllMocks();
|
52
|
-
user = userEvent.setup();
|
53
|
-
});
|
54
|
-
|
55
|
-
it('renders without crashing', () => {
|
56
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
57
|
-
expect(screen.getByText(/does the transaction require approval/i)).toBeInTheDocument();
|
58
|
-
});
|
59
|
-
|
60
|
-
it('allows approval required to be selected', async () => {
|
61
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
62
|
-
await user.click(screen.getByLabelText(/yes/i));
|
63
|
-
const yesRadioButton = screen.getByLabelText(/yes/i) as HTMLInputElement;
|
64
|
-
expect(yesRadioButton.checked).toBe(true);
|
65
|
-
});
|
66
|
-
|
67
|
-
it('allows approval not required to be selected', async () => {
|
68
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
69
|
-
await user.click(screen.getByLabelText(/no/i));
|
70
|
-
const noRadioButton = screen.getByLabelText(/no/i) as HTMLInputElement;
|
71
|
-
expect(noRadioButton.checked).toBe(true);
|
72
|
-
});
|
73
|
-
|
74
|
-
it('calls onGoBack when Go Back button is clicked', async () => {
|
75
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
76
|
-
await user.click(screen.getByText(/go back/i));
|
77
|
-
expect(mockOnGoBack).toHaveBeenCalled();
|
78
|
-
});
|
79
|
-
|
80
|
-
it('shows loading indicator when saving', async () => {
|
81
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
82
|
-
await user.click(screen.getByText(/save/i));
|
83
|
-
await waitFor(() => {
|
84
|
-
expect(mockOnSave).toHaveBeenCalled();
|
85
|
-
});
|
86
|
-
});
|
87
|
-
|
88
|
-
it('calls onSave when save button is clicked', async () => {
|
89
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
90
|
-
await user.click(screen.getByText(/save/i));
|
91
|
-
await waitFor(() => {
|
92
|
-
expect(mockOnSave).toHaveBeenCalled();
|
93
|
-
});
|
94
|
-
});
|
95
|
-
|
96
|
-
it('disables the save button while saving', async () => {
|
97
|
-
const mockOnSave = jest.fn<Promise<void>, [StockOperationDTO]>(
|
98
|
-
(model) => new Promise((resolve) => setTimeout(resolve, 500)),
|
99
|
-
);
|
100
|
-
|
101
|
-
const testProps = {
|
102
|
-
...defaultProps,
|
103
|
-
actions: {
|
104
|
-
...defaultProps.actions,
|
105
|
-
onSave: mockOnSave,
|
106
|
-
},
|
107
|
-
};
|
108
|
-
|
109
|
-
render(<StockOperationSubmission {...testProps} />);
|
110
|
-
const saveButton = screen.getByRole('button', { name: 'Save' });
|
111
|
-
await user.click(saveButton);
|
112
|
-
|
113
|
-
expect(saveButton).toBeDisabled();
|
114
|
-
|
115
|
-
await waitFor(() => {
|
116
|
-
expect(saveButton).not.toBeDisabled();
|
117
|
-
});
|
118
|
-
});
|
119
|
-
|
120
|
-
it('calls onComplete when Complete button is clicked', async () => {
|
121
|
-
render(<StockOperationSubmission {...defaultProps} />);
|
122
|
-
await user.click(screen.getByText(/complete/i));
|
123
|
-
await waitFor(() => {
|
124
|
-
expect(mockOnComplete).toHaveBeenCalled();
|
125
|
-
});
|
126
|
-
});
|
127
|
-
|
128
|
-
it('does not render buttons when locked', () => {
|
129
|
-
const lockedProps = {
|
130
|
-
...defaultProps,
|
131
|
-
locked: true,
|
132
|
-
};
|
133
|
-
|
134
|
-
render(<StockOperationSubmission {...lockedProps} />);
|
135
|
-
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument();
|
136
|
-
expect(screen.queryByRole('button', { name: /complete/i })).not.toBeInTheDocument();
|
137
|
-
});
|
138
|
-
});
|
@@ -1,55 +0,0 @@
|
|
1
|
-
import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
|
2
|
-
import { StockOperationItemDTO } from '../../core/api/types/stockOperation/StockOperationItemDTO';
|
3
|
-
import { Party } from '../../core/api/types/Party';
|
4
|
-
import { StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
|
5
|
-
import { SaveStockOperation } from '../../stock-items/types';
|
6
|
-
import { StockBatchDTO } from '../../core/api/types/stockItem/StockBatchDTO';
|
7
|
-
import { StockItemPackagingUOMDTO } from '../../core/api/types/stockItem/StockItemPackagingUOM';
|
8
|
-
import { StockItemInventory } from '../../core/api/types/stockItem/StockItemInventory';
|
9
|
-
|
10
|
-
export interface InitializeResult {
|
11
|
-
dto?: StockOperationDTO;
|
12
|
-
requisition?: string;
|
13
|
-
stockItems?: StockOperationItemDTO[];
|
14
|
-
showQuantityRequested?: boolean;
|
15
|
-
isNegativeQuantityAllowed: boolean;
|
16
|
-
requiresBatchUuid: boolean;
|
17
|
-
requiresActualBatchInfo: boolean;
|
18
|
-
isQuantityOptional: boolean;
|
19
|
-
canCaptureQuantityPrice: boolean;
|
20
|
-
requiresStockAdjustmentReason: boolean;
|
21
|
-
requiresDispatchAcknowledgement: boolean;
|
22
|
-
allowExpiredBatchNumbers: boolean;
|
23
|
-
canEditModel: boolean;
|
24
|
-
canViewModel: boolean;
|
25
|
-
canApproveModel: boolean;
|
26
|
-
canIssueStock: boolean;
|
27
|
-
canReceiveItems: boolean;
|
28
|
-
canDisplayReceivedItems: boolean;
|
29
|
-
canUpdateItemsBatchInformation: boolean;
|
30
|
-
canPrint: boolean;
|
31
|
-
sourceTags: string[];
|
32
|
-
destinationTags: string[];
|
33
|
-
shouldLockSource: boolean;
|
34
|
-
shouldLockDestination: boolean;
|
35
|
-
sourcePartyListFilter?: (p: Party) => boolean;
|
36
|
-
destinationPartyListFilter?: (p: Party) => boolean;
|
37
|
-
location?: string;
|
38
|
-
sourcePartyList?: Party[];
|
39
|
-
destinationPartyList?: Party[];
|
40
|
-
stockOperationTypes: StockOperationType[];
|
41
|
-
hasQtyRequested: boolean;
|
42
|
-
batchNos?: { [key: string]: StockBatchDTO[] };
|
43
|
-
itemUoM?: { [key: string]: StockItemPackagingUOMDTO[] };
|
44
|
-
batchBalance?: { [key: string]: StockItemInventory };
|
45
|
-
}
|
46
|
-
|
47
|
-
export interface AddStockOperationProps {
|
48
|
-
isEditing?: boolean;
|
49
|
-
model?: StockOperationDTO;
|
50
|
-
onSave?: SaveStockOperation;
|
51
|
-
operation: StockOperationType;
|
52
|
-
operations?: StockOperationType[];
|
53
|
-
canPrint?: boolean;
|
54
|
-
canEdit?: boolean;
|
55
|
-
}
|
@@ -1,54 +0,0 @@
|
|
1
|
-
import { z } from 'zod';
|
2
|
-
|
3
|
-
export const baseStockOperationSchema = {
|
4
|
-
stockItemUuid: z.string().min(1, { message: 'Required' }),
|
5
|
-
stockItemName: z.string().min(1).nullish(),
|
6
|
-
stockItemPackagingUOMUuid: z.string().min(1, { message: 'Required' }),
|
7
|
-
stockItemPackagingUOMName: z.string().min(1).nullish(),
|
8
|
-
batchNo: z.string().min(1, { message: 'Required' }),
|
9
|
-
stockBatchUuid: z.string().optional(),
|
10
|
-
expiration: z.coerce.date({ required_error: 'Required' }),
|
11
|
-
quantity: z.coerce.number().min(1, { message: 'Required' }),
|
12
|
-
purchasePrice: z.coerce.number().nullish(),
|
13
|
-
hasExpiration: z.boolean().nullish(),
|
14
|
-
};
|
15
|
-
|
16
|
-
export const stockItemTableSchema = z.object(baseStockOperationSchema);
|
17
|
-
|
18
|
-
export const stockItemTableSchemaWithNoExpiration = stockItemTableSchema.omit({
|
19
|
-
expiration: true,
|
20
|
-
});
|
21
|
-
|
22
|
-
export const stockOperationItemsSchema = z.object({
|
23
|
-
stockItems: z.array(stockItemTableSchema),
|
24
|
-
});
|
25
|
-
|
26
|
-
export type StockOperationItemsFormData = z.infer<typeof stockOperationItemsSchema>;
|
27
|
-
|
28
|
-
export type StockOperationItemFormData = z.infer<typeof stockOperationItemsSchema>['stockItems'][number];
|
29
|
-
|
30
|
-
export function useValidationSchema(operationType?: string) {
|
31
|
-
if (operationType === 'requisition') {
|
32
|
-
const customSchema = stockItemTableSchema.omit({
|
33
|
-
batchNo: true,
|
34
|
-
expiration: true,
|
35
|
-
});
|
36
|
-
return z.object({
|
37
|
-
stockItems: z.array(customSchema),
|
38
|
-
});
|
39
|
-
}
|
40
|
-
if (operationType === 'adjustment') {
|
41
|
-
const customSchema = stockItemTableSchema.extend({
|
42
|
-
quantity: z.coerce
|
43
|
-
.number()
|
44
|
-
.refine((value) => value !== 0, {
|
45
|
-
message: 'Quantity cannot be zero.',
|
46
|
-
})
|
47
|
-
.or(z.literal(0, { invalid_type_error: 'Invalid quantity format' })),
|
48
|
-
});
|
49
|
-
return z.object({
|
50
|
-
stockItems: z.array(customSchema),
|
51
|
-
});
|
52
|
-
}
|
53
|
-
return stockOperationItemsSchema;
|
54
|
-
}
|
@@ -1,114 +0,0 @@
|
|
1
|
-
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
|
2
|
-
import { Control, Controller, FieldValues } from 'react-hook-form';
|
3
|
-
import { ComboBox, InlineLoading } from '@carbon/react';
|
4
|
-
import { useStockItemBatchNos } from './batch-no-selector.resource';
|
5
|
-
import { StockBatchDTO } from '../../core/api/types/stockItem/StockBatchDTO';
|
6
|
-
import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
7
|
-
import { useTranslation } from 'react-i18next';
|
8
|
-
import { formatForDatePicker } from '../../constants';
|
9
|
-
|
10
|
-
interface BatchNoSelectorProps<T> {
|
11
|
-
placeholder?: string;
|
12
|
-
stockItemUuid: string;
|
13
|
-
batchUuid?: string;
|
14
|
-
onBatchNoChanged?: (item: StockBatchDTO) => void;
|
15
|
-
title?: string;
|
16
|
-
invalid?: boolean;
|
17
|
-
invalidText?: ReactNode;
|
18
|
-
selectedItem?: string;
|
19
|
-
|
20
|
-
// Control
|
21
|
-
controllerName: string;
|
22
|
-
name: string;
|
23
|
-
control: Control<FieldValues, T>;
|
24
|
-
}
|
25
|
-
|
26
|
-
const BatchNoSelector = <T,>(props: BatchNoSelectorProps<T>) => {
|
27
|
-
const { isLoading, stockItemBatchNos } = useStockItemBatchNos(props.stockItemUuid);
|
28
|
-
const [validationMessage, setValidationMessage] = useState<string | null>(null);
|
29
|
-
const [selectedItem, setSelectedItem] = useState<StockBatchDTO | null>(null);
|
30
|
-
const { t } = useTranslation();
|
31
|
-
|
32
|
-
const initialSelectedItem = useMemo(
|
33
|
-
() => stockItemBatchNos?.find((stockItem) => stockItem.uuid === props.batchUuid) ?? '',
|
34
|
-
[stockItemBatchNos, props.batchUuid],
|
35
|
-
);
|
36
|
-
|
37
|
-
const { items, setStockItemUuid } = useStockItemBatchInformationHook();
|
38
|
-
|
39
|
-
useEffect(() => {
|
40
|
-
setStockItemUuid(props.stockItemUuid);
|
41
|
-
}, [props.stockItemUuid, setStockItemUuid]);
|
42
|
-
|
43
|
-
const stockItemBatchesInfo = stockItemBatchNos?.map((item) => {
|
44
|
-
const matchingBatch = items?.find((batch) => batch.batchNumber === item.batchNo);
|
45
|
-
if (matchingBatch) {
|
46
|
-
return {
|
47
|
-
...item,
|
48
|
-
quantity: matchingBatch.quantity ?? '',
|
49
|
-
};
|
50
|
-
}
|
51
|
-
return item;
|
52
|
-
});
|
53
|
-
|
54
|
-
const filteredBatches = stockItemBatchesInfo?.filter((s) => s.quantity !== undefined && s.quantity !== 0);
|
55
|
-
|
56
|
-
useEffect(() => {
|
57
|
-
if (
|
58
|
-
!isLoading &&
|
59
|
-
stockItemBatchNos &&
|
60
|
-
props.selectedItem &&
|
61
|
-
(stockItemBatchNos.length === 0 || filteredBatches.length === 0)
|
62
|
-
) {
|
63
|
-
setValidationMessage('No stock batch numbers defined. Do a initial/receipt stock operation first.');
|
64
|
-
} else {
|
65
|
-
setValidationMessage(null);
|
66
|
-
}
|
67
|
-
}, [isLoading, stockItemBatchNos, props.selectedItem, filteredBatches]);
|
68
|
-
|
69
|
-
if (isLoading) return <InlineLoading status="active" data-testid="loading" />;
|
70
|
-
|
71
|
-
return (
|
72
|
-
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
73
|
-
<Controller
|
74
|
-
name={props.controllerName}
|
75
|
-
control={props.control}
|
76
|
-
render={({ field: { onChange, ref } }) => (
|
77
|
-
<ComboBox
|
78
|
-
style={{ flexGrow: '1' }}
|
79
|
-
titleText={props.title}
|
80
|
-
name={props.name}
|
81
|
-
control={props.control}
|
82
|
-
controllerName={props.controllerName}
|
83
|
-
id={props.name}
|
84
|
-
size={'sm'}
|
85
|
-
items={filteredBatches || []}
|
86
|
-
onChange={(data: { selectedItem?: StockBatchDTO }) => {
|
87
|
-
setSelectedItem(data.selectedItem || null);
|
88
|
-
props.onBatchNoChanged?.(data.selectedItem);
|
89
|
-
onChange(data.selectedItem?.uuid);
|
90
|
-
}}
|
91
|
-
initialSelectedItem={initialSelectedItem}
|
92
|
-
itemToString={(s: StockBatchDTO) =>
|
93
|
-
s?.batchNo
|
94
|
-
? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
|
95
|
-
: ''
|
96
|
-
}
|
97
|
-
placeholder={props.placeholder}
|
98
|
-
invalid={props.invalid}
|
99
|
-
invalidText={props.invalidText}
|
100
|
-
ref={ref}
|
101
|
-
/>
|
102
|
-
)}
|
103
|
-
/>
|
104
|
-
{isLoading && <InlineLoading status="active" />}
|
105
|
-
{validationMessage && (
|
106
|
-
<div data-testid="validation-message" style={{ color: 'red', marginTop: '8px' }}>
|
107
|
-
{t(validationMessage)}
|
108
|
-
</div>
|
109
|
-
)}
|
110
|
-
</div>
|
111
|
-
);
|
112
|
-
};
|
113
|
-
|
114
|
-
export default BatchNoSelector;
|
File without changes
|
@@ -1,101 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { render, screen } from '@testing-library/react';
|
3
|
-
import { useForm, FormProvider } from 'react-hook-form';
|
4
|
-
import { useStockItemBatchNos } from './batch-no-selector.resource';
|
5
|
-
import { useStockItemBatchInformationHook } from '../../stock-items/add-stock-item/batch-information/batch-information.resource';
|
6
|
-
import BatchNoSelector from './batch-no-selector.component';
|
7
|
-
import { StockBatchDTO } from '../../core/api/types/stockItem/StockBatchDTO';
|
8
|
-
import userEvent from '@testing-library/user-event';
|
9
|
-
import { waitFor } from '@testing-library/react';
|
10
|
-
|
11
|
-
// Mock hooks
|
12
|
-
jest.mock('./batch-no-selector.resource');
|
13
|
-
jest.mock('../../stock-items/add-stock-item/batch-information/batch-information.resource');
|
14
|
-
jest.mock('react-i18next', () => ({
|
15
|
-
useTranslation: () => ({ t: (key: string) => key }),
|
16
|
-
}));
|
17
|
-
|
18
|
-
const mockUseStockItemBatchNos = useStockItemBatchNos as jest.Mock;
|
19
|
-
const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
|
20
|
-
|
21
|
-
describe('BatchNoSelector Component', () => {
|
22
|
-
const stockItemUuid = 'test-uuid';
|
23
|
-
const batchUuid = 'batch-uuid';
|
24
|
-
const mockStockItemBatchNos: StockBatchDTO[] = [
|
25
|
-
{ uuid: '1', batchNo: 'batch1', quantity: '10', expiration: new Date(), stockItemUuid: '', voided: false },
|
26
|
-
{ uuid: '2', batchNo: 'batch2', quantity: '20', expiration: new Date(), stockItemUuid: '', voided: false },
|
27
|
-
];
|
28
|
-
const mockBatchInformation = [
|
29
|
-
{ batchNumber: 'batch1', quantity: '10' },
|
30
|
-
{ batchNumber: 'batch2', quantity: '20' },
|
31
|
-
];
|
32
|
-
|
33
|
-
beforeEach(() => {
|
34
|
-
mockUseStockItemBatchNos.mockReturnValue({
|
35
|
-
isLoading: false,
|
36
|
-
stockItemBatchNos: mockStockItemBatchNos,
|
37
|
-
});
|
38
|
-
mockUseStockItemBatchInformationHook.mockReturnValue({
|
39
|
-
items: mockBatchInformation,
|
40
|
-
setStockItemUuid: jest.fn(),
|
41
|
-
});
|
42
|
-
});
|
43
|
-
|
44
|
-
const renderComponent = () => {
|
45
|
-
const Wrapper = () => {
|
46
|
-
const methods = useForm();
|
47
|
-
return (
|
48
|
-
<FormProvider {...methods}>
|
49
|
-
<BatchNoSelector
|
50
|
-
stockItemUuid={stockItemUuid}
|
51
|
-
batchUuid={batchUuid}
|
52
|
-
controllerName="batchNo"
|
53
|
-
name="batchNo"
|
54
|
-
control={methods.control}
|
55
|
-
/>
|
56
|
-
</FormProvider>
|
57
|
-
);
|
58
|
-
};
|
59
|
-
render(<Wrapper />);
|
60
|
-
};
|
61
|
-
|
62
|
-
test('should render without crashing', () => {
|
63
|
-
renderComponent();
|
64
|
-
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
65
|
-
});
|
66
|
-
|
67
|
-
test('should display loading state when isLoading is true', () => {
|
68
|
-
mockUseStockItemBatchNos.mockReturnValueOnce({
|
69
|
-
isLoading: true,
|
70
|
-
stockItemBatchNos: [],
|
71
|
-
});
|
72
|
-
renderComponent();
|
73
|
-
expect(screen.getByTestId('loading')).toBeInTheDocument();
|
74
|
-
});
|
75
|
-
|
76
|
-
test('should call onBatchNoChanged when a batch is selected', async () => {
|
77
|
-
const onBatchNoChanged = jest.fn();
|
78
|
-
const Wrapper = () => {
|
79
|
-
const methods = useForm();
|
80
|
-
return (
|
81
|
-
<FormProvider {...methods}>
|
82
|
-
<BatchNoSelector
|
83
|
-
stockItemUuid={stockItemUuid}
|
84
|
-
batchUuid={batchUuid}
|
85
|
-
controllerName="batchNo"
|
86
|
-
name="batchNo"
|
87
|
-
control={methods.control}
|
88
|
-
onBatchNoChanged={onBatchNoChanged}
|
89
|
-
/>
|
90
|
-
</FormProvider>
|
91
|
-
);
|
92
|
-
};
|
93
|
-
render(<Wrapper />);
|
94
|
-
|
95
|
-
const combobox = screen.getByRole('combobox');
|
96
|
-
await userEvent.click(combobox);
|
97
|
-
await userEvent.click(screen.getByText((content) => content.includes('batch1')));
|
98
|
-
|
99
|
-
expect(onBatchNoChanged).toHaveBeenCalledWith(mockStockItemBatchNos[0]);
|
100
|
-
});
|
101
|
-
});
|