@ampath/esm-dispensing-app 1.10.0-next.1

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 (277) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +80 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +12 -0
  7. package/.tx/config +11 -0
  8. package/.yarn/versions/1c40b9b6.yml +0 -0
  9. package/.yarn/versions/ff162597.yml +0 -0
  10. package/LICENSE +401 -0
  11. package/README.md +124 -0
  12. package/__mocks__/react-i18next.js +51 -0
  13. package/dist/1043.js +1 -0
  14. package/dist/1043.js.map +1 -0
  15. package/dist/1119.js +1 -0
  16. package/dist/1197.js +1 -0
  17. package/dist/2146.js +1 -0
  18. package/dist/2177.js +2 -0
  19. package/dist/2177.js.LICENSE.txt +9 -0
  20. package/dist/2177.js.map +1 -0
  21. package/dist/2690.js +1 -0
  22. package/dist/2890.js +2 -0
  23. package/dist/2890.js.LICENSE.txt +14 -0
  24. package/dist/2890.js.map +1 -0
  25. package/dist/2898.js +1 -0
  26. package/dist/2898.js.map +1 -0
  27. package/dist/3041.js +1 -0
  28. package/dist/3041.js.map +1 -0
  29. package/dist/3099.js +1 -0
  30. package/dist/3184.js +2 -0
  31. package/dist/3184.js.LICENSE.txt +14 -0
  32. package/dist/3184.js.map +1 -0
  33. package/dist/3568.js +1 -0
  34. package/dist/3568.js.map +1 -0
  35. package/dist/3584.js +1 -0
  36. package/dist/4055.js +1 -0
  37. package/dist/4099.js +1 -0
  38. package/dist/4099.js.map +1 -0
  39. package/dist/4132.js +1 -0
  40. package/dist/4225.js +1 -0
  41. package/dist/4225.js.map +1 -0
  42. package/dist/4300.js +1 -0
  43. package/dist/4335.js +1 -0
  44. package/dist/4353.js +1 -0
  45. package/dist/4353.js.map +1 -0
  46. package/dist/439.js +1 -0
  47. package/dist/4618.js +1 -0
  48. package/dist/4652.js +1 -0
  49. package/dist/4944.js +1 -0
  50. package/dist/5173.js +1 -0
  51. package/dist/5241.js +1 -0
  52. package/dist/5422.js +1 -0
  53. package/dist/5422.js.map +1 -0
  54. package/dist/5442.js +1 -0
  55. package/dist/5661.js +1 -0
  56. package/dist/5897.js +1 -0
  57. package/dist/5897.js.map +1 -0
  58. package/dist/6022.js +1 -0
  59. package/dist/609.js +1 -0
  60. package/dist/609.js.map +1 -0
  61. package/dist/6468.js +1 -0
  62. package/dist/6540.js +2 -0
  63. package/dist/6540.js.LICENSE.txt +9 -0
  64. package/dist/6540.js.map +1 -0
  65. package/dist/6589.js +1 -0
  66. package/dist/6606.js +1 -0
  67. package/dist/6606.js.map +1 -0
  68. package/dist/6679.js +1 -0
  69. package/dist/6825.js +1 -0
  70. package/dist/6825.js.map +1 -0
  71. package/dist/6840.js +1 -0
  72. package/dist/6841.js +1 -0
  73. package/dist/6841.js.map +1 -0
  74. package/dist/6859.js +1 -0
  75. package/dist/7097.js +1 -0
  76. package/dist/7159.js +1 -0
  77. package/dist/723.js +1 -0
  78. package/dist/7240.js +1 -0
  79. package/dist/7240.js.map +1 -0
  80. package/dist/7255.js +1 -0
  81. package/dist/7255.js.map +1 -0
  82. package/dist/7617.js +1 -0
  83. package/dist/795.js +1 -0
  84. package/dist/8163.js +1 -0
  85. package/dist/8349.js +1 -0
  86. package/dist/8371.js +1 -0
  87. package/dist/8569.js +2 -0
  88. package/dist/8569.js.LICENSE.txt +41 -0
  89. package/dist/8569.js.map +1 -0
  90. package/dist/8600.js +1 -0
  91. package/dist/8600.js.map +1 -0
  92. package/dist/8618.js +1 -0
  93. package/dist/8885.js +1 -0
  94. package/dist/8885.js.map +1 -0
  95. package/dist/890.js +1 -0
  96. package/dist/9214.js +1 -0
  97. package/dist/9538.js +1 -0
  98. package/dist/9569.js +1 -0
  99. package/dist/961.js +2 -0
  100. package/dist/961.js.LICENSE.txt +19 -0
  101. package/dist/961.js.map +1 -0
  102. package/dist/963.js +1 -0
  103. package/dist/963.js.map +1 -0
  104. package/dist/986.js +1 -0
  105. package/dist/9879.js +1 -0
  106. package/dist/9895.js +1 -0
  107. package/dist/9900.js +1 -0
  108. package/dist/9913.js +1 -0
  109. package/dist/main.js +2 -0
  110. package/dist/main.js.LICENSE.txt +51 -0
  111. package/dist/main.js.map +1 -0
  112. package/dist/openmrs-esm-dispensing-app.js +1 -0
  113. package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
  114. package/dist/openmrs-esm-dispensing-app.js.map +1 -0
  115. package/dist/routes.json +1 -0
  116. package/e2e/README.md +119 -0
  117. package/e2e/commands/drug-order-operations.ts +43 -0
  118. package/e2e/commands/encounter-operations.ts +60 -0
  119. package/e2e/commands/index.ts +5 -0
  120. package/e2e/commands/patient-operations.ts +109 -0
  121. package/e2e/commands/provider-operations.ts +9 -0
  122. package/e2e/commands/types/index.ts +157 -0
  123. package/e2e/commands/visit-operations.ts +38 -0
  124. package/e2e/core/global-setup.ts +32 -0
  125. package/e2e/core/index.ts +1 -0
  126. package/e2e/core/test.ts +31 -0
  127. package/e2e/fixtures/api.ts +27 -0
  128. package/e2e/fixtures/index.ts +1 -0
  129. package/e2e/pages/dispensing-page.ts +9 -0
  130. package/e2e/pages/index.ts +1 -0
  131. package/e2e/specs/active-prescriptions.spec.ts +72 -0
  132. package/e2e/specs/close-prescription.spec.ts +71 -0
  133. package/e2e/specs/dispense-medication.spec.ts +71 -0
  134. package/e2e/specs/pause-prescription.spec.ts +72 -0
  135. package/e2e/support/github/Dockerfile +34 -0
  136. package/e2e/support/github/docker-compose.yml +24 -0
  137. package/e2e/support/github/run-e2e-docker-env.sh +42 -0
  138. package/e2e/types/index.ts +157 -0
  139. package/example.env +7 -0
  140. package/jest.config.js +24 -0
  141. package/package.json +110 -0
  142. package/playwright.config.ts +36 -0
  143. package/prettier.config.js +8 -0
  144. package/src/components/action-buttons.component.tsx +87 -0
  145. package/src/components/action-buttons.scss +16 -0
  146. package/src/components/action-buttons.test.tsx +217 -0
  147. package/src/components/medication-card.component.tsx +37 -0
  148. package/src/components/medication-card.scss +20 -0
  149. package/src/components/medication-card.test.tsx +36 -0
  150. package/src/components/medication-dispense-review.scss +108 -0
  151. package/src/components/medication-event.component.tsx +96 -0
  152. package/src/components/medication-event.scss +44 -0
  153. package/src/components/medication-event.test.tsx +212 -0
  154. package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
  155. package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
  156. package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
  157. package/src/conditions/conditions.component.tsx +118 -0
  158. package/src/conditions/conditions.resource.ts +100 -0
  159. package/src/conditions/conditions.scss +26 -0
  160. package/src/conditions/conditions.test.tsx +200 -0
  161. package/src/config-schema.ts +192 -0
  162. package/src/constants.ts +22 -0
  163. package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
  164. package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
  165. package/src/declarations.d.ts +2 -0
  166. package/src/diagnoses/diagnoses.component.tsx +111 -0
  167. package/src/diagnoses/diagnoses.resource.ts +30 -0
  168. package/src/diagnoses/diagnoses.scss +31 -0
  169. package/src/dispensing-link.component.tsx +9 -0
  170. package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
  171. package/src/dispensing-tiles/dispensing-tile.scss +43 -0
  172. package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
  173. package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
  174. package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
  175. package/src/dispensing.component.tsx +31 -0
  176. package/src/dispensing.scss +5 -0
  177. package/src/dispensing.test.tsx +9 -0
  178. package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
  179. package/src/fill-prescription/fill-prescription-button.scss +8 -0
  180. package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
  181. package/src/fill-prescription/on-prescription-filled.scss +7 -0
  182. package/src/forms/close-dispense-form.workspace.tsx +194 -0
  183. package/src/forms/dispense-form.workspace.test.tsx +334 -0
  184. package/src/forms/dispense-form.workspace.tsx +324 -0
  185. package/src/forms/forms.scss +152 -0
  186. package/src/forms/medication-dispense-review.component.tsx +649 -0
  187. package/src/forms/medication-dispense-review.test.tsx +158 -0
  188. package/src/forms/pause-dispense-form.workspace.tsx +196 -0
  189. package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
  190. package/src/forms/stock-dispense/stock.resource.tsx +67 -0
  191. package/src/history/delete-confirm.modal.tsx +35 -0
  192. package/src/history/history-and-comments.component.tsx +338 -0
  193. package/src/history/history-and-comments.scss +54 -0
  194. package/src/index.ts +57 -0
  195. package/src/location/location.resource.test.tsx +108 -0
  196. package/src/location/location.resource.tsx +32 -0
  197. package/src/medication/medication.resource.test.tsx +156 -0
  198. package/src/medication/medication.resource.tsx +45 -0
  199. package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
  200. package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
  201. package/src/medication-request/medication-request.resource.test.tsx +1333 -0
  202. package/src/medication-request/medication-request.resource.tsx +257 -0
  203. package/src/patient/patient-info-cell.component.tsx +26 -0
  204. package/src/patient/patient.resources.ts +14 -0
  205. package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
  206. package/src/pharmacy-header/pharmacy-header.scss +55 -0
  207. package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
  208. package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
  209. package/src/prescriptions/patient-search-tab-panel.scss +26 -0
  210. package/src/prescriptions/prescription-actions.component.tsx +24 -0
  211. package/src/prescriptions/prescription-actions.scss +14 -0
  212. package/src/prescriptions/prescription-details.component.tsx +152 -0
  213. package/src/prescriptions/prescription-details.scss +87 -0
  214. package/src/prescriptions/prescription-details.test.tsx +267 -0
  215. package/src/prescriptions/prescription-expanded.component.tsx +58 -0
  216. package/src/prescriptions/prescription-expanded.scss +56 -0
  217. package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
  218. package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
  219. package/src/prescriptions/prescriptions-table.component.tsx +189 -0
  220. package/src/prescriptions/prescriptions.scss +152 -0
  221. package/src/print-prescription/prescription-print-action.component.tsx +30 -0
  222. package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
  223. package/src/print-prescription/prescription-printout.component.tsx +154 -0
  224. package/src/print-prescription/print-prescription.scss +75 -0
  225. package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
  226. package/src/routes.json +137 -0
  227. package/src/types.ts +530 -0
  228. package/src/utils.test.ts +2947 -0
  229. package/src/utils.ts +637 -0
  230. package/tools/i18next-parser.config.js +89 -0
  231. package/tools/setup-tests.ts +8 -0
  232. package/tools/update-openmrs-deps.mjs +42 -0
  233. package/translations/am.json +133 -0
  234. package/translations/ar.json +133 -0
  235. package/translations/ar_SY.json +133 -0
  236. package/translations/bn.json +133 -0
  237. package/translations/cs.json +133 -0
  238. package/translations/de.json +133 -0
  239. package/translations/en.json +133 -0
  240. package/translations/en_US.json +133 -0
  241. package/translations/es.json +133 -0
  242. package/translations/es_MX.json +133 -0
  243. package/translations/fr.json +133 -0
  244. package/translations/he.json +133 -0
  245. package/translations/hi.json +133 -0
  246. package/translations/hi_IN.json +133 -0
  247. package/translations/id.json +133 -0
  248. package/translations/it.json +133 -0
  249. package/translations/ka.json +133 -0
  250. package/translations/km.json +133 -0
  251. package/translations/ku.json +133 -0
  252. package/translations/ky.json +133 -0
  253. package/translations/lg.json +133 -0
  254. package/translations/ne.json +133 -0
  255. package/translations/pl.json +133 -0
  256. package/translations/pt.json +133 -0
  257. package/translations/pt_BR.json +133 -0
  258. package/translations/qu.json +133 -0
  259. package/translations/ro_RO.json +133 -0
  260. package/translations/ru_RU.json +133 -0
  261. package/translations/si.json +133 -0
  262. package/translations/sq.json +133 -0
  263. package/translations/sw.json +133 -0
  264. package/translations/sw_KE.json +133 -0
  265. package/translations/tr.json +133 -0
  266. package/translations/tr_TR.json +133 -0
  267. package/translations/uk.json +133 -0
  268. package/translations/uz.json +133 -0
  269. package/translations/uz@Latn.json +133 -0
  270. package/translations/uz_UZ.json +133 -0
  271. package/translations/vi.json +133 -0
  272. package/translations/zh.json +133 -0
  273. package/translations/zh_CN.json +133 -0
  274. package/translations/zh_TW.json +133 -0
  275. package/tsconfig.json +23 -0
  276. package/turbo.json +41 -0
  277. package/webpack.config.js +1 -0
@@ -0,0 +1,156 @@
1
+ import { useMedicationCodeableConcept, useMedicationFormulations } from './medication.resource';
2
+ import useSWR from 'swr';
3
+ import { openmrsFetch } from '@openmrs/esm-framework';
4
+
5
+ jest.mocked(openmrsFetch);
6
+ jest.mock('swr');
7
+
8
+ describe('Medication Resource Tests', () => {
9
+ test('useMedicationCodeableConcept should call useSWR with null reference if existing codeable concept uuid passed in', () => {
10
+ // @ts-ignore
11
+ useSWR.mockImplementation(() => ({ data: null }));
12
+ const result = useMedicationCodeableConcept('123abc', 'Medication/123abc');
13
+ expect(useSWR).toHaveBeenCalledWith(null, openmrsFetch);
14
+ expect(result.medicationCodeableConceptUuid).toBeNull();
15
+ });
16
+
17
+ test('useMedicationCodeableConcept should call useSWR with medication reference if no existing codeable concept uuid passed in', () => {
18
+ // @ts-ignore
19
+ useSWR.mockImplementation(() => ({
20
+ data: { data: { code: { coding: [{ code: 'def678' }] } } },
21
+ }));
22
+ const result = useMedicationCodeableConcept(null, 'Medication/123abc');
23
+ expect(useSWR).toHaveBeenCalledWith('/ws/fhir2/R4/Medication/123abc', openmrsFetch);
24
+ expect(result.medicationCodeableConceptUuid).toBe('def678');
25
+ });
26
+
27
+ test('useMedicationFormulations should call useSWR with null reference if no concept uuid provided', () => {
28
+ // @ts-ignore
29
+ useSWR.mockImplementation(() => ({ data: null }));
30
+ const result = useMedicationFormulations(null);
31
+ expect(useSWR).toHaveBeenCalledWith(null, openmrsFetch);
32
+ expect(result.medicationFormulations).toBeUndefined(); // TODO: is this really what we want? or rather null or empty
33
+ });
34
+
35
+ test('useMedicationFormulations should call useSWR with with medications by concept endpoint and return formualations if concept defined', () => {
36
+ // @ts-ignore
37
+ useSWR.mockImplementation(() => ({
38
+ data: {
39
+ data: {
40
+ resourceType: 'Bundle',
41
+ id: '2ee923c2-6e97-464e-ae0b-797b24c3b3b5',
42
+ meta: {
43
+ lastUpdated: '2023-02-27T17:51:19.801-05:00',
44
+ },
45
+ type: 'searchset',
46
+ total: 2,
47
+ link: [
48
+ {
49
+ relation: 'self',
50
+ url: 'https://ci.pih-emr.org/openmrs/ws/fhir2/R4/Medication?code=3cd27a8a-26fe-102b-80cb-0017a47871b2',
51
+ },
52
+ ],
53
+ entry: [
54
+ {
55
+ fullUrl: 'https://ci.pih-emr.org/openmrs/ws/fhir2/R4/Medication/e47fa273-0c52-4f0f-b57b-34001a3e9677',
56
+ resource: {
57
+ resourceType: 'Medication',
58
+ id: 'e47fa273-0c52-4f0f-b57b-34001a3e9677',
59
+ meta: {
60
+ lastUpdated: '2019-09-09T22:55:46.000-04:00',
61
+ },
62
+ text: {
63
+ status: 'generated',
64
+ div: '<div xmlns="http://www.w3.org/1999/xhtml"><table class="hapiPropertyTable"><tbody><tr><td>Id:</td><td>e47fa273-0c52-4f0f-b57b-34001a3e9677</td></tr><tr><td>Code:</td><td>Isoniazide</td></tr><tr><td>Status:</td><td>ACTIVE</td></tr></tbody></table></div>',
65
+ },
66
+ extension: [
67
+ {
68
+ url: 'http://fhir.openmrs.org/ext/medicine',
69
+ extension: [
70
+ {
71
+ url: 'http://fhir.openmrs.org/ext/medicine#drugName',
72
+ valueString: 'Isoniazid (H), 100mg tablet',
73
+ },
74
+ ],
75
+ },
76
+ ],
77
+ code: {
78
+ coding: [
79
+ {
80
+ code: '3cd27a8a-26fe-102b-80cb-0017a47871b2',
81
+ display: 'Isoniazide',
82
+ },
83
+ {
84
+ system: 'https://openconceptlab.org/orgs/CIEL/sources/CIEL',
85
+ code: '1679',
86
+ display: 'Isoniazide',
87
+ },
88
+ {
89
+ system: 'http://snomed.info/sct',
90
+ code: '81335000',
91
+ display: 'Isoniazide',
92
+ },
93
+ ],
94
+ text: 'Isoniazide',
95
+ },
96
+ status: 'active',
97
+ },
98
+ },
99
+ {
100
+ fullUrl: 'https://ci.pih-emr.org/openmrs/ws/fhir2/R4/Medication/849218ee-901c-46b3-80f9-7c808132893b',
101
+ resource: {
102
+ resourceType: 'Medication',
103
+ id: '849218ee-901c-46b3-80f9-7c808132893b',
104
+ meta: {
105
+ lastUpdated: '2019-09-09T22:55:46.000-04:00',
106
+ },
107
+ text: {
108
+ status: 'generated',
109
+ div: '<div xmlns="http://www.w3.org/1999/xhtml"><table class="hapiPropertyTable"><tbody><tr><td>Id:</td><td>849218ee-901c-46b3-80f9-7c808132893b</td></tr><tr><td>Code:</td><td>Isoniazide</td></tr><tr><td>Status:</td><td>ACTIVE</td></tr></tbody></table></div>',
110
+ },
111
+ extension: [
112
+ {
113
+ url: 'http://fhir.openmrs.org/ext/medicine',
114
+ extension: [
115
+ {
116
+ url: 'http://fhir.openmrs.org/ext/medicine#drugName',
117
+ valueString: 'Isoniazid (H), 300mg tablet',
118
+ },
119
+ ],
120
+ },
121
+ ],
122
+ code: {
123
+ coding: [
124
+ {
125
+ code: '3cd27a8a-26fe-102b-80cb-0017a47871b2',
126
+ display: 'Isoniazide',
127
+ },
128
+ {
129
+ system: 'https://openconceptlab.org/orgs/CIEL/sources/CIEL',
130
+ code: '1679',
131
+ display: 'Isoniazide',
132
+ },
133
+ {
134
+ system: 'http://snomed.info/sct',
135
+ code: '81335000',
136
+ display: 'Isoniazide',
137
+ },
138
+ ],
139
+ text: 'Isoniazide',
140
+ },
141
+ status: 'active',
142
+ },
143
+ },
144
+ ],
145
+ },
146
+ },
147
+ }));
148
+ const result = useMedicationFormulations('123abc');
149
+ expect(useSWR).toHaveBeenCalledWith('/ws/fhir2/R4/Medication?code=123abc', openmrsFetch);
150
+ expect(result.medicationFormulations.length).toBe(2);
151
+ expect(result.medicationFormulations[0].id).toBe('e47fa273-0c52-4f0f-b57b-34001a3e9677');
152
+ expect(result.medicationFormulations[0].resourceType).toBe('Medication');
153
+ expect(result.medicationFormulations[1].id).toBe('849218ee-901c-46b3-80f9-7c808132893b');
154
+ expect(result.medicationFormulations[1].resourceType).toBe('Medication');
155
+ });
156
+ });
@@ -0,0 +1,45 @@
1
+ import useSWR from 'swr';
2
+ import { type Medication, type MedicationFormulationsResponse } from '../types';
3
+ import { fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework';
4
+ import { getConceptCodingUuid, getMedicationsByConceptEndpoint } from '../utils';
5
+
6
+ /**
7
+ * Given a Medication Reference, fetches that Medication and returns the codeable concept associated with it
8
+ * Note: returns null if existingMedicationCodeableConcept already defined
9
+ *
10
+ * @param existingMedicationCodeableConceptUuid
11
+ * @param medicationReference
12
+ */
13
+ export function useMedicationCodeableConcept(
14
+ existingMedicationCodeableConceptUuid: string,
15
+ medicationReference: string,
16
+ ) {
17
+ const { data } = useSWR<{ data: Medication }, Error>(
18
+ existingMedicationCodeableConceptUuid || !medicationReference ? null : `${fhirBaseUrl}/${medicationReference}`,
19
+ openmrsFetch,
20
+ );
21
+
22
+ return {
23
+ medicationCodeableConceptUuid: data ? getConceptCodingUuid(data.data.code.coding) : null,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Given a concept uuid, returns all Medications associated with that concept
29
+ *
30
+ * @param medicationCodeableConcept
31
+ */
32
+ export function useMedicationFormulations(medicationCodeableConcept: string) {
33
+ const { data } = useSWR<{ data: MedicationFormulationsResponse }, Error>(
34
+ medicationCodeableConcept ? getMedicationsByConceptEndpoint(medicationCodeableConcept) : null, // note that we don't start the search until we've confirmed we have a uuid to search for
35
+ openmrsFetch,
36
+ );
37
+
38
+ if (data?.data?.entry) {
39
+ return {
40
+ medicationFormulations: data.data.entry.map((e) => e.resource),
41
+ };
42
+ } else {
43
+ return {};
44
+ }
45
+ }
@@ -0,0 +1,243 @@
1
+ import useSWR from 'swr';
2
+ import { openmrsFetch, type Session } from '@openmrs/esm-framework';
3
+ import {
4
+ deleteMedicationDispense,
5
+ initiateMedicationDispenseBody,
6
+ saveMedicationDispense,
7
+ useOrderConfig,
8
+ useSubstitutionReasonValueSet,
9
+ useSubstitutionTypeValueSet,
10
+ } from './medication-dispense.resource';
11
+ import {
12
+ type MedicationDispense,
13
+ type MedicationRequest,
14
+ MedicationDispenseStatus,
15
+ MedicationRequestStatus,
16
+ type Provider,
17
+ } from '../types';
18
+ import dayjs from 'dayjs';
19
+
20
+ jest.mocked(openmrsFetch);
21
+ jest.mock('swr');
22
+
23
+ describe('Medication Dispense Resource tests', () => {
24
+ test('saveMedicationDispense should call medication dispense FHIR endpoint with appropriate data and method POST', () => {
25
+ const medicationDispense: MedicationDispense = {
26
+ dosageInstruction: undefined,
27
+ id: '',
28
+ location: { display: '', reference: '', type: '' },
29
+ medicationReference: { display: '', reference: '', type: '' },
30
+ meta: { lastUpdated: '' },
31
+ quantity: undefined,
32
+ performer: undefined,
33
+ resourceType: 'MedicationDispense',
34
+ status: null,
35
+ subject: { display: '', reference: '', type: '' },
36
+ substitution: { reason: [], type: undefined, wasSubstituted: false },
37
+ type: undefined,
38
+ whenHandedOver: '',
39
+ whenPrepared: '',
40
+ };
41
+ const abortController: AbortController = {
42
+ signal: undefined,
43
+ abort(): void {},
44
+ };
45
+
46
+ saveMedicationDispense(medicationDispense, MedicationDispenseStatus.completed, abortController);
47
+ expect(openmrsFetch).toHaveBeenCalledWith('/ws/fhir2/R4/MedicationDispense', {
48
+ method: 'POST',
49
+ signal: abortController.signal,
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: medicationDispense,
54
+ });
55
+ });
56
+
57
+ test('saveMedicationDispense should call medication dispense FHIR endpoint with appropriate data and method PUT', () => {
58
+ const medicationDispense: MedicationDispense = {
59
+ dosageInstruction: undefined,
60
+ id: '123abc',
61
+ location: { display: '', reference: '', type: '' },
62
+ medicationReference: { display: '', reference: '', type: '' },
63
+ meta: { lastUpdated: '' },
64
+ performer: undefined,
65
+ quantity: undefined,
66
+ resourceType: 'MedicationDispense',
67
+ status: null,
68
+ subject: { display: '', reference: '', type: '' },
69
+ substitution: { reason: [], type: undefined, wasSubstituted: false },
70
+ type: undefined,
71
+ whenHandedOver: '',
72
+ whenPrepared: '',
73
+ };
74
+ const abortController: AbortController = {
75
+ signal: undefined,
76
+ abort(): void {},
77
+ };
78
+
79
+ saveMedicationDispense(medicationDispense, MedicationDispenseStatus.completed, abortController);
80
+ expect(openmrsFetch).toHaveBeenCalledWith('/ws/fhir2/R4/MedicationDispense/123abc', {
81
+ method: 'PUT',
82
+ signal: abortController.signal,
83
+ headers: {
84
+ 'Content-Type': 'application/json',
85
+ },
86
+ body: medicationDispense,
87
+ });
88
+ });
89
+
90
+ test('deleteMedicationDispense should call medication request FHIR endpoint with appropriate uuid and method DELETE', () => {
91
+ deleteMedicationDispense('123abc');
92
+ expect(openmrsFetch).toHaveBeenCalledWith('/ws/fhir2/R4/MedicationDispense/123abc', { method: 'DELETE' });
93
+ });
94
+
95
+ test('useOrderConfig should fetch order config via SWR', () => {
96
+ // @ts-ignore
97
+ useSWR.mockImplementation(() => ({ data: { data: 'mockedOrderConfig' } }));
98
+ const orderConfig = useOrderConfig();
99
+ expect(orderConfig.orderConfigObject).toBe('mockedOrderConfig');
100
+ });
101
+
102
+ test('useSubstitutionTypeValueSet should call fetch substitution type value set via SWR', () => {
103
+ // @ts-ignore
104
+ useSWR.mockImplementation(() => ({
105
+ data: { data: 'mockedSubstitutionTypeValueSet' },
106
+ }));
107
+ const result = useSubstitutionTypeValueSet('123');
108
+ expect(useSWR).toHaveBeenCalledWith('/ws/fhir2/R4/ValueSet/123', openmrsFetch);
109
+ expect(result.substitutionTypeValueSet).toBe('mockedSubstitutionTypeValueSet');
110
+ });
111
+
112
+ test('useSubstitutionReasonValueSet should call fetch substitution reason value set via SWR', () => {
113
+ // @ts-ignore
114
+ useSWR.mockImplementation(() => ({
115
+ data: { data: 'mockedSubstitutionReasonValueSet' },
116
+ }));
117
+ const result = useSubstitutionReasonValueSet('123');
118
+ expect(useSWR).toHaveBeenCalledWith('/ws/fhir2/R4/ValueSet/123', openmrsFetch);
119
+ expect(result.substitutionReasonValueSet).toBe('mockedSubstitutionReasonValueSet');
120
+ });
121
+
122
+ test('initiateMedicationDispenseBody should initialize medication dispense body from request', () => {
123
+ const activeMedicationRequest: MedicationRequest = {
124
+ intent: '',
125
+ medicationReference: { reference: 'Medication/123abc' },
126
+ meta: { lastUpdated: '' },
127
+ priority: '',
128
+ resourceType: 'MedicationRequest',
129
+ status: MedicationRequestStatus.active,
130
+ subject: { reference: 'Patient/765432' },
131
+ dispenseRequest: {
132
+ numberOfRepeatsAllowed: 0,
133
+ quantity: {
134
+ value: 20.0,
135
+ system: 'http://snomed.info/sct',
136
+ code: '123456789',
137
+ unit: 'Tablet',
138
+ },
139
+ validityPeriod: { start: '' },
140
+ },
141
+ encounter: { reference: '', type: '' },
142
+ requester: {
143
+ display: '',
144
+ identifier: { value: '' },
145
+ reference: '',
146
+ type: '',
147
+ },
148
+ dosageInstruction: [
149
+ {
150
+ text: 'Take with food',
151
+ timing: {
152
+ repeat: {
153
+ duration: 30.0,
154
+ durationUnit: 'd',
155
+ },
156
+ code: {
157
+ coding: [
158
+ {
159
+ code: '',
160
+ },
161
+ ],
162
+ },
163
+ },
164
+ asNeededBoolean: false,
165
+ route: {
166
+ coding: [
167
+ {
168
+ code: '545767',
169
+ },
170
+ ],
171
+ },
172
+ doseAndRate: [
173
+ {
174
+ doseQuantity: {
175
+ value: 5.0,
176
+ unit: 'Tablet',
177
+ system: 'http://snomed.info/sct',
178
+ code: '385055001',
179
+ },
180
+ },
181
+ ],
182
+ },
183
+ ],
184
+ id: '456def',
185
+ };
186
+
187
+ const session: Session = {
188
+ authenticated: true,
189
+ sessionId: '',
190
+ user: undefined,
191
+ currentProvider: {
192
+ uuid: 'ghi789',
193
+ identifier: undefined,
194
+ },
195
+ sessionLocation: {
196
+ uuid: '987654',
197
+ display: undefined,
198
+ links: undefined,
199
+ },
200
+ };
201
+
202
+ const providers: Provider[] = [
203
+ {
204
+ uuid: 'ghi789',
205
+ person: null,
206
+ },
207
+ ];
208
+
209
+ const medicationDispense: MedicationDispense = initiateMedicationDispenseBody(
210
+ activeMedicationRequest,
211
+ session,
212
+ providers,
213
+ true,
214
+ );
215
+
216
+ expect(medicationDispense.id).toBeUndefined();
217
+ expect(medicationDispense.resourceType).toBe('MedicationDispense');
218
+ expect(medicationDispense.medicationReference.reference).toBe('Medication/123abc');
219
+ expect(medicationDispense.status).toBeNull();
220
+ expect(medicationDispense.authorizingPrescription[0].reference).toBe('MedicationRequest/456def');
221
+ expect(medicationDispense.authorizingPrescription[0].type).toBe('MedicationRequest');
222
+ expect(medicationDispense.medicationCodeableConcept).toBeUndefined();
223
+ expect(medicationDispense.subject.reference).toBe('Patient/765432');
224
+ expect(medicationDispense.performer[0].actor.reference).toBe('Practitioner/ghi789');
225
+ expect(medicationDispense.location.reference).toBe('Location/987654');
226
+ expect(medicationDispense.quantity.value).toBe(20.0);
227
+ expect(medicationDispense.quantity.system).toBe('http://snomed.info/sct');
228
+ expect(medicationDispense.quantity.unit).toBe('Tablet');
229
+ expect(medicationDispense.quantity.code).toBe('123456789');
230
+ expect(dayjs(medicationDispense.whenPrepared).isToday()).toBeTruthy();
231
+ expect(dayjs(medicationDispense.whenHandedOver).isToday()).toBeTruthy();
232
+ expect(medicationDispense.dosageInstruction[0].text).toBe('Take with food');
233
+ expect(medicationDispense.dosageInstruction[0].timing.repeat.duration).toBe(30.0);
234
+ expect(medicationDispense.dosageInstruction[0].timing.repeat.durationUnit).toBe('d');
235
+ expect(medicationDispense.dosageInstruction[0].asNeededBoolean).toBe(false);
236
+ expect(medicationDispense.dosageInstruction[0].route.coding[0].code).toBe('545767');
237
+ expect(medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.value).toBe(5);
238
+ expect(medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.system).toBe('http://snomed.info/sct');
239
+ expect(medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.code).toBe('385055001');
240
+ expect(medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.unit).toBe('Tablet');
241
+ expect(medicationDispense.substitution.wasSubstituted).toBe(false);
242
+ });
243
+ });
@@ -0,0 +1,178 @@
1
+ import dayjs from 'dayjs';
2
+ import useSWR from 'swr';
3
+ import { fhirBaseUrl, restBaseUrl, openmrsFetch, type Session } from '@openmrs/esm-framework';
4
+ import {
5
+ type MedicationDispense,
6
+ type MedicationDispenseStatus,
7
+ type MedicationRequest,
8
+ type OrderConfig,
9
+ type Provider,
10
+ type ProviderRequestResponse,
11
+ type ValueSet,
12
+ } from '../types';
13
+
14
+ export function saveMedicationDispense(
15
+ medicationDispense: MedicationDispense,
16
+ medicationDispenseStatus: MedicationDispenseStatus,
17
+ abortController?: AbortController,
18
+ ) {
19
+ // if we have an id, this is an update, otherwise it's a create
20
+ const url = medicationDispense.id
21
+ ? `${fhirBaseUrl}/MedicationDispense/${medicationDispense.id}`
22
+ : `${fhirBaseUrl}/MedicationDispense`;
23
+
24
+ const method = medicationDispense.id ? 'PUT' : 'POST';
25
+
26
+ medicationDispense.status = medicationDispenseStatus;
27
+
28
+ // TODO for now we don't support a different prepared and handed over date, so just set the handed over to the prepared date
29
+ medicationDispense.whenPrepared = medicationDispense.whenHandedOver;
30
+
31
+ return openmrsFetch(url, {
32
+ method: method,
33
+ signal: abortController?.signal,
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: medicationDispense,
38
+ });
39
+ }
40
+
41
+ export function deleteMedicationDispense(medicationDispenseUuid: string) {
42
+ return openmrsFetch(`${fhirBaseUrl}/MedicationDispense/${medicationDispenseUuid}`, {
43
+ method: 'DELETE',
44
+ });
45
+ }
46
+
47
+ export function useOrderConfig() {
48
+ const { data, error, isValidating } = useSWR<{ data: OrderConfig }, Error>(
49
+ `${restBaseUrl}/orderentryconfig`,
50
+ openmrsFetch,
51
+ );
52
+ return {
53
+ orderConfigObject: data ? data.data : null,
54
+ error,
55
+ isLoading: !data && !error,
56
+ isValidating,
57
+ };
58
+ }
59
+
60
+ export function useProviders(providerRoles: Array<string>) {
61
+ const rep = 'custom:(uuid,person:(display)';
62
+ const { data } = useSWR<{ data: ProviderRequestResponse }, Error>(
63
+ providerRoles && providerRoles.length > 0
64
+ ? `${restBaseUrl}/provider?providerRoles=${providerRoles.join(',')}&v=${rep})`
65
+ : `${restBaseUrl}/provider?v=${rep})`,
66
+ openmrsFetch,
67
+ );
68
+ return data?.data?.results.sort((a, b) => a.person?.display.localeCompare(b.person?.display));
69
+ }
70
+
71
+ export function useReasonForPauseValueSet(uuid: string) {
72
+ const valueSet = useValueSet(uuid);
73
+ return { reasonForPauseValueSet: valueSet };
74
+ }
75
+
76
+ export function useReasonForCloseValueSet(uuid: string) {
77
+ const valueSet = useValueSet(uuid);
78
+ return { reasonForCloseValueSet: valueSet };
79
+ }
80
+
81
+ export function useSubstitutionTypeValueSet(uuid: string) {
82
+ const valueSet = useValueSet(uuid);
83
+ return { substitutionTypeValueSet: valueSet };
84
+ }
85
+
86
+ export function useSubstitutionReasonValueSet(uuid: string) {
87
+ const valueSet = useValueSet(uuid);
88
+ return { substitutionReasonValueSet: valueSet };
89
+ }
90
+
91
+ export function useValueSet(uuid: string) {
92
+ const { data } = useSWR<{ data: ValueSet }, Error>(`${fhirBaseUrl}/ValueSet/${uuid}`, openmrsFetch);
93
+ return data ? data.data : null;
94
+ }
95
+
96
+ // TODO: what about the issue with the repetitive reloading?
97
+ export function initiateMedicationDispenseBody(
98
+ medicationRequest: MedicationRequest,
99
+ session: Session,
100
+ providers: Provider[],
101
+ populateDispenseInformation: boolean,
102
+ ): MedicationDispense {
103
+ let medicationDispense: MedicationDispense = {
104
+ resourceType: 'MedicationDispense',
105
+ status: null,
106
+ authorizingPrescription: [
107
+ {
108
+ reference: 'MedicationRequest/' + medicationRequest.id,
109
+ type: 'MedicationRequest',
110
+ },
111
+ ],
112
+ medicationReference: medicationRequest.medicationReference,
113
+ medicationCodeableConcept: medicationRequest.medicationCodeableConcept,
114
+ subject: medicationRequest.subject,
115
+ performer: [
116
+ {
117
+ actor: {
118
+ reference: session?.currentProvider ? `Practitioner/${session.currentProvider.uuid}` : '',
119
+ },
120
+ },
121
+ ],
122
+ location: {
123
+ reference: session?.sessionLocation ? `Location/${session.sessionLocation.uuid}` : '',
124
+ },
125
+ whenHandedOver: dayjs().format(),
126
+ };
127
+
128
+ if (populateDispenseInformation) {
129
+ medicationDispense = {
130
+ ...medicationDispense,
131
+ quantity: {
132
+ value: medicationRequest.dispenseRequest?.quantity?.value,
133
+ code: medicationRequest.dispenseRequest?.quantity?.code,
134
+ unit: medicationRequest.dispenseRequest?.quantity?.unit,
135
+ system: medicationRequest.dispenseRequest?.quantity?.system,
136
+ },
137
+ dosageInstruction: [
138
+ {
139
+ // see https://openmrs.atlassian.net/browse/O3-3791 for an explanation for the reason for the below
140
+ text: [
141
+ medicationRequest.dosageInstruction[0].text,
142
+ medicationRequest.dosageInstruction[0].additionalInstruction?.length > 0
143
+ ? medicationRequest.dosageInstruction[0].additionalInstruction[0].text
144
+ : null,
145
+ ]
146
+ .filter((str) => str != null)
147
+ .join(' '),
148
+ timing: medicationRequest.dosageInstruction[0].timing,
149
+ asNeededBoolean: false,
150
+ route: medicationRequest.dosageInstruction[0].route,
151
+ doseAndRate: medicationRequest.dosageInstruction[0].doseAndRate
152
+ ? medicationRequest.dosageInstruction[0].doseAndRate
153
+ : [
154
+ {
155
+ doseQuantity: {
156
+ value: null,
157
+ code: null,
158
+ unit: null,
159
+ },
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ substitution: blankSubstitution,
165
+ };
166
+ }
167
+ return medicationDispense;
168
+ }
169
+
170
+ export const blankSubstitution = {
171
+ wasSubstituted: false,
172
+ reason: [
173
+ {
174
+ coding: [{ code: null }],
175
+ },
176
+ ],
177
+ type: { coding: [{ code: null }] },
178
+ };