@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,157 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
4
+ import { useStockItem } from '../../../stock-items/stock-items.resource';
5
+ import QtyUomSelector from './quantity-uom-selector.component';
6
+
7
+ jest.mock('../../../stock-items/stock-items.resource');
8
+ jest.mock('react-i18next', () => ({
9
+ useTranslation: () => ({ t: (key: string) => key }),
10
+ }));
11
+
12
+ const mockUseStockItem = useStockItem as jest.Mock;
13
+
14
+ describe('QtyUOMSelector', () => {
15
+ const mockOnValueChange = jest.fn();
16
+ const mockStockItemUuid = 'test-uuid';
17
+ const mockitem = {
18
+ uuid: '33225466-93c8-4720-b110-4f445f3764c6',
19
+ drugUuid: '1776AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
20
+ drugName: 'Ibuprofen Injection solution 5mg/mL ',
21
+ conceptUuid: '77897AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
22
+ conceptName: 'IBUPROFEN',
23
+ hasExpiration: true,
24
+ preferredVendorUuid: 'b7e481ef-06e7-46ba-8a7c-ab077aa3355f',
25
+ preferredVendorName: 'Kenya Medical Supplies Authority',
26
+ purchasePrice: 100.0,
27
+ purchasePriceUoMUuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
28
+ purchasePriceUoMName: 'Box',
29
+ purchasePriceUoMFactor: 10.0,
30
+ dispensingUnitName: 'Tablet',
31
+ dispensingUnitUuid: '1513AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
32
+ dispensingUnitPackagingUoMUuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
33
+ dispensingUnitPackagingUoMName: 'Box',
34
+ dispensingUnitPackagingUoMFactor: 10.0,
35
+ defaultStockOperationsUoMUuid: null,
36
+ defaultStockOperationsUoMName: null,
37
+ defaultStockOperationsUoMFactor: null,
38
+ categoryUuid: '183f9497-b72e-4ebe-ae17-5dcf110ff3b6',
39
+ categoryName: 'Other Lymph Node Comments',
40
+ commonName: 'Ibuprofen 5 MG/ML Injectable Solution',
41
+ acronym: null,
42
+ reorderLevel: null,
43
+ reorderLevelUoMUuid: null,
44
+ reorderLevelUoMName: null,
45
+ reorderLevelUoMFactor: null,
46
+ dateCreated: '2024-09-03T09:46:47.000+0300',
47
+ creatorGivenName: 'Mark',
48
+ creatorFamilyName: 'Miller',
49
+ voided: false,
50
+ expiryNotice: 180,
51
+ permission: {
52
+ canView: true,
53
+ canEdit: true,
54
+ },
55
+ packagingUnits: [
56
+ {
57
+ uuid: '8023ef7d-56da-442f-bc74-319f000840b4',
58
+ factor: 1.0,
59
+ packagingUomUuid: '162382AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
60
+ packagingUomName: 'Vial',
61
+ stockItemUuid: '33225466-93c8-4720-b110-4f445f3764c6',
62
+ isDefaultStockOperationsUoM: false,
63
+ isDispensingUnit: false,
64
+ links: [
65
+ {
66
+ rel: 'full',
67
+ uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitempackaginguom/8023ef7d-56da-442f-bc74-319f000840b4?v=full',
68
+ resourceAlias: 'stockitempackaginguom',
69
+ },
70
+ ],
71
+ resourceVersion: '1.8',
72
+ },
73
+ {
74
+ uuid: '65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee',
75
+ factor: 10.0,
76
+ packagingUomUuid: '162396AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
77
+ packagingUomName: 'Box',
78
+ stockItemUuid: '33225466-93c8-4720-b110-4f445f3764c6',
79
+ isDefaultStockOperationsUoM: false,
80
+ isDispensingUnit: true,
81
+ links: [
82
+ {
83
+ rel: 'full',
84
+ uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitempackaginguom/65dc9ec7-0663-4cf0-a35c-6a2d2cf99eee?v=full',
85
+ resourceAlias: 'stockitempackaginguom',
86
+ },
87
+ ],
88
+ resourceVersion: '1.8',
89
+ },
90
+ ],
91
+ references: [],
92
+ links: [
93
+ {
94
+ rel: 'self',
95
+ uri: 'http://hie.kenyahmis.org/openmrs/ws/rest/v1/stockmanagement/stockitem/33225466-93c8-4720-b110-4f445f3764c6',
96
+ resourceAlias: 'stockitem',
97
+ },
98
+ ],
99
+ resourceVersion: '1.8',
100
+ };
101
+
102
+ beforeEach(() => {
103
+ jest.clearAllMocks();
104
+ mockUseStockItem.mockReturnValue({
105
+ isLoading: false,
106
+ item: mockitem,
107
+ });
108
+ });
109
+ it('should render Skeleton when loading stock item', () => {
110
+ mockUseStockItem.mockReturnValue({ isLoading: true });
111
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
112
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
113
+ });
114
+ it('should render Inline notification error when error ocuured while fetching item', () => {
115
+ const errorMessage = 'error loading stock item';
116
+ mockUseStockItem.mockReturnValue({ isLoading: false, error: new Error(errorMessage) });
117
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
118
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
119
+ expect(screen.getByRole('status')).toBeInTheDocument();
120
+ });
121
+ it('should render Inline notification error when error ocuured while fetching item', () => {
122
+ const errorMessage = 'error loading stock item';
123
+ mockUseStockItem.mockReturnValue({ isLoading: false, error: new Error(errorMessage) });
124
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
125
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
126
+ expect(screen.getByRole('status')).toBeInTheDocument();
127
+ });
128
+
129
+ it('should display error message when error prop is provided', () => {
130
+ const errorMessage = 'This is an error';
131
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />);
132
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
133
+ });
134
+ it('should display error message when error prop is provided', () => {
135
+ const errorMessage = 'This is an error';
136
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />);
137
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
138
+ });
139
+
140
+ it('should render packing uom options', async () => {
141
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
142
+ const combobox = screen.getByRole('combobox');
143
+ await userEvent.click(combobox);
144
+ mockitem.packagingUnits.forEach(({ factor, packagingUomName }) => {
145
+ expect(screen.getByText(`${packagingUomName} - ${factor}`)).toBeInTheDocument();
146
+ });
147
+ });
148
+ it('should packing uom selection', async () => {
149
+ const itemToSelect = mockitem.packagingUnits[1];
150
+ render(<QtyUomSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
151
+ const combobox = screen.getByRole('combobox');
152
+ await userEvent.click(combobox);
153
+ const option = screen.getByText(`${itemToSelect.packagingUomName} - ${itemToSelect.factor}`);
154
+ await userEvent.click(option);
155
+ expect(mockOnValueChange).toHaveBeenCalledWith(itemToSelect.uuid);
156
+ });
157
+ });
@@ -0,0 +1,53 @@
1
+ import { ComboBox, InlineNotification, SkeletonText } from '@carbon/react';
2
+ import React, { useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { StockItemPackagingUOMDTO } from '../../../core/api/types/stockItem/StockItemPackagingUOM';
5
+ import { useStockItem } from '../../../stock-items/stock-items.resource';
6
+
7
+ interface QtyUomSelectorProps {
8
+ stockItemUuid: string;
9
+ intiallvalue?: string;
10
+ onValueChange?: (value: string) => void;
11
+ error?: string;
12
+ }
13
+
14
+ const QtyUomSelector: React.FC<QtyUomSelectorProps> = ({ stockItemUuid, error, intiallvalue, onValueChange }) => {
15
+ const { t } = useTranslation();
16
+ const { isLoading, error: stockItemError, item } = useStockItem(stockItemUuid);
17
+ const initialSelectedItem = useMemo<StockItemPackagingUOMDTO | null>(
18
+ () => (item?.packagingUnits ?? []).find((u) => u.uuid === intiallvalue),
19
+ [item?.packagingUnits, intiallvalue],
20
+ );
21
+
22
+ if (isLoading) return <SkeletonText role="progressbar" />;
23
+
24
+ if (stockItemError)
25
+ return (
26
+ <InlineNotification
27
+ kind="error"
28
+ title={t('packagingUomError', 'Error loading Stock item')}
29
+ subtitle={stockItemError?.message}
30
+ />
31
+ );
32
+
33
+ return (
34
+ <ComboBox
35
+ titleText={t('quantityUom', 'Qty UoM')}
36
+ name={'stockItemPackagingUOMUuid'}
37
+ id={'stockItemPackagingUOMUuid'}
38
+ items={item?.packagingUnits ?? []}
39
+ onChange={(data: { selectedItem?: StockItemPackagingUOMDTO }) => {
40
+ onValueChange?.(data.selectedItem?.uuid);
41
+ }}
42
+ initialSelectedItem={initialSelectedItem}
43
+ itemToString={(s: StockItemPackagingUOMDTO) =>
44
+ s.packagingUomName ? `${s?.packagingUomName} - ${s?.factor} ` : ''
45
+ }
46
+ placeholder={t('filter', 'Filter') + '...'}
47
+ invalid={error}
48
+ invalidText={error}
49
+ />
50
+ );
51
+ };
52
+
53
+ export default QtyUomSelector;
@@ -0,0 +1,56 @@
1
+ import { ClickableTile, Search } from '@carbon/react';
2
+ import { useDebounce } from '@openmrs/esm-framework';
3
+ import React, { useEffect, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { StockItemDTO } from '../../../core/api/types/stockItem/StockItem';
6
+ import { useFilterableStockItems } from '../hooks/useFilterableStockItems';
7
+ import styles from './input-components-styles.scss';
8
+
9
+ type StockItemSearchProps = {
10
+ onSelectedItem?: (stockItem: StockItemDTO) => void;
11
+ };
12
+
13
+ const StockItemSearch: React.FC<StockItemSearchProps> = ({ onSelectedItem }) => {
14
+ const { t } = useTranslation();
15
+ const { isLoading, stockItemsList, setSearchString } = useFilterableStockItems({});
16
+ const [searchTerm, setSearchTerm] = useState('');
17
+ const debouncedSearchTerm = useDebounce(searchTerm);
18
+
19
+ useEffect(() => {
20
+ if (debouncedSearchTerm?.length !== 0) {
21
+ setSearchString(debouncedSearchTerm);
22
+ }
23
+ }, [debouncedSearchTerm, setSearchString]);
24
+
25
+ const handleOnSearchResultClick = (stockItem: StockItemDTO) => {
26
+ onSelectedItem?.(stockItem);
27
+ setSearchTerm('');
28
+ };
29
+ return (
30
+ <div className={styles.stockItemSearchContainer}>
31
+ <div style={{ display: 'flex' }}>
32
+ <Search
33
+ size="lg"
34
+ placeholder={t('findItems', 'Find your items')}
35
+ labelText={t('search', 'Search')}
36
+ closeButtonLabelText={t('clearSearch', 'Clear search input')}
37
+ value={searchTerm}
38
+ id="search-stock-operation-item"
39
+ name="search-stock-operation-item"
40
+ onChange={(e) => setSearchTerm(e.target.value)}
41
+ />
42
+ </div>
43
+ {searchTerm && stockItemsList?.length > 0 && (
44
+ <div className={styles.searchResults}>
45
+ {stockItemsList?.slice(0, 5).map((stockItem) => (
46
+ <ClickableTile onClick={() => handleOnSearchResultClick(stockItem)} key={stockItem?.uuid}>
47
+ {stockItem?.commonName}
48
+ </ClickableTile>
49
+ ))}
50
+ </div>
51
+ )}
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default StockItemSearch;
@@ -0,0 +1,59 @@
1
+ import { ComboBox, InlineNotification, SelectSkeleton } from '@carbon/react';
2
+ import { useConfig } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { Controller, useFormContext } from 'react-hook-form';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { ConfigObject } from '../../../config-schema';
7
+ import { Concept } from '../../../core/api/types/concept/Concept';
8
+ import { useConcept } from '../../../stock-lookups/stock-lookups.resource';
9
+
10
+ const StockOperationReasonSelector = () => {
11
+ const { stockAdjustmentReasonUUID } = useConfig<ConfigObject>();
12
+ const form = useFormContext<{ reasonUuid: string }>();
13
+ const {
14
+ isLoading,
15
+ error,
16
+ items: { answers: reasons },
17
+ } = useConcept(stockAdjustmentReasonUUID);
18
+ const { t } = useTranslation();
19
+ if (isLoading) return <SelectSkeleton role="progressbar" />;
20
+
21
+ if (error)
22
+ return (
23
+ <InlineNotification
24
+ lowContrast
25
+ kind="error"
26
+ title={t('reasonError', 'Error loading reasons concepts')}
27
+ subtitle={error?.message}
28
+ />
29
+ );
30
+
31
+ return (
32
+ <Controller
33
+ control={form.control}
34
+ name={'reasonUuid'}
35
+ render={({ field, fieldState: { error } }) => (
36
+ <ComboBox
37
+ readOnly={field.disabled}
38
+ titleText={t('reason', 'Reason')}
39
+ placeholder={t('chooseAReason', 'Choose a reason')}
40
+ name={'reasonUuid'}
41
+ id={'reasonUuid'}
42
+ size={'xl'}
43
+ items={reasons}
44
+ initialSelectedItem={reasons?.find((p) => p.uuid === field.value)}
45
+ selectedItem={reasons.find((p) => p.uuid === field.value)}
46
+ itemToString={(item?: Concept) => (item && item?.display ? `${item?.display}` : '')}
47
+ onChange={(data: { selectedItem?: Concept }) => {
48
+ field.onChange(data?.selectedItem?.uuid);
49
+ }}
50
+ ref={field.ref}
51
+ invalid={error?.message}
52
+ invalidText={error?.message}
53
+ />
54
+ )}
55
+ />
56
+ );
57
+ };
58
+
59
+ export default StockOperationReasonSelector;
@@ -0,0 +1,216 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
4
+
5
+ import { useConfig } from '@openmrs/esm-framework';
6
+ import { useFormContext } from 'react-hook-form';
7
+ import { useConcept } from '../../../stock-lookups/stock-lookups.resource';
8
+ import StockOperationReasonSelector from './stock-operation-reason-selector.component';
9
+
10
+ // Mock the hooks
11
+ jest.mock('@openmrs/esm-framework', () => ({
12
+ useConfig: jest.fn(),
13
+ }));
14
+
15
+ jest.mock('react-hook-form', () => ({
16
+ useFormContext: jest.fn(),
17
+ Controller: ({ render }) => render({ field: {}, fieldState: {} }),
18
+ }));
19
+ jest.mock('../../../stock-lookups/stock-lookups.resource');
20
+ jest.mock('react-i18next', () => ({
21
+ useTranslation: () => ({ t: (key: string) => key }),
22
+ }));
23
+
24
+ const mockUseConcept = useConcept as jest.Mock;
25
+ const mockUseConfig = useConfig as jest.Mock;
26
+ const mockUseFormContext = useFormContext as jest.Mock;
27
+
28
+ describe('StockoperationReasonSelector', () => {
29
+ const mockConcepts = {
30
+ uuid: '3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25',
31
+ display: 'Stock Adjustment Reason',
32
+ name: {
33
+ display: 'Stock Adjustment Reason',
34
+ uuid: '4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
35
+ name: 'Stock Adjustment Reason',
36
+ locale: 'en',
37
+ localePreferred: true,
38
+ conceptNameType: 'FULLY_SPECIFIED',
39
+ links: [
40
+ {
41
+ rel: 'self',
42
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
43
+ resourceAlias: 'name',
44
+ },
45
+ {
46
+ rel: 'full',
47
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc?v=full',
48
+ resourceAlias: 'name',
49
+ },
50
+ ],
51
+ resourceVersion: '1.9',
52
+ },
53
+ datatype: {
54
+ uuid: '8d4a48b6-c2cc-11de-8d13-0010c6dffd0f',
55
+ display: 'Coded',
56
+ links: [
57
+ {
58
+ rel: 'self',
59
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/conceptdatatype/8d4a48b6-c2cc-11de-8d13-0010c6dffd0f',
60
+ resourceAlias: 'conceptdatatype',
61
+ },
62
+ ],
63
+ },
64
+ conceptClass: {
65
+ uuid: '8d491e50-c2cc-11de-8d13-0010c6dffd0f',
66
+ display: 'Question',
67
+ links: [
68
+ {
69
+ rel: 'self',
70
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/conceptclass/8d491e50-c2cc-11de-8d13-0010c6dffd0f',
71
+ resourceAlias: 'conceptclass',
72
+ },
73
+ ],
74
+ },
75
+ set: false,
76
+ version: null,
77
+ retired: false,
78
+ names: [
79
+ {
80
+ uuid: '4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
81
+ display: 'Stock Adjustment Reason',
82
+ links: [
83
+ {
84
+ rel: 'self',
85
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/4eb6556a-a2d4-4e85-9b62-4d076a1063fc',
86
+ resourceAlias: 'name',
87
+ },
88
+ ],
89
+ },
90
+ {
91
+ uuid: '36eb0855-c810-4816-9bed-1de5c615e702',
92
+ display: 'Stock Adjustment Reason',
93
+ links: [
94
+ {
95
+ rel: 'self',
96
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/name/36eb0855-c810-4816-9bed-1de5c615e702',
97
+ resourceAlias: 'name',
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ descriptions: [
103
+ {
104
+ uuid: '67311e07-1935-448b-8305-7d11abf0de63',
105
+ display: 'Stock Adjustment Reason',
106
+ links: [
107
+ {
108
+ rel: 'self',
109
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25/description/67311e07-1935-448b-8305-7d11abf0de63',
110
+ resourceAlias: 'description',
111
+ },
112
+ ],
113
+ },
114
+ ],
115
+ mappings: [],
116
+ answers: [
117
+ {
118
+ uuid: '165420AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
119
+ display: 'Drug not available due to expired medication',
120
+ links: [
121
+ {
122
+ rel: 'self',
123
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/165420AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
124
+ resourceAlias: 'concept',
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ uuid: '160584AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
130
+ display: 'Lost or ran out of medication',
131
+ links: [
132
+ {
133
+ rel: 'self',
134
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/160584AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
135
+ resourceAlias: 'concept',
136
+ },
137
+ ],
138
+ },
139
+ {
140
+ uuid: '122835AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
141
+ display: 'Work Shift Change',
142
+ links: [
143
+ {
144
+ rel: 'self',
145
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/122835AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
146
+ resourceAlias: 'concept',
147
+ },
148
+ ],
149
+ },
150
+ {
151
+ uuid: '160561AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
152
+ display: 'New drug available',
153
+ links: [
154
+ {
155
+ rel: 'self',
156
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/160561AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
157
+ resourceAlias: 'concept',
158
+ },
159
+ ],
160
+ },
161
+ ],
162
+ setMembers: [],
163
+ attributes: [],
164
+ links: [
165
+ {
166
+ rel: 'self',
167
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25',
168
+ resourceAlias: 'concept',
169
+ },
170
+ {
171
+ rel: 'full',
172
+ uri: 'http://qa.kenyahmis.org/openmrs/ws/rest/v1/concept/3bbfaa44-d5b8-404d-b4c1-2bf49ad8ce25?v=full',
173
+ resourceAlias: 'concept',
174
+ },
175
+ ],
176
+ resourceVersion: '2.0',
177
+ };
178
+ beforeEach(() => {
179
+ jest.clearAllMocks();
180
+ mockUseConfig.mockReturnValue({ stockAdjustmentReasonUUID: 'uuid' });
181
+ mockUseConcept.mockReturnValue({
182
+ isLoading: false,
183
+ items: mockConcepts,
184
+ });
185
+ });
186
+
187
+ it('should display loading state while loading reason concepts', () => {
188
+ mockUseFormContext.mockReturnValue({ control: {} });
189
+ mockUseConcept.mockReturnValue({
190
+ isLoading: true,
191
+ items: mockConcepts,
192
+ });
193
+ render(<StockOperationReasonSelector />);
194
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
195
+ });
196
+ it('should display error notification when error encountered while fetching concepts', () => {
197
+ const errorMessahe = 'Error message';
198
+ mockUseConcept.mockReturnValue({
199
+ isLoading: false,
200
+ items: mockConcepts,
201
+ error: new Error(errorMessahe),
202
+ });
203
+ render(<StockOperationReasonSelector />);
204
+ expect(screen.getByRole('status')).toBeInTheDocument();
205
+ expect(screen.getByText(errorMessahe)).toBeInTheDocument();
206
+ });
207
+
208
+ it('renders ComboBox with reasons', async () => {
209
+ render(<StockOperationReasonSelector />);
210
+ const combobox = screen.getByRole('combobox');
211
+ await userEvent.click(combobox);
212
+ mockConcepts.answers.forEach((ans) => {
213
+ expect(screen.getByText(`${ans?.display}`)).toBeInTheDocument();
214
+ });
215
+ });
216
+ });
@@ -1,31 +1,35 @@
1
1
  import { TextInput } from '@carbon/react';
2
2
  import React, { ChangeEvent, useEffect, useMemo, useState } from 'react';
3
- import { useStockItemBatchNos } from './batch-no-selector.resource';
4
3
  import { TextInputSkeleton } from '@carbon/react';
4
+ import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
5
+ import { useTranslation } from 'react-i18next';
5
6
 
6
7
  type UniqueBatchNoEntryInputProps = {
7
8
  defaultValue?: string;
8
9
  onValueChange?: (value: string) => void;
9
10
  error?: string;
10
11
  stockItemUuid: string;
12
+ stockOperationItemUuid: string;
11
13
  };
12
14
  const UniqueBatchNoEntryInput: React.FC<UniqueBatchNoEntryInputProps> = ({
13
15
  defaultValue,
14
16
  onValueChange,
15
17
  error,
16
18
  stockItemUuid,
19
+ stockOperationItemUuid,
17
20
  }) => {
18
- const { isLoading, stockItemBatchNos } = useStockItemBatchNos(stockItemUuid);
21
+ const { isLoading, stockItemBatchNos } = useStockItemBatchNumbers(stockItemUuid);
19
22
  const [value, setValue] = useState(defaultValue);
20
23
  const [_error, setError] = useState<string>();
21
-
24
+ const { t } = useTranslation();
25
+ const isNewItem = useMemo(() => stockOperationItemUuid.startsWith('new-item'), [stockOperationItemUuid]);
22
26
  const batchNoAlreadyUsed = useMemo(
23
- () => stockItemBatchNos?.findIndex((batchNo) => batchNo.batchNo === value) !== -1,
24
- [stockItemBatchNos, value],
27
+ () => isNewItem && stockItemBatchNos?.findIndex((batchNo) => batchNo.batchNo === value) !== -1,
28
+ [stockItemBatchNos, value, isNewItem],
25
29
  );
26
30
 
27
31
  useEffect(() => {
28
- if (defaultValue) setValue(defaultValue);
32
+ if (defaultValue) setValue(defaultValue ?? '');
29
33
  }, [defaultValue]);
30
34
 
31
35
  useEffect(() => {
@@ -41,12 +45,13 @@ const UniqueBatchNoEntryInput: React.FC<UniqueBatchNoEntryInputProps> = ({
41
45
 
42
46
  return (
43
47
  <TextInput
44
- size="sm"
45
48
  maxLength={50}
46
49
  onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
47
50
  value={value}
48
51
  invalidText={_error ?? error}
49
52
  invalid={_error ?? error}
53
+ placeholder={t('batchNumber', 'Batch Number')}
54
+ labelText={t('batchNumber', 'Batch Number')}
50
55
  />
51
56
  );
52
57
  };