@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.
Files changed (128) hide show
  1. package/__mocks__/index.ts +1 -0
  2. package/__mocks__/operation-type.mock.ts +532 -0
  3. package/dist/155.js +1 -0
  4. package/dist/155.js.map +1 -0
  5. package/dist/172.js +1 -1
  6. package/dist/20.js +1 -1
  7. package/dist/290.js +1 -1
  8. package/dist/493.js +2 -0
  9. package/dist/493.js.map +1 -0
  10. package/dist/606.js +1 -1
  11. package/dist/627.js +1 -1
  12. package/dist/922.js +1 -0
  13. package/dist/922.js.map +1 -0
  14. package/dist/main.js +1 -1
  15. package/dist/main.js.map +1 -1
  16. package/dist/openmrs-esm-stock-management-app.js +1 -1
  17. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +75 -51
  18. package/dist/openmrs-esm-stock-management-app.js.map +1 -1
  19. package/dist/routes.json +1 -1
  20. package/package.json +1 -1
  21. package/src/config-schema.ts +6 -0
  22. package/src/core/utils/utils.ts +29 -0
  23. package/src/index.ts +4 -0
  24. package/src/routes.json +9 -0
  25. package/src/stock-items/add-stock-item/transactions/printout/transactions-stockcard-printout.component.tsx +8 -12
  26. package/src/stock-items/add-stock-item/transactions/transactions.component.tsx +8 -12
  27. package/src/stock-items/stock-items.resource.ts +5 -5
  28. package/src/stock-lookups/stock-lookups.resource.ts +2 -2
  29. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.scss +34 -0
  30. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.tsx +111 -0
  31. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.component.tsx +87 -0
  32. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.scss +31 -0
  33. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operations-status.tsx +45 -0
  34. package/src/stock-operations/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +41 -16
  35. package/src/stock-operations/stock-operation-reference.component.tsx +64 -0
  36. package/src/stock-operations/stock-operation-status/stock-operation-status-row.tsx +77 -0
  37. package/src/stock-operations/stock-operation-status/stock-operation-status.scss +32 -0
  38. package/src/stock-operations/stock-operation-status/stock-operation-status.tsx +45 -0
  39. package/src/stock-operations/stock-operation-types-selector/stock-operation-types-selector.component.tsx +30 -29
  40. package/src/stock-operations/stock-operation.utils.tsx +16 -79
  41. package/src/stock-operations/stock-operations-dialog/stock-operations-issue-stock-button.component.tsx +27 -39
  42. package/src/stock-operations/stock-operations-dialog/stock-operations-print-button.component.tsx +51 -59
  43. package/src/stock-operations/{stock-item-selector/stock-item-selector.resource.tsx → stock-operations-forms/hooks/useFilterableStockItems.ts} +4 -4
  44. package/src/stock-operations/stock-operations-forms/hooks/useFilteredOperationTypesByRoles.ts +30 -0
  45. package/src/stock-operations/stock-operations-forms/hooks/useOperationTypePermisions.ts +29 -0
  46. package/src/stock-operations/stock-operations-forms/hooks/useParties.ts +73 -0
  47. package/src/stock-operations/{users-selector/users-selector.resource.tsx → stock-operations-forms/hooks/useSearchUser.ts} +9 -7
  48. package/src/stock-operations/{batch-no-selector/batch-no-selector.resource.tsx → stock-operations-forms/hooks/useStockItemBatchNumbers.ts} +3 -3
  49. package/src/stock-operations/stock-operations-forms/hooks/useStockOperationLinks.ts +20 -0
  50. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +72 -0
  51. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +90 -0
  52. 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
  53. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +157 -0
  54. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +53 -0
  55. package/src/stock-operations/stock-operations-forms/input-components/stock-item-search.component.tsx +79 -0
  56. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.component.tsx +59 -0
  57. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +216 -0
  58. package/src/stock-operations/{batch-no-selector → stock-operations-forms/input-components}/unique-batch-no-entry-input.component.tsx +12 -7
  59. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +110 -0
  60. package/src/stock-operations/stock-operations-forms/input-components/users-selector.component.tsx +111 -0
  61. package/src/stock-operations/stock-operations-forms/step1.test.tsx +303 -0
  62. package/src/stock-operations/stock-operations-forms/step2.test.tsx +254 -0
  63. package/src/stock-operations/stock-operations-forms/step3.test.tsx +223 -0
  64. package/src/stock-operations/stock-operations-forms/steps/base-operation-details-form-step.tsx +241 -0
  65. package/src/stock-operations/stock-operations-forms/steps/quantity-uom-cell.component.tsx +33 -0
  66. package/src/stock-operations/stock-operations-forms/steps/received-items.component.tsx +110 -0
  67. package/src/stock-operations/stock-operations-forms/steps/stock-availability-cell.component.tsx +51 -0
  68. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-batch-no-cell.component.tsx +40 -0
  69. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-cell.component.tsx +50 -0
  70. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-expiry-cell.component.tsx +41 -0
  71. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +281 -0
  72. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.scc.scss +64 -0
  73. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +243 -0
  74. package/src/stock-operations/stock-operations-forms/stock-issue-form-initializer-with-related-requisition-operation.component.tsx +55 -0
  75. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +41 -0
  76. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +211 -0
  77. package/src/stock-operations/stock-operations-forms/stock-operation-form-header.component.tsx +166 -0
  78. package/src/stock-operations/stock-operations-forms/stock-operation-form.component.tsx +205 -0
  79. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +111 -0
  80. package/src/stock-operations/stock-operations-forms/stock-operation-related-link.component.tsx +45 -0
  81. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +41 -0
  82. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +52 -0
  83. package/src/stock-operations/stock-operations-forms/stock-operations-form-utils.ts +32 -0
  84. package/src/stock-operations/stock-operations-table.component.tsx +57 -92
  85. package/src/stock-operations/stock-operations.resource.ts +16 -13
  86. package/src/stock-operations/validation-schema.ts +72 -14
  87. package/dist/766.js +0 -2
  88. package/dist/766.js.map +0 -1
  89. package/dist/822.js +0 -1
  90. package/dist/822.js.map +0 -1
  91. package/src/stock-operations/add-stock-operation/add-stock-operation.component.tsx +0 -349
  92. package/src/stock-operations/add-stock-operation/add-stock-operation.resource.tsx +0 -27
  93. package/src/stock-operations/add-stock-operation/add-stock-operation.scss +0 -60
  94. package/src/stock-operations/add-stock-operation/add-stock-operation.test.tsx +0 -192
  95. package/src/stock-operations/add-stock-operation/add-stock-operation.utils.tsx +0 -152
  96. package/src/stock-operations/add-stock-operation/add-stock-utils.ts +0 -103
  97. package/src/stock-operations/add-stock-operation/base-operation-details.component.tsx +0 -439
  98. package/src/stock-operations/add-stock-operation/base-operation-details.scss +0 -30
  99. package/src/stock-operations/add-stock-operation/received-items.component.tsx +0 -93
  100. package/src/stock-operations/add-stock-operation/stock-item-search/stock-item-search.component.tsx +0 -70
  101. package/src/stock-operations/add-stock-operation/stock-items-addition-row.component.tsx +0 -357
  102. package/src/stock-operations/add-stock-operation/stock-items-addition-row.resource.tsx +0 -0
  103. package/src/stock-operations/add-stock-operation/stock-items-addition-row.scss +0 -12
  104. package/src/stock-operations/add-stock-operation/stock-items-addition-row.test.tsx +0 -10
  105. package/src/stock-operations/add-stock-operation/stock-items-addition.component.scss +0 -17
  106. package/src/stock-operations/add-stock-operation/stock-items-addition.component.tsx +0 -254
  107. package/src/stock-operations/add-stock-operation/stock-operation-context/useStockOperationContext.tsx +0 -16
  108. package/src/stock-operations/add-stock-operation/stock-operation-reference.component.tsx +0 -39
  109. package/src/stock-operations/add-stock-operation/stock-operation-related-link.component.tsx +0 -38
  110. package/src/stock-operations/add-stock-operation/stock-operation-status.component.tsx +0 -170
  111. package/src/stock-operations/add-stock-operation/stock-operation-submission.component.tsx +0 -189
  112. package/src/stock-operations/add-stock-operation/stock-operation-submission.test.tsx +0 -138
  113. package/src/stock-operations/add-stock-operation/types.ts +0 -55
  114. package/src/stock-operations/add-stock-operation/validationSchema.ts +0 -54
  115. package/src/stock-operations/batch-no-selector/batch-no-selector.component.tsx +0 -114
  116. package/src/stock-operations/batch-no-selector/batch-no-selector.scss +0 -0
  117. package/src/stock-operations/batch-no-selector/batch-no-selector.test.tsx +0 -101
  118. package/src/stock-operations/party-selector/party-selector.component.tsx +0 -59
  119. package/src/stock-operations/qty-uom-selector/qty-uom-selector.component.tsx +0 -65
  120. package/src/stock-operations/qty-uom-selector/qty-uom-selector.resource.tsx +0 -0
  121. package/src/stock-operations/qty-uom-selector/qty-uom-selector.scss +0 -0
  122. package/src/stock-operations/qty-uom-selector/qty-uom-selector.test.tsx +0 -10
  123. package/src/stock-operations/stock-item-selector/stock-item-selector.component.tsx +0 -69
  124. package/src/stock-operations/stock-item-selector/stock-item-selector.scss +0 -0
  125. package/src/stock-operations/stock-item-selector/stock-item-selector.test.tsx +0 -10
  126. package/src/stock-operations/stock-operation-reason-selector/stock-operation-reason-selector.component.tsx +0 -62
  127. package/src/stock-operations/users-selector/users-selector.component.tsx +0 -75
  128. /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
+ });
@@ -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;
@@ -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;