@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,72 @@
1
+ import { ComboBox, SelectSkeleton } from '@carbon/react';
2
+ import React, { useEffect, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
5
+ import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
6
+ import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
7
+ import { formatForDatePicker } from '../../../constants';
8
+
9
+ interface BatchNoSelectorProps {
10
+ stockItemUuid: string;
11
+ initialValue?: string;
12
+ onValueChange?: (value: string) => void;
13
+ error?: string;
14
+ }
15
+
16
+ const BatchNoSelector: React.FC<BatchNoSelectorProps> = ({ stockItemUuid, error, initialValue, onValueChange }) => {
17
+ const { isLoading, stockItemBatchNos } = useStockItemBatchNumbers(stockItemUuid);
18
+ const { t } = useTranslation();
19
+
20
+ const { items, setStockItemUuid, isLoading: isLoadingBatchinfo } = useStockItemBatchInformationHook();
21
+
22
+ useEffect(() => {
23
+ setStockItemUuid(stockItemUuid);
24
+ }, [stockItemUuid, setStockItemUuid]);
25
+
26
+ const stockItemBatchesInfo = useMemo(() => {
27
+ return stockItemBatchNos?.map((item) => {
28
+ const matchingBatch = items?.find((batch) => batch.batchNumber === item.batchNo);
29
+ if (matchingBatch) {
30
+ return {
31
+ ...item,
32
+ quantity: matchingBatch.quantity ?? '',
33
+ };
34
+ }
35
+ return item;
36
+ });
37
+ }, [stockItemBatchNos, items]);
38
+
39
+ const filteredBatches = useMemo(() => {
40
+ return stockItemBatchesInfo?.filter((s) => s.quantity !== undefined && s.quantity !== 0);
41
+ }, [stockItemBatchesInfo]);
42
+ const initialSelectedItem = useMemo(
43
+ () => filteredBatches?.find((s) => s.uuid === initialValue),
44
+ [filteredBatches, initialValue],
45
+ );
46
+
47
+ if (isLoading || isLoadingBatchinfo) return <SelectSkeleton role="progressbar" />;
48
+
49
+ return (
50
+ <ComboBox
51
+ style={{ flexGrow: '1' }}
52
+ titleText={t('batchNo', 'Batch')}
53
+ name={'stockBatchUuid'}
54
+ id={'stockBatchUuid'}
55
+ items={filteredBatches || []}
56
+ onChange={(data: { selectedItem?: StockBatchDTO }) => {
57
+ onValueChange(data.selectedItem?.uuid);
58
+ }}
59
+ selectedItem={initialSelectedItem}
60
+ itemToString={(s: StockBatchDTO) =>
61
+ s?.batchNo
62
+ ? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
63
+ : ''
64
+ }
65
+ placeholder={t('filter', "'Filter") + '...'}
66
+ invalid={error}
67
+ invalidText={error}
68
+ />
69
+ );
70
+ };
71
+
72
+ export default BatchNoSelector;
@@ -0,0 +1,90 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
4
+ import { StockItemInventory } from '../../../core/api/types/stockItem/StockItemInventory';
5
+ import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
6
+ import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
7
+ import BatchNoSelector from './batch-no-selector.component';
8
+ import { formatForDatePicker } from '../../../constants';
9
+
10
+ jest.mock('../hooks/useStockItemBatchNumbers');
11
+ jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource');
12
+ jest.mock('react-i18next', () => ({
13
+ useTranslation: () => ({ t: (key: string) => key }),
14
+ }));
15
+
16
+ const mockUseStockItemBatchNumbers = useStockItemBatchNumbers as jest.Mock;
17
+ const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
18
+
19
+ describe('BatchNoSelector', () => {
20
+ const mockOnValueChange = jest.fn();
21
+ const mockStockItemUuid = 'test-uuid';
22
+ const mockExpiration = new Date();
23
+ beforeEach(() => {
24
+ jest.clearAllMocks();
25
+
26
+ mockUseStockItemBatchNumbers.mockReturnValue({
27
+ isLoading: false,
28
+ stockItemBatchNos: [
29
+ { uuid: '1', batchNo: 'BATCH-001', quantity: 10, expiration: mockExpiration },
30
+ { uuid: '2', batchNo: 'BATCH-002', quantity: 20, expiration: mockExpiration },
31
+ ],
32
+ });
33
+ mockUseStockItemBatchInformationHook.mockReturnValue({
34
+ items: [
35
+ { batchNumber: 'BATCH-001', quantity: 10 },
36
+ { batchNumber: 'BATCH-002', quantity: 20 },
37
+ ] as StockItemInventory[],
38
+ setStockItemUuid: jest.fn(),
39
+ });
40
+ });
41
+
42
+ it('should render loading skeleton when isLoading is true', () => {
43
+ mockUseStockItemBatchNumbers.mockReturnValue({ isLoading: true, stockItemBatchNos: [] });
44
+ mockUseStockItemBatchInformationHook.mockReturnValue({ isLoading: true, items: [], setStockItemUuid: jest.fn() });
45
+ render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
46
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
47
+ });
48
+
49
+ it('should render combobox with batch numbers', async () => {
50
+ render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
51
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
52
+ expect(screen.getByText('batchNo')).toBeInTheDocument();
53
+ });
54
+
55
+ it('should handle batch selection', async () => {
56
+ render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
57
+ const combobox = screen.getByRole('combobox');
58
+ await userEvent.click(combobox);
59
+ await userEvent.type(combobox, 'BATCH-001');
60
+ const option = screen.getByText(`BATCH-001 | Qty: 10 | Expiry: ${formatForDatePicker(mockExpiration)}`);
61
+ await userEvent.click(option);
62
+ expect(mockOnValueChange).toHaveBeenCalledWith('1');
63
+ });
64
+
65
+ it('should display error message when error prop is provided', () => {
66
+ const errorMessage = 'This is an error';
67
+ render(
68
+ <BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} error={errorMessage} />,
69
+ );
70
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
71
+ });
72
+
73
+ it('should filter out batches with zero or undefined quantity', async () => {
74
+ mockUseStockItemBatchNumbers.mockReturnValue({
75
+ isLoading: false,
76
+ stockItemBatchNos: [
77
+ { uuid: '1', batchNo: 'BATCH-001', quantity: 10 },
78
+ { uuid: '2', batchNo: 'BATCH-002', quantity: 0 },
79
+ { uuid: '3', batchNo: 'BATCH-003', quantity: undefined },
80
+ ],
81
+ });
82
+
83
+ render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
84
+ const combobox = screen.getByRole('combobox');
85
+ await userEvent.click(combobox);
86
+
87
+ expect(screen.queryByText('BATCH-002')).not.toBeInTheDocument();
88
+ expect(screen.queryByText('BATCH-003')).not.toBeInTheDocument();
89
+ });
90
+ });
@@ -1,6 +1,6 @@
1
1
  @use '@carbon/colors';
2
2
  @use '@carbon/type';
3
- @use '@carbon/styles/scss/spacing';
3
+ @use '@carbon/layout';
4
4
  @use '@openmrs/esm-styleguide/src/vars' as *;
5
5
 
6
6
  // Patient List Table
@@ -25,7 +25,7 @@
25
25
  .stockItemSearchContainer {
26
26
  position: relative;
27
27
  flex-direction: column;
28
- margin-bottom: spacing.$spacing-01;
28
+ margin-bottom: layout.$spacing-01;
29
29
  }
30
30
 
31
31
  .searchResults {
@@ -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,79 @@
1
+ import { ClickableTile, Search } from '@carbon/react';
2
+ import { useConfig, useDebounce } from '@openmrs/esm-framework';
3
+ import React, { useCallback, 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
+ import { ConfigObject } from '../../../config-schema';
9
+
10
+ type StockItemSearchProps = {
11
+ onSelectedItem?: (stockItem: StockItemDTO) => void;
12
+ };
13
+
14
+ const StockItemSearch: React.FC<StockItemSearchProps> = ({ onSelectedItem }) => {
15
+ const { t } = useTranslation();
16
+ const { isLoading, stockItemsList, setSearchString } = useFilterableStockItems({});
17
+ const [searchTerm, setSearchTerm] = useState('');
18
+ const debouncedSearchTerm = useDebounce(searchTerm);
19
+ const { useItemCommonNameAsDisplay } = useConfig<ConfigObject>();
20
+ const getDrugName = useCallback(
21
+ (item: StockItemDTO) => {
22
+ if (useItemCommonNameAsDisplay) return;
23
+ const commonName = item?.commonName ? `(Common name: ${item.commonName})` : undefined;
24
+ return `${item?.drugName || t('noDrugNameAvailable', 'No drug name available') + (commonName ?? '')}`;
25
+ },
26
+ [useItemCommonNameAsDisplay, t],
27
+ );
28
+
29
+ const getCommonName = useCallback(
30
+ (item: StockItemDTO) => {
31
+ if (!useItemCommonNameAsDisplay) return;
32
+ const drugName = item?.drugName ? `(Drug name: ${item.drugName})` : undefined;
33
+ return `${item?.commonName || t('noCommonNameAvailable', 'No common name available') + (drugName ?? '')}`;
34
+ },
35
+ [useItemCommonNameAsDisplay, t],
36
+ );
37
+ useEffect(() => {
38
+ if (debouncedSearchTerm?.length !== 0) {
39
+ setSearchString(debouncedSearchTerm);
40
+ }
41
+ }, [debouncedSearchTerm, setSearchString]);
42
+
43
+ const handleOnSearchResultClick = (stockItem: StockItemDTO) => {
44
+ onSelectedItem?.(stockItem);
45
+ setSearchTerm('');
46
+ };
47
+
48
+ return (
49
+ <div className={styles.stockItemSearchContainer}>
50
+ <div style={{ display: 'flex' }}>
51
+ <Search
52
+ size="lg"
53
+ placeholder={t('findItems', 'Find your items')}
54
+ labelText={t('search', 'Search')}
55
+ closeButtonLabelText={t('clearSearch', 'Clear search input')}
56
+ value={searchTerm}
57
+ id="search-stock-operation-item"
58
+ name="search-stock-operation-item"
59
+ onChange={(e) => setSearchTerm(e.target.value)}
60
+ />
61
+ </div>
62
+ {searchTerm && stockItemsList?.length > 0 && (
63
+ <div className={styles.searchResults}>
64
+ {stockItemsList?.slice(0, 5).map((stockItem) => {
65
+ const commonName = getCommonName(stockItem);
66
+ const drugName = getDrugName(stockItem);
67
+ return (
68
+ <ClickableTile onClick={() => handleOnSearchResultClick(stockItem)} key={stockItem?.uuid}>
69
+ {useItemCommonNameAsDisplay ? commonName : drugName}
70
+ </ClickableTile>
71
+ );
72
+ })}
73
+ </div>
74
+ )}
75
+ </div>
76
+ );
77
+ };
78
+
79
+ 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;