@openmrs/esm-stock-management-app 1.0.1-pre.783 → 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.
Files changed (121) 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/914.js +1 -0
  13. package/dist/914.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/core/utils/utils.ts +29 -0
  22. package/src/index.ts +4 -0
  23. package/src/routes.json +9 -0
  24. package/src/stock-items/add-stock-item/transactions/printout/transactions-stockcard-printout.component.tsx +8 -12
  25. package/src/stock-items/add-stock-item/transactions/transactions.component.tsx +8 -12
  26. package/src/stock-items/stock-items.resource.ts +5 -5
  27. package/src/stock-lookups/stock-lookups.resource.ts +2 -2
  28. package/src/stock-operations/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +41 -16
  29. package/src/stock-operations/{add-stock-operation/received-items.component.tsx → received-items.component.tsx} +1 -1
  30. package/src/stock-operations/stock-operation-reference.component.tsx +64 -0
  31. package/src/stock-operations/stock-operation-status/stock-operation-status-row.tsx +77 -0
  32. package/src/stock-operations/stock-operation-status/stock-operation-status.scss +32 -0
  33. package/src/stock-operations/stock-operation-status/stock-operation-status.tsx +45 -0
  34. package/src/stock-operations/stock-operation-types-selector/stock-operation-types-selector.component.tsx +30 -29
  35. package/src/stock-operations/stock-operation.utils.tsx +16 -79
  36. package/src/stock-operations/stock-operations-dialog/stock-operations-issue-stock-button.component.tsx +27 -39
  37. package/src/stock-operations/stock-operations-dialog/stock-operations-print-button.component.tsx +51 -59
  38. package/src/stock-operations/{stock-item-selector/stock-item-selector.resource.tsx → stock-operations-forms/hooks/useFilterableStockItems.ts} +4 -4
  39. package/src/stock-operations/stock-operations-forms/hooks/useFilteredOperationTypesByRoles.ts +30 -0
  40. package/src/stock-operations/stock-operations-forms/hooks/useOperationTypePermisions.ts +29 -0
  41. package/src/stock-operations/stock-operations-forms/hooks/useParties.ts +73 -0
  42. package/src/stock-operations/{users-selector/users-selector.resource.tsx → stock-operations-forms/hooks/useSearchUser.ts} +9 -7
  43. package/src/stock-operations/{batch-no-selector/batch-no-selector.resource.tsx → stock-operations-forms/hooks/useStockItemBatchNumbers.ts} +3 -3
  44. package/src/stock-operations/stock-operations-forms/hooks/useStockOperationLinks.ts +20 -0
  45. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +72 -0
  46. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +90 -0
  47. 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
  48. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +157 -0
  49. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +53 -0
  50. package/src/stock-operations/stock-operations-forms/input-components/stock-item-search.component.tsx +56 -0
  51. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.component.tsx +59 -0
  52. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +216 -0
  53. package/src/stock-operations/{batch-no-selector → stock-operations-forms/input-components}/unique-batch-no-entry-input.component.tsx +12 -7
  54. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +110 -0
  55. package/src/stock-operations/stock-operations-forms/input-components/users-selector.component.tsx +111 -0
  56. package/src/stock-operations/stock-operations-forms/step1.test.tsx +303 -0
  57. package/src/stock-operations/stock-operations-forms/step2.test.tsx +250 -0
  58. package/src/stock-operations/stock-operations-forms/step3.test.tsx +223 -0
  59. package/src/stock-operations/stock-operations-forms/steps/base-operation-details-form-step.tsx +241 -0
  60. package/src/stock-operations/stock-operations-forms/steps/quantity-uom-cell.component.tsx +33 -0
  61. package/src/stock-operations/stock-operations-forms/steps/stock-availability-cell.component.tsx +51 -0
  62. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-batch-no-cell.component.tsx +40 -0
  63. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-cell.component.tsx +38 -0
  64. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-expiry-cell.component.tsx +41 -0
  65. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +281 -0
  66. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.scc.scss +64 -0
  67. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +236 -0
  68. package/src/stock-operations/stock-operations-forms/stock-issue-form-initializer-with-related-requisition-operation.component.tsx +55 -0
  69. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +41 -0
  70. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +197 -0
  71. package/src/stock-operations/stock-operations-forms/stock-operation-form-header.component.tsx +166 -0
  72. package/src/stock-operations/stock-operations-forms/stock-operation-form.component.tsx +200 -0
  73. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +111 -0
  74. package/src/stock-operations/stock-operations-forms/stock-operation-related-link.component.tsx +45 -0
  75. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +41 -0
  76. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +52 -0
  77. package/src/stock-operations/stock-operations-forms/stock-operations-form-utils.ts +32 -0
  78. package/src/stock-operations/stock-operations-table.component.tsx +20 -56
  79. package/src/stock-operations/stock-operations.resource.ts +16 -13
  80. package/src/stock-operations/validation-schema.ts +72 -14
  81. package/dist/766.js +0 -2
  82. package/dist/766.js.map +0 -1
  83. package/dist/822.js +0 -1
  84. package/dist/822.js.map +0 -1
  85. package/src/stock-operations/add-stock-operation/add-stock-operation.component.tsx +0 -349
  86. package/src/stock-operations/add-stock-operation/add-stock-operation.resource.tsx +0 -27
  87. package/src/stock-operations/add-stock-operation/add-stock-operation.scss +0 -60
  88. package/src/stock-operations/add-stock-operation/add-stock-operation.test.tsx +0 -192
  89. package/src/stock-operations/add-stock-operation/add-stock-operation.utils.tsx +0 -152
  90. package/src/stock-operations/add-stock-operation/add-stock-utils.ts +0 -103
  91. package/src/stock-operations/add-stock-operation/base-operation-details.component.tsx +0 -439
  92. package/src/stock-operations/add-stock-operation/base-operation-details.scss +0 -30
  93. package/src/stock-operations/add-stock-operation/stock-item-search/stock-item-search.component.tsx +0 -70
  94. package/src/stock-operations/add-stock-operation/stock-items-addition-row.component.tsx +0 -357
  95. package/src/stock-operations/add-stock-operation/stock-items-addition-row.resource.tsx +0 -0
  96. package/src/stock-operations/add-stock-operation/stock-items-addition-row.scss +0 -12
  97. package/src/stock-operations/add-stock-operation/stock-items-addition-row.test.tsx +0 -10
  98. package/src/stock-operations/add-stock-operation/stock-items-addition.component.scss +0 -17
  99. package/src/stock-operations/add-stock-operation/stock-items-addition.component.tsx +0 -254
  100. package/src/stock-operations/add-stock-operation/stock-operation-context/useStockOperationContext.tsx +0 -16
  101. package/src/stock-operations/add-stock-operation/stock-operation-reference.component.tsx +0 -39
  102. package/src/stock-operations/add-stock-operation/stock-operation-related-link.component.tsx +0 -38
  103. package/src/stock-operations/add-stock-operation/stock-operation-status.component.tsx +0 -170
  104. package/src/stock-operations/add-stock-operation/stock-operation-submission.component.tsx +0 -189
  105. package/src/stock-operations/add-stock-operation/stock-operation-submission.test.tsx +0 -138
  106. package/src/stock-operations/add-stock-operation/types.ts +0 -55
  107. package/src/stock-operations/add-stock-operation/validationSchema.ts +0 -54
  108. package/src/stock-operations/batch-no-selector/batch-no-selector.component.tsx +0 -114
  109. package/src/stock-operations/batch-no-selector/batch-no-selector.scss +0 -0
  110. package/src/stock-operations/batch-no-selector/batch-no-selector.test.tsx +0 -101
  111. package/src/stock-operations/party-selector/party-selector.component.tsx +0 -59
  112. package/src/stock-operations/qty-uom-selector/qty-uom-selector.component.tsx +0 -65
  113. package/src/stock-operations/qty-uom-selector/qty-uom-selector.resource.tsx +0 -0
  114. package/src/stock-operations/qty-uom-selector/qty-uom-selector.scss +0 -0
  115. package/src/stock-operations/qty-uom-selector/qty-uom-selector.test.tsx +0 -10
  116. package/src/stock-operations/stock-item-selector/stock-item-selector.component.tsx +0 -69
  117. package/src/stock-operations/stock-item-selector/stock-item-selector.scss +0 -0
  118. package/src/stock-operations/stock-item-selector/stock-item-selector.test.tsx +0 -10
  119. package/src/stock-operations/stock-operation-reason-selector/stock-operation-reason-selector.component.tsx +0 -62
  120. package/src/stock-operations/users-selector/users-selector.component.tsx +0 -75
  121. /package/dist/{766.js.LICENSE.txt → 493.js.LICENSE.txt} +0 -0
@@ -0,0 +1,281 @@
1
+ import {
2
+ Button,
3
+ DataTable,
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableContainer,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@carbon/react';
12
+ import { Edit, TrashCan } from '@carbon/react/icons';
13
+ import { isDesktop, launchWorkspace } from '@openmrs/esm-framework';
14
+ import React, { useCallback, useMemo } from 'react';
15
+ import { useFormContext } from 'react-hook-form';
16
+ import { useTranslation } from 'react-i18next';
17
+ import { StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
18
+ import { StockOperationType } from '../../../core/api/types/stockOperation/StockOperationType';
19
+ import { getStockOperationUniqueId } from '../../stock-operation.utils';
20
+ import { BaseStockOperationItemFormData, StockOperationItemDtoSchema } from '../../validation-schema';
21
+ import useOperationTypePermisions from '../hooks/useOperationTypePermisions';
22
+ import StockItemSearch from '../input-components/stock-item-search.component';
23
+ import { StockItemFormProps } from '../stock-item-form/stock-item-form.workspace';
24
+ import QuantityUomCell from './quantity-uom-cell.component';
25
+ import StockAvailability from './stock-availability-cell.component';
26
+ import StockOperationItemBatchNoCell from './stock-operation-item-batch-no-cell.component';
27
+ import StockOperationItemCell from './stock-operation-item-cell.component';
28
+ import StockoperationItemExpiryCell from './stock-operation-item-expiry-cell.component';
29
+ import styles from './stock-operation-items-form-step.scc.scss';
30
+
31
+ type StockOperationItemsFormStepProps = {
32
+ stockOperation?: StockOperationDTO;
33
+ stockOperationType: StockOperationType;
34
+ onNext?: () => void;
35
+ onPrevious?: () => void;
36
+ };
37
+ const StockOperationItemsFormStep: React.FC<StockOperationItemsFormStepProps> = ({
38
+ stockOperationType,
39
+ stockOperation,
40
+ onNext,
41
+ onPrevious,
42
+ }) => {
43
+ const { t } = useTranslation();
44
+ const operationTypePermision = useOperationTypePermisions(stockOperationType);
45
+
46
+ const form = useFormContext<StockOperationItemDtoSchema>();
47
+ const observableOperationItems = form.watch('stockOperationItems');
48
+ const headers = useMemo(() => {
49
+ return [
50
+ {
51
+ key: 'item',
52
+ header: t('item', 'Item'),
53
+ styles: { width: '40% !important' },
54
+ },
55
+ {
56
+ key: 'itemDetails',
57
+ header: t('itemDetails', 'Item Details'),
58
+ styles: { width: '20% !important' },
59
+ },
60
+ ...(operationTypePermision.requiresBatchUuid || operationTypePermision.requiresActualBatchInfo
61
+ ? [
62
+ {
63
+ key: 'batch',
64
+ header: t('batchNo', 'Batch No'),
65
+ styles: { width: '15% !important' },
66
+ },
67
+ ]
68
+ : []),
69
+ ...(operationTypePermision.requiresActualBatchInfo
70
+ ? [
71
+ {
72
+ key: 'expiry',
73
+ header: t('expiry', 'Expiry'),
74
+ },
75
+ ]
76
+ : []),
77
+ ...(operationTypePermision.requiresBatchUuid
78
+ ? [
79
+ {
80
+ key: 'expiry',
81
+ header: t('expiry', 'Expiry'),
82
+ },
83
+ ]
84
+ : []),
85
+
86
+ {
87
+ key: 'quantity',
88
+ header: t('qty', 'Qty'),
89
+ },
90
+ {
91
+ key: 'quantityuom',
92
+ header: t('quantityUom', 'Qty UoM'),
93
+ },
94
+ ...(operationTypePermision.canCaptureQuantityPrice
95
+ ? [
96
+ {
97
+ key: 'purchasePrice',
98
+ header: t('purchasePrice', 'Purchase Price'),
99
+ },
100
+ ]
101
+ : []),
102
+ { key: 'actions', header: t('actions', 'Actions') },
103
+ ];
104
+ }, [operationTypePermision, t]);
105
+
106
+ const handleLaunchStockItem = useCallback(
107
+ (stockOperationItem?: BaseStockOperationItemFormData) => {
108
+ launchWorkspace('stock-operation-stock-items-form', {
109
+ workspaceTitle: t('stockItem', 'StockItem'),
110
+ ...({
111
+ stockOperationType,
112
+ stockOperationItem,
113
+ onSave: (data) => {
114
+ const items = (form.getValues('stockOperationItems') ?? []) as Array<BaseStockOperationItemFormData>;
115
+ const index = items.findIndex((i) => i.uuid === data.uuid);
116
+ if (index === -1) {
117
+ items.push(data);
118
+ } else {
119
+ items[index] = data;
120
+ }
121
+ form.setValue('stockOperationItems', items as any);
122
+ },
123
+ } as StockItemFormProps),
124
+ });
125
+ },
126
+ [stockOperationType, t, form],
127
+ );
128
+
129
+ const handleDeleteStockOperationItem = useCallback(
130
+ (item: BaseStockOperationItemFormData) => {
131
+ form.setValue('stockOperationItems', observableOperationItems.filter((i) => i.uuid !== item.uuid) as any);
132
+ },
133
+ [form, observableOperationItems],
134
+ );
135
+
136
+ const tableRows = useMemo(() => {
137
+ return observableOperationItems?.map((item) => {
138
+ const {
139
+ batchNo,
140
+ expiration,
141
+ quantity,
142
+ purchasePrice,
143
+ uuid,
144
+ stockItemUuid,
145
+ stockItemPackagingUOMUuid,
146
+ stockBatchUuid,
147
+ } = item;
148
+
149
+ return {
150
+ id: uuid,
151
+ item: stockItemUuid ? <StockOperationItemCell stockItemUuid={stockItemUuid} /> : '--',
152
+ itemDetails: stockItemUuid ? <StockAvailability stockItemUuid={stockItemUuid} /> : '--',
153
+ batch: (
154
+ <StockOperationItemBatchNoCell
155
+ operation={stockOperationType}
156
+ stockBatchUuid={stockBatchUuid}
157
+ batchNo={batchNo}
158
+ stockItemUuid={stockItemUuid}
159
+ />
160
+ ),
161
+ expiry: (
162
+ <StockoperationItemExpiryCell
163
+ operation={stockOperationType}
164
+ stockBatchUuid={stockBatchUuid}
165
+ expiration={expiration}
166
+ stockItemUuid={stockItemUuid}
167
+ />
168
+ ),
169
+ quantity: quantity?.toLocaleString(),
170
+ quantityuom: stockItemPackagingUOMUuid ? (
171
+ <QuantityUomCell stockItemPackagingUOMUuid={stockItemPackagingUOMUuid} stockItemUuid={stockItemUuid} />
172
+ ) : (
173
+ '--'
174
+ ),
175
+ purchasePrice: purchasePrice,
176
+ actions: (
177
+ <>
178
+ <Button
179
+ type="button"
180
+ size="sm"
181
+ className="submitButton clear-padding-margin"
182
+ iconDescription={'Edit'}
183
+ kind="ghost"
184
+ renderIcon={Edit}
185
+ onClick={() => {
186
+ handleLaunchStockItem(item);
187
+ }}
188
+ />
189
+ <Button
190
+ type="button"
191
+ size="sm"
192
+ className="submitButton clear-padding-margin"
193
+ iconDescription={'Delete'}
194
+ kind="ghost"
195
+ renderIcon={TrashCan}
196
+ onClick={() => {
197
+ handleDeleteStockOperationItem(item);
198
+ }}
199
+ />
200
+ </>
201
+ ),
202
+ };
203
+ });
204
+ }, [observableOperationItems, handleLaunchStockItem, handleDeleteStockOperationItem, stockOperationType]);
205
+
206
+ const headerTitle = t('stockoperationItems', 'Stock operation items');
207
+
208
+ return (
209
+ <div style={{ margin: '10px' }}>
210
+ <div className={styles.tableContainer}>
211
+ <div className={styles.heading}>
212
+ <h4>{headerTitle}</h4>
213
+ <div className={styles.btnSet}>
214
+ {typeof onPrevious === 'function' && (
215
+ <Button kind="secondary" onClick={onPrevious}>
216
+ {t('previous', 'Previous')}
217
+ </Button>
218
+ )}
219
+ {typeof onNext === 'function' && (
220
+ <Button kind="primary" onClick={onNext}>
221
+ {t('next', 'Next')}
222
+ </Button>
223
+ )}
224
+ </div>
225
+ </div>
226
+ <StockItemSearch
227
+ onSelectedItem={(stockItem) =>
228
+ handleLaunchStockItem({
229
+ uuid: `new-item-${getStockOperationUniqueId()}`,
230
+ stockItemUuid: stockItem.uuid,
231
+ hasExpiration: stockItem.hasExpiration,
232
+ purchasePrice: stockItem.purchasePrice,
233
+ })
234
+ }
235
+ />
236
+
237
+ <DataTable
238
+ rows={tableRows ?? []}
239
+ headers={headers}
240
+ isSortable={false}
241
+ useZebraStyles={true}
242
+ className={styles.dataTable}
243
+ render={({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
244
+ <TableContainer>
245
+ <Table {...getTableProps()}>
246
+ <TableHead>
247
+ <TableRow>
248
+ {headers.map((header: any) => (
249
+ <TableHeader
250
+ {...getHeaderProps({
251
+ header,
252
+ isSortable: false,
253
+ })}
254
+ className={isDesktop ? styles.desktopHeader : styles.tabletHeader}
255
+ style={header?.styles}
256
+ key={`${header.key}`}
257
+ >
258
+ {header.header?.content ?? header?.header}
259
+ </TableHeader>
260
+ ))}
261
+ </TableRow>
262
+ </TableHead>
263
+ <TableBody>
264
+ {rows.map((row) => (
265
+ <TableRow {...getRowProps({ row })}>
266
+ {row.cells.map((cell) => (
267
+ <TableCell key={cell.id}>{cell.value}</TableCell>
268
+ ))}
269
+ </TableRow>
270
+ ))}
271
+ </TableBody>
272
+ </Table>
273
+ </TableContainer>
274
+ )}
275
+ ></DataTable>
276
+ </div>
277
+ </div>
278
+ );
279
+ };
280
+
281
+ export default StockOperationItemsFormStep;
@@ -0,0 +1,64 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+ @import '~@openmrs/esm-styleguide/src/vars';
5
+
6
+ .tableContainer {
7
+ :global(.cds--data-table-content) {
8
+ height: calc(100vh - 150px);
9
+ }
10
+ }
11
+
12
+ .tableHeader {
13
+ width: 3% !important;
14
+ }
15
+
16
+ .tableBody {
17
+ display: flex;
18
+ flex-direction: row;
19
+ width: 3% !important;
20
+ gap: 8px;
21
+ }
22
+
23
+ .dataTable {
24
+ width: 100%;
25
+ }
26
+
27
+ .availability {
28
+ font-size: 0.875rem;
29
+
30
+ .outOfStock {
31
+ color: #da1e28;
32
+ }
33
+ }
34
+
35
+ .heading {
36
+ text-align: left;
37
+ text-transform: capitalize;
38
+ display: flex;
39
+ flex-direction: row;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ margin: layout.$spacing-03 0 layout.$spacing-03 0;
43
+
44
+ h4 {
45
+ @include type.type-style('heading-compact-02');
46
+ color: colors.$gray-70;
47
+
48
+ &:after {
49
+ content: '';
50
+ display: block;
51
+ width: 2rem;
52
+ padding-top: 3px;
53
+ border-bottom: 0.375rem solid;
54
+ @include brand-03(border-bottom-color);
55
+ }
56
+ }
57
+ }
58
+
59
+ .btnSet {
60
+ display: flex;
61
+ flex-direction: row;
62
+ gap: layout.$spacing-03;
63
+ align-items: center;
64
+ }
@@ -0,0 +1,236 @@
1
+ import { Button, Column, InlineLoading, RadioButton, RadioButtonGroup, Stack } from '@carbon/react';
2
+ import { Departure, ListChecked, Save, SendFilled } from '@carbon/react/icons';
3
+ import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
4
+ import React, { useCallback, useMemo, useState } from 'react';
5
+ import { useFormContext } from 'react-hook-form';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { extractErrorMessagesFromResponse } from '../../../constants';
8
+ import { StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
9
+ import { StockOperationItemDTO } from '../../../core/api/types/stockOperation/StockOperationItemDTO';
10
+ import { OperationType, StockOperationType } from '../../../core/api/types/stockOperation/StockOperationType';
11
+ import { otherUser } from '../../../core/utils/utils';
12
+ import { handleMutate } from '../../../utils';
13
+ import { showActionDialogButton } from '../../stock-operation.utils';
14
+ import { createStockOperation, deleteStockOperationItem, updateStockOperation } from '../../stock-operations.resource';
15
+ import { StockOperationItemDtoSchema } from '../../validation-schema';
16
+ import useOperationTypePermisions from '../hooks/useOperationTypePermisions';
17
+ import styles from '../stock-operation-form.scss';
18
+
19
+ type StockOperationSubmissionFormStepProps = {
20
+ onPrevious?: () => void;
21
+ stockOperation?: StockOperationDTO;
22
+ stockOperationType: StockOperationType;
23
+ };
24
+ const StockOperationSubmissionFormStep: React.FC<StockOperationSubmissionFormStepProps> = ({
25
+ onPrevious,
26
+ stockOperationType,
27
+ stockOperation,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const operationTypePermision = useOperationTypePermisions(stockOperationType);
31
+ const editable = useMemo(() => !stockOperation || stockOperation.status === 'NEW', [stockOperation]);
32
+ const form = useFormContext<StockOperationItemDtoSchema>();
33
+ const [approvalRequired, setApprovalRequired] = useState<boolean | null>(stockOperation?.approvalRequired);
34
+ const isStockIssueOperation = useMemo(
35
+ () => OperationType.STOCK_ISSUE_OPERATION_TYPE === stockOperationType.operationType,
36
+ [stockOperationType],
37
+ );
38
+ const handleRadioButtonChange = (selectedItem: boolean) => {
39
+ setApprovalRequired(selectedItem);
40
+ };
41
+
42
+ const handleSave = useCallback(async () => {
43
+ let result: StockOperationDTO; // To store the result for returning
44
+ await form.handleSubmit(async (formData) => {
45
+ try {
46
+ // Get deleted items (items in stock operation bt not i form data)
47
+ const itemsToDelete =
48
+ stockOperation?.stockOperationItems?.reduce<Array<StockOperationItemDTO>>((prev, curr) => {
49
+ const itemDoNotExistInFormData =
50
+ formData.stockOperationItems.findIndex((item) => item.uuid === curr.uuid) === -1;
51
+ if (itemDoNotExistInFormData) {
52
+ return [...prev, curr];
53
+ }
54
+ return prev;
55
+ }, []) ?? [];
56
+ // Delete them from backend asynchronosely
57
+ const deleted = await Promise.allSettled(itemsToDelete.map((item) => deleteStockOperationItem(item.uuid)));
58
+ // Give delete status on completion
59
+ deleted.forEach((del, index) => {
60
+ showSnackbar({
61
+ kind: del.status === 'rejected' ? 'error' : 'success',
62
+ title:
63
+ del.status === 'rejected'
64
+ ? t('stockoperationItemDeleteError', 'Error deleting stock operation item {{item}}', {
65
+ item: itemsToDelete[index].commonName,
66
+ })
67
+ : t('success', 'Success'),
68
+ subtitle:
69
+ del.status === 'rejected'
70
+ ? del.reason?.message
71
+ : t('stockoperationItemDeletSuccess', 'Stock operation item {{item}} deleted succesfully', {
72
+ item: itemsToDelete[index].commonName,
73
+ }),
74
+ });
75
+ });
76
+ // construct update payload
77
+ const payload = {
78
+ ...formData,
79
+ // Remove other uuid if responsible person is set to other
80
+ responsiblePersonUuid:
81
+ formData.responsiblePersonUuid === otherUser.uuid ? undefined : formData.responsiblePersonUuid,
82
+ approvalRequired: approvalRequired ? true : false,
83
+ stockOperationItems: [
84
+ ...formData.stockOperationItems.map((item) => ({
85
+ ...item,
86
+ uuid:
87
+ item.uuid.startsWith('new-item-') || (!stockOperation && isStockIssueOperation) ? undefined : item.uuid, // Remove uuid for newly inserted items and stock issue items derived from requisition to avoid foreign key constraint lookup error
88
+ })),
89
+ ],
90
+ };
91
+ const resp = await (stockOperation
92
+ ? updateStockOperation(stockOperation, payload as any)
93
+ : createStockOperation(payload as any));
94
+ result = resp.data; // Store the response data
95
+ handleMutate(`${restBaseUrl}/stockmanagement/stockoperation`);
96
+ showSnackbar({
97
+ isLowContrast: true,
98
+ title: stockOperation
99
+ ? t('editStockOperation', 'Edit stock operation')
100
+ : t('addStockOperation', 'Add stock operation'),
101
+ kind: 'success',
102
+ subtitle: stockOperation
103
+ ? t('stockOperationEdited', 'Stock operation edited successfully')
104
+ : t('stockOperationAdded', 'Stock operation added successfully'),
105
+ });
106
+ } catch (error) {
107
+ const errorMessages = extractErrorMessagesFromResponse(error);
108
+ showSnackbar({
109
+ subtitle: errorMessages.join(', '),
110
+ title: t('errorSavingForm', 'Error on saving form'),
111
+ kind: 'error',
112
+ isLowContrast: true,
113
+ });
114
+ throw error;
115
+ }
116
+ })(); // Call handleSubmit to trigger validation and submission
117
+ return result; // Return the result after handleSubmit completes
118
+ }, [form, stockOperation, t, approvalRequired, isStockIssueOperation]);
119
+
120
+ const handleComplete = useCallback(() => {
121
+ handleSave().then((operation) => {
122
+ showActionDialogButton('Complete', false, { ...operation, status: 'COMPLETED' });
123
+ });
124
+ }, [handleSave]);
125
+ const handleSubmitForReview = useCallback(() => {
126
+ handleSave().then((operation) => {
127
+ showActionDialogButton('Submit', false, { ...operation, status: 'SUBMITTED' });
128
+ });
129
+ }, [handleSave]);
130
+ const handleDispatch = useCallback(() => {
131
+ handleSave().then((operation) => {
132
+ showActionDialogButton('Dispatch', false, { ...operation, status: 'DISPATCHED' });
133
+ });
134
+ }, [handleSave]);
135
+
136
+ return (
137
+ <Stack gap={4} className={styles.grid}>
138
+ <div className={styles.heading}>
139
+ <h4>
140
+ {operationTypePermision?.requiresDispatchAcknowledgement
141
+ ? t('submitAndDispatch', 'Submit/Dispatch')
142
+ : t('submitAndComplete', 'Submit/Complete')}
143
+ </h4>
144
+ <div className={styles.btnSet}>
145
+ {typeof onPrevious === 'function' && (
146
+ <Button kind="secondary" onClick={onPrevious}>
147
+ Previous
148
+ </Button>
149
+ )}
150
+ </div>
151
+ </div>
152
+
153
+ <Column>
154
+ <RadioButtonGroup
155
+ name="rbgApprovelRequired"
156
+ legendText={t('doesThisTransactionRequireApproval', 'Does the transaction require approval ?')}
157
+ onChange={handleRadioButtonChange}
158
+ readOnly={!editable}
159
+ valueSelected={approvalRequired === true}
160
+ >
161
+ <RadioButton value={true} id="rbgApprovelRequired-true" labelText={t('yes', 'Yes')} />
162
+ <RadioButton value={false} id="rbgApprovelRequired-false" labelText={t('no', 'No')} />
163
+ </RadioButtonGroup>
164
+ </Column>
165
+ {editable && (
166
+ <Column>
167
+ {approvalRequired != null && (
168
+ <>
169
+ {!operationTypePermision.requiresDispatchAcknowledgement && !approvalRequired && (
170
+ <Button
171
+ name="complete"
172
+ type="button"
173
+ style={{ margin: '4px' }}
174
+ className="submitButton"
175
+ kind="primary"
176
+ onClick={handleComplete}
177
+ renderIcon={ListChecked}
178
+ >
179
+ {t('complete', 'Complete')}
180
+ </Button>
181
+ )}
182
+ {operationTypePermision.requiresDispatchAcknowledgement && !approvalRequired && (
183
+ <Button
184
+ name="dispatch"
185
+ type="button"
186
+ style={{ margin: '4px' }}
187
+ className="submitButton"
188
+ kind="primary"
189
+ onClick={handleDispatch}
190
+ renderIcon={Departure}
191
+ >
192
+ {form.formState.isSubmitting ? (
193
+ <InlineLoading description={t('dispatching', 'Dispatching')} />
194
+ ) : (
195
+ t('dispatch', 'Dispatch')
196
+ )}
197
+ </Button>
198
+ )}
199
+ {approvalRequired && (
200
+ <Button
201
+ name="submit"
202
+ type="button"
203
+ style={{ margin: '4px' }}
204
+ className="submitButton"
205
+ kind="primary"
206
+ onClick={handleSubmitForReview}
207
+ renderIcon={SendFilled}
208
+ >
209
+ {form.formState.isSubmitting ? (
210
+ <InlineLoading description={t('submittingForReview', 'Submitting for review')} />
211
+ ) : (
212
+ t('submitForReview', 'Submit For Review')
213
+ )}
214
+ </Button>
215
+ )}
216
+ </>
217
+ )}
218
+ <Button
219
+ name="save"
220
+ type="button"
221
+ className="submitButton"
222
+ style={{ margin: '4px' }}
223
+ disabled={form.formState.isSubmitting}
224
+ kind="secondary"
225
+ onClick={handleSave}
226
+ renderIcon={Save}
227
+ >
228
+ {form.formState.isSubmitting ? <InlineLoading /> : t('save', 'Save')}
229
+ </Button>
230
+ </Column>
231
+ )}
232
+ </Stack>
233
+ );
234
+ };
235
+
236
+ export default StockOperationSubmissionFormStep;
@@ -0,0 +1,55 @@
1
+ import { showSnackbar } from '@openmrs/esm-framework';
2
+ import React, { useEffect, useMemo } from 'react';
3
+ import { useFormContext } from 'react-hook-form';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { OperationType, StockOperationType } from '../../core/api/types/stockOperation/StockOperationType';
6
+ import { useStockOperation } from '../stock-operations.resource';
7
+ import { getStockOperationItemFormSchema, StockOperationItemDtoSchema } from '../validation-schema';
8
+
9
+ type StockIssueFormInitializerWithRelatedRequisitionOperationProps = {
10
+ stockRequisitionUuid: string;
11
+ stockOperationType: StockOperationType;
12
+ };
13
+
14
+ const StockIssueFormInitializerWithRelatedRequisitionOperation: React.FC<
15
+ StockIssueFormInitializerWithRelatedRequisitionOperationProps
16
+ > = ({ stockRequisitionUuid, stockOperationType }) => {
17
+ const form = useFormContext<StockOperationItemDtoSchema>();
18
+ const { t } = useTranslation();
19
+ const { error, items: stockOperation, isLoading } = useStockOperation(stockRequisitionUuid);
20
+ const { setValue } = form;
21
+ const stockOperationItemFormSchema = useMemo(() => {
22
+ return getStockOperationItemFormSchema(OperationType.STOCK_ISSUE_OPERATION_TYPE);
23
+ }, []);
24
+ const items = form.watch('stockOperationItems');
25
+ // initialize form values with requisition values for Stock issue operation type
26
+ useEffect(() => {
27
+ if (stockOperation) {
28
+ // Initialize form with the values
29
+ setValue('sourceUuid', stockOperation.sourceUuid);
30
+ setValue('destinationUuid', stockOperation.destinationUuid);
31
+ setValue('requisitionStockOperationUuid', stockRequisitionUuid);
32
+ setValue('operationTypeUuid', stockOperationType.uuid);
33
+ }
34
+ }, [stockOperation, stockOperationItemFormSchema, setValue, items, stockOperationType, stockRequisitionUuid]);
35
+
36
+ // Handle errors encountered
37
+ useEffect(() => {
38
+ if (!stockRequisitionUuid)
39
+ showSnackbar({
40
+ kind: 'error',
41
+ title: t('stockIssueError', 'StockIssue error'),
42
+ subtitle: t('relatedStockRequisitionRequired', 'Related stock requisition Required'),
43
+ });
44
+ if (error) {
45
+ showSnackbar({
46
+ kind: 'error',
47
+ title: t('stockIssueError', 'StockIssue error'),
48
+ subtitle: error?.message,
49
+ });
50
+ }
51
+ }, [stockRequisitionUuid, error, t]);
52
+ return <React.Fragment />;
53
+ };
54
+
55
+ export default StockIssueFormInitializerWithRelatedRequisitionOperation;
@@ -0,0 +1,41 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ width: 100%;
10
+ height: 100%;
11
+ }
12
+
13
+ .grid {
14
+ margin: layout.$spacing-05 layout.$spacing-05;
15
+ padding: layout.$spacing-05 0 0 0;
16
+ }
17
+
18
+ .button {
19
+ display: flex;
20
+ align-content: flex-start;
21
+ align-items: baseline;
22
+ min-width: 50%;
23
+ }
24
+
25
+ .buttonSet {
26
+ display: flex;
27
+ justify-content: space-between;
28
+ width: 100%;
29
+ }
30
+
31
+ .datePickerInput span,
32
+ .datePickerInput div,
33
+ .datePickerInput input,
34
+ .datePickerInput {
35
+ min-width: 100%;
36
+ }
37
+
38
+ .title {
39
+ @include type.type-style('heading-02');
40
+ padding: layout.$spacing-03;
41
+ }