@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
@@ -1,50 +1,50 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
 
3
3
  import { Button } from '@carbon/react';
4
- import { useTranslation } from 'react-i18next';
5
4
  import { Printer } from '@carbon/react/icons';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { StockItemInventory } from '../../core/api/types/stockItem/StockItemInventory';
6
7
  import { StockOperationDTO } from '../../core/api/types/stockOperation/StockOperationDTO';
7
8
  import { StockOperationItemCost } from '../../core/api/types/stockOperation/StockOperationItemCost';
8
- import { StockItemInventory } from '../../core/api/types/stockItem/StockItemInventory';
9
9
 
10
- import { StockItemInventoryFilter } from '../../stock-items/stock-items.resource';
10
+ import { InlineLoading } from '@carbon/react';
11
+ import { showSnackbar } from '@openmrs/esm-framework';
12
+ import { extractErrorMessagesFromResponse } from '../../constants';
11
13
  import { ResourceRepresentation } from '../../core/api/api';
12
- import { BuildStockOperationData } from '../stock-print-reports/StockOperationReport';
14
+ import { OperationType } from '../../core/api/types/stockOperation/StockOperationType';
15
+ import { StockItemInventoryFilter } from '../../stock-items/stock-items.resource';
16
+ import {
17
+ getStockItemInventory,
18
+ getStockOperation,
19
+ getStockOperationItemsCost,
20
+ useStockOperationAndItems,
21
+ } from '../stock-operations.resource';
13
22
  import { PrintGoodsReceivedNoteStockOperation } from '../stock-print-reports/GoodsReceivedNote';
14
- import { PrintTransferOutStockOperation } from '../stock-print-reports/StockTransferDocument';
15
23
  import { PrintRequisitionStockOperation } from '../stock-print-reports/RequisitionDocument';
16
- import { getStockItemInventory, getStockOperation, getStockOperationItemsCost } from '../stock-operations.resource';
24
+ import { BuildStockOperationData } from '../stock-print-reports/StockOperationReport';
25
+ import { PrintTransferOutStockOperation } from '../stock-print-reports/StockTransferDocument';
17
26
 
18
27
  interface StockOperationCancelButtonProps {
19
28
  operation: StockOperationDTO;
20
29
  }
21
30
 
22
- const StockOperationPrintButton: React.FC<StockOperationCancelButtonProps> = ({ operation }) => {
31
+ const StockOperationPrintButton: React.FC<StockOperationCancelButtonProps> = ({ operation: _operation }) => {
23
32
  const { t } = useTranslation();
24
-
33
+ const { isLoading, items: operation, error } = useStockOperationAndItems(_operation.uuid);
34
+ const [loading, setLoading] = useState(false);
25
35
  const onPrintStockOperation = async () => {
36
+ setLoading(true);
26
37
  try {
27
38
  let parentOperation: StockOperationDTO | null | undefined;
28
39
  let itemsCost: StockOperationItemCost[] | null | undefined = null;
29
40
  let itemInventory: StockItemInventory[] | null | undefined = null;
30
41
 
31
42
  if (operation.requisitionStockOperationUuid) {
32
- // get stock operation
33
- getStockOperation(operation.requisitionStockOperationUuid)
34
- .then((payload: any) => {
35
- if ((payload as any).error) {
36
- return;
37
- }
38
- parentOperation = payload;
39
- })
40
- .catch((error: any) => {
41
- if ((error as any).error) {
42
- return;
43
- }
44
- return;
45
- });
43
+ // get related requisition stock operation
44
+ const response = await getStockOperation(operation.requisitionStockOperationUuid);
45
+ parentOperation = response.data;
46
46
  if (!parentOperation) {
47
- return null;
47
+ return;
48
48
  }
49
49
  }
50
50
 
@@ -59,19 +59,8 @@ const StockOperationPrintButton: React.FC<StockOperationCancelButtonProps> = ({
59
59
  if (operation?.uuid) {
60
60
  inventoryFilter.stockOperationUuid = operation.uuid;
61
61
  }
62
- getStockOperationItemsCost(inventoryFilter)
63
- .then((payload: any) => {
64
- if ((payload as any).error) {
65
- return;
66
- }
67
- itemsCost = payload?.results;
68
- })
69
- .catch((error: any) => {
70
- if ((error as any).error) {
71
- return;
72
- }
73
- return;
74
- });
62
+ const res = await getStockOperationItemsCost(inventoryFilter);
63
+ itemsCost = res.data?.results;
75
64
  }
76
65
  }
77
66
  const enableBalance = true;
@@ -88,35 +77,22 @@ const StockOperationPrintButton: React.FC<StockOperationCancelButtonProps> = ({
88
77
  inventoryFilter.groupBy = 'LocationStockItem';
89
78
  inventoryFilter.includeStockItemName = 'true';
90
79
 
91
- inventoryFilter.date = JSON.stringify(parentOperation?.dateCreated ?? operation?.dateCreated);
92
-
80
+ inventoryFilter.date = (parentOperation?.dateCreated ?? operation?.dateCreated) as any;
93
81
  // get stock item inventory
94
- getStockItemInventory(inventoryFilter)
95
- .then((payload: any) => {
96
- if ((payload as any).error) {
97
- return;
98
- }
99
- itemInventory = payload?.results;
100
- })
101
- .catch((error: any) => {
102
- if ((error as any).error) {
103
- return;
104
- }
105
- return;
106
- });
82
+ const res = await getStockItemInventory(inventoryFilter);
83
+ itemInventory = res.data?.results;
107
84
  }
108
-
109
85
  const data = await BuildStockOperationData(
110
86
  operation,
111
- operation.stockOperationItems,
87
+ operation.stockOperationItems ?? _operation?.stockOperationItems ?? [],
112
88
  parentOperation,
113
89
  itemsCost,
114
90
  itemInventory,
115
91
  );
116
92
  if (data) {
117
- if (operation?.operationType === 'receipt') {
93
+ if (operation?.operationType === OperationType.RECEIPT_OPERATION_TYPE) {
118
94
  await PrintGoodsReceivedNoteStockOperation(data);
119
- } else if (operation?.operationType === 'transferout') {
95
+ } else if (operation?.operationType === OperationType.TRANSFER_OUT_OPERATION_TYPE) {
120
96
  await PrintTransferOutStockOperation(data);
121
97
  } else {
122
98
  await PrintRequisitionStockOperation(data);
@@ -124,14 +100,30 @@ const StockOperationPrintButton: React.FC<StockOperationCancelButtonProps> = ({
124
100
  } else {
125
101
  console.info(data);
126
102
  }
127
- } catch (e) {
103
+ } catch (e: any) {
128
104
  console.info(e);
105
+ showSnackbar({
106
+ kind: 'error',
107
+ title: t('errorPrintingStockOperation', 'Error printing stock operation'),
108
+ subtitle: extractErrorMessagesFromResponse(e).join(', '),
109
+ });
110
+ } finally {
111
+ setLoading(false);
129
112
  }
130
113
  };
131
114
 
132
115
  return (
133
- <Button onClick={onPrintStockOperation} kind="tertiary" renderIcon={(props) => <Printer size={16} {...props} />}>
134
- {t('print', 'Print')}
116
+ <Button
117
+ onClick={onPrintStockOperation}
118
+ kind="tertiary"
119
+ disabled={isLoading || loading}
120
+ renderIcon={(props) => <Printer size={16} {...props} />}
121
+ >
122
+ {loading || isLoading ? (
123
+ <InlineLoading description={t('loading', 'Loading') + '...'} iconDescription={t('loading', 'Loading')} />
124
+ ) : (
125
+ t('print', 'Print')
126
+ )}
135
127
  </Button>
136
128
  );
137
129
  };
@@ -1,9 +1,9 @@
1
- import { UserFilterCriteria } from '../../stock-lookups/stock-lookups.resource';
2
1
  import { useEffect, useState } from 'react';
3
- import { ResourceRepresentation } from '../../core/api/api';
4
- import { StockItemFilter, useStockItems as useStockItemsData } from '../../stock-items/stock-items.resource';
2
+ import { StockItemFilter, useStockItems as useStockItemsData } from '../../../stock-items/stock-items.resource';
3
+ import { UserFilterCriteria } from '../../../stock-lookups/stock-lookups.resource';
4
+ import { ResourceRepresentation } from '../../../core/api/api';
5
5
 
6
- export function useStockItems(filter?: StockItemFilter) {
6
+ export function useFilterableStockItems(filter?: StockItemFilter) {
7
7
  const [conceptFilter, setConceptFilter] = useState<UserFilterCriteria>(
8
8
  filter || {
9
9
  v: ResourceRepresentation.Default,
@@ -0,0 +1,30 @@
1
+ import { useMemo } from 'react';
2
+ import { useStockOperationTypes, useUserRoles } from '../../../stock-lookups/stock-lookups.resource';
3
+
4
+ const useFilteredOperationTypesByRoles = () => {
5
+ const {
6
+ types: { results },
7
+ isLoading: isStockOperationTypesLoading,
8
+ error: stockOperationTypesError,
9
+ } = useStockOperationTypes();
10
+
11
+ const { userRoles, isLoading: isUserRolesLoading, error: userRolesError } = useUserRoles();
12
+
13
+ const operationTypes = useMemo(() => {
14
+ const applicablePrivilegeScopes = userRoles?.operationTypes?.map((p) => p.operationTypeUuid) || [];
15
+ const uniqueApplicablePrivilegeScopes = [...new Set(applicablePrivilegeScopes)];
16
+
17
+ return results?.filter((p) => uniqueApplicablePrivilegeScopes.includes(p.uuid)) || [];
18
+ }, [results, userRoles]);
19
+
20
+ const isLoading = isStockOperationTypesLoading || isUserRolesLoading;
21
+ const error = stockOperationTypesError || userRolesError;
22
+
23
+ return {
24
+ operationTypes,
25
+ isLoading,
26
+ error,
27
+ };
28
+ };
29
+
30
+ export default useFilteredOperationTypesByRoles;
@@ -0,0 +1,29 @@
1
+ import {
2
+ operationFromString,
3
+ StockOperationType,
4
+ StockOperationTypeCanCapturePurchasePrice,
5
+ StockOperationTypeHasPrint,
6
+ StockOperationTypeIsNegativeQtyAllowed,
7
+ StockOperationTypeIsQuantityOptional,
8
+ StockOperationTypeRequiresActualBatchInformation,
9
+ StockOperationTypeRequiresBatchUuid,
10
+ StockOperationTypeRequiresDispatchAcknowledgement,
11
+ StockOperationTypeRequiresStockAdjustmentReason,
12
+ } from '../../../core/api/types/stockOperation/StockOperationType';
13
+
14
+ const useOperationTypePermisions = (stockoperationType: StockOperationType) => {
15
+ const opType = operationFromString(stockoperationType.operationType);
16
+ return {
17
+ isNegativeQuantityAllowed: StockOperationTypeIsNegativeQtyAllowed(opType),
18
+ requiresBatchUuid: StockOperationTypeRequiresBatchUuid(opType),
19
+ requiresActualBatchInfo: StockOperationTypeRequiresActualBatchInformation(opType),
20
+ isQuantityOptional: StockOperationTypeIsQuantityOptional(opType),
21
+ canCaptureQuantityPrice: StockOperationTypeCanCapturePurchasePrice(opType),
22
+ requiresStockAdjustmentReason: StockOperationTypeRequiresStockAdjustmentReason(opType),
23
+ requiresDispatchAcknowledgement: StockOperationTypeRequiresDispatchAcknowledgement(opType),
24
+ allowExpiredBatchNumbers: stockoperationType?.allowExpiredBatchNumbers ?? false,
25
+ allowPrinting: StockOperationTypeHasPrint(opType),
26
+ };
27
+ };
28
+
29
+ export default useOperationTypePermisions;
@@ -0,0 +1,73 @@
1
+ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import React, { useCallback, useMemo } from 'react';
3
+ import useSWR from 'swr';
4
+ import { Party } from '../../../core/api/types/Party';
5
+ import { LocationTypeLocation, LocationTypeOther } from '../../../core/api/types/stockOperation/LocationType';
6
+ import { StockOperationType } from '../../../core/api/types/stockOperation/StockOperationType';
7
+
8
+ const useParties = (stockOperationType: StockOperationType) => {
9
+ const apiUrl = `${restBaseUrl}/stockmanagement/party?v=default`;
10
+ const { data, isLoading, mutate, error } = useSWR<FetchResponse<{ results: Array<Party> }>>(apiUrl, openmrsFetch);
11
+ const sourceTags = useMemo(() => {
12
+ return (
13
+ stockOperationType?.stockOperationTypeLocationScopes
14
+ ?.filter((p) => stockOperationType?.hasSource && p.isSource)
15
+ .map((p) => p.locationTag) ?? []
16
+ );
17
+ }, [stockOperationType]);
18
+
19
+ const destinationTags = useMemo(() => {
20
+ return (
21
+ stockOperationType?.stockOperationTypeLocationScopes
22
+ ?.filter((p) => stockOperationType?.hasDestination && p.isDestination)
23
+ .map((p) => p.locationTag) ?? []
24
+ );
25
+ }, [stockOperationType]);
26
+
27
+ const sourcePartiesFilter = useCallback(
28
+ (p: Party) => {
29
+ return (
30
+ (p.locationUuid &&
31
+ stockOperationType?.sourceType === LocationTypeLocation &&
32
+ (sourceTags.length === 0 || (p.tags && sourceTags.some((x) => p.tags.includes(x))))) ||
33
+ (p.stockSourceUuid && stockOperationType?.sourceType === LocationTypeOther)
34
+ );
35
+ },
36
+ [stockOperationType, sourceTags],
37
+ );
38
+
39
+ const destinationPartiesFilter = useCallback(
40
+ (p: Party) => {
41
+ return (
42
+ (p.locationUuid &&
43
+ stockOperationType?.destinationType === LocationTypeLocation &&
44
+ (destinationTags.length === 0 || (p.tags && destinationTags.some((x) => p.tags.includes(x))))) ||
45
+ (p.stockSourceUuid && stockOperationType?.destinationType === LocationTypeOther)
46
+ );
47
+ },
48
+ [stockOperationType, destinationTags],
49
+ );
50
+
51
+ const sourceParties = useMemo(() => {
52
+ return data?.data?.results?.filter(sourcePartiesFilter) ?? [];
53
+ }, [data, sourcePartiesFilter]);
54
+
55
+ const destinationParties = useMemo(() => {
56
+ return data?.data?.results?.filter(destinationPartiesFilter) ?? [];
57
+ }, [data, destinationPartiesFilter]);
58
+
59
+ return {
60
+ parties: data?.data?.results ?? [],
61
+ isLoading,
62
+ mutate,
63
+ sourceParties,
64
+ destinationParties,
65
+ sourcePartiesFilter,
66
+ destinationPartiesFilter,
67
+ error,
68
+ sourceTags,
69
+ destinationTags,
70
+ };
71
+ };
72
+
73
+ export default useParties;
@@ -1,8 +1,8 @@
1
- import { ConceptFilterCriteria, UserFilterCriteria, useUsers } from '../../stock-lookups/stock-lookups.resource';
2
- import { useEffect, useState } from 'react';
3
- import { ResourceRepresentation } from '../../core/api/api';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { ConceptFilterCriteria, UserFilterCriteria, useUsers } from '../../../stock-lookups/stock-lookups.resource';
3
+ import { ResourceRepresentation } from '../../../core/api/api';
4
4
 
5
- export function useUsersHook(filter?: ConceptFilterCriteria) {
5
+ const useSearchUser = (filter?: ConceptFilterCriteria) => {
6
6
  const [conceptFilter, setConceptFilter] = useState<UserFilterCriteria>(
7
7
  filter || {
8
8
  v: ResourceRepresentation.Default,
@@ -20,12 +20,12 @@ export function useUsersHook(filter?: ConceptFilterCriteria) {
20
20
 
21
21
  // Drug filter type
22
22
  const [limit, setLimit] = useState(filter?.limit || 10);
23
- const [representation, setRepresentation] = useState(filter?.v || ResourceRepresentation.Default);
23
+ const [representation, setRepresentation] = useState<string>(filter?.v || ResourceRepresentation.Default);
24
24
 
25
25
  useEffect(() => {
26
26
  setConceptFilter({
27
27
  startIndex: 0,
28
- v: representation,
28
+ v: representation as ResourceRepresentation,
29
29
  limit: limit,
30
30
  q: searchString,
31
31
  });
@@ -38,4 +38,6 @@ export function useUsersHook(filter?: ConceptFilterCriteria) {
38
38
  setSearchString,
39
39
  isLoading,
40
40
  };
41
- }
41
+ };
42
+
43
+ export default useSearchUser;
@@ -1,8 +1,8 @@
1
- import { StockBatchFilter, useStockBatches } from '../../stock-items/stock-items.resource';
2
1
  import { useEffect, useState } from 'react';
3
- import { ResourceRepresentation } from '../../core/api/api';
2
+ import { ResourceRepresentation } from '../../../core/api/api';
3
+ import { StockBatchFilter, useStockBatches } from '../../../stock-items/stock-items.resource';
4
4
 
5
- export function useStockItemBatchNos(stockItemUuid: string) {
5
+ export function useStockItemBatchNumbers(stockItemUuid: string) {
6
6
  const [conceptFilter, setConceptFilter] = useState<StockBatchFilter>({
7
7
  v: ResourceRepresentation.Default,
8
8
  limit: 10,
@@ -0,0 +1,20 @@
1
+ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { StockOperationLinkDTO } from '../../../core/api/types/stockOperation/StockOperationLinkDTO';
4
+
5
+ const useStockOperationLinks = (stockOperationUuid?: string) => {
6
+ const apiUrl = `${restBaseUrl}/stockmanagement/stockoperationlink?v=default&q=${stockOperationUuid}`;
7
+ const { data, error, isLoading, mutate } = useSWR<
8
+ FetchResponse<{
9
+ results: Array<StockOperationLinkDTO>;
10
+ }>
11
+ >(stockOperationUuid ? apiUrl : null, openmrsFetch);
12
+ return {
13
+ error,
14
+ isLoading,
15
+ mutate,
16
+ operationLinks: data?.data?.results ?? [],
17
+ };
18
+ };
19
+
20
+ export default useStockOperationLinks;
@@ -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 {