@openmrs/esm-form-engine-lib 2.1.0-pre.1362
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.
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +58 -0
- package/.husky/pre-commit +6 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +4 -0
- package/LICENSE.txt +401 -0
- package/README.md +136 -0
- package/__mocks__/concepts.mock.json +140 -0
- package/__mocks__/forms/afe-forms/component_art.json +38 -0
- package/__mocks__/forms/afe-forms/component_preclinic-review.json +38 -0
- package/__mocks__/forms/afe-forms/demo_hts-form.json +62 -0
- package/__mocks__/forms/afe-forms/form-component.json +38 -0
- package/__mocks__/forms/afe-forms/mini-form.json +31 -0
- package/__mocks__/forms/afe-forms/nested-form1.json +38 -0
- package/__mocks__/forms/afe-forms/nested-form2.json +38 -0
- package/__mocks__/forms/afe-forms/test-orders.json +72 -0
- package/__mocks__/forms/afe-forms/test-schema-transformer-form.json +88 -0
- package/__mocks__/forms/rfe-forms/age-validation-form.json +58 -0
- package/__mocks__/forms/rfe-forms/bmi-test-form.json +69 -0
- package/__mocks__/forms/rfe-forms/bsa-test-form.json +69 -0
- package/__mocks__/forms/rfe-forms/component_art.json +1705 -0
- package/__mocks__/forms/rfe-forms/component_preclinic-review.json +480 -0
- package/__mocks__/forms/rfe-forms/conditional-answered-form.json +97 -0
- package/__mocks__/forms/rfe-forms/conditional-required-form.json +281 -0
- package/__mocks__/forms/rfe-forms/demo_hts-form.json +346 -0
- package/__mocks__/forms/rfe-forms/edd-test-form.json +88 -0
- package/__mocks__/forms/rfe-forms/external_data_source_form.json +43 -0
- package/__mocks__/forms/rfe-forms/filter-answer-options-test-form.json +87 -0
- package/__mocks__/forms/rfe-forms/form-component.json +43 -0
- package/__mocks__/forms/rfe-forms/forms-loader.test.schema.ts +209 -0
- package/__mocks__/forms/rfe-forms/historical-expressions-form.json +170 -0
- package/__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json +374 -0
- package/__mocks__/forms/rfe-forms/mini-form.json +29 -0
- package/__mocks__/forms/rfe-forms/mockHistoricalvisitsEncounter.json +89 -0
- package/__mocks__/forms/rfe-forms/months-on-art-form.json +90 -0
- package/__mocks__/forms/rfe-forms/multi-select-form.json +86 -0
- package/__mocks__/forms/rfe-forms/nested-form1.json +43 -0
- package/__mocks__/forms/rfe-forms/nested-form2.json +43 -0
- package/__mocks__/forms/rfe-forms/next-visit-test-form.json +78 -0
- package/__mocks__/forms/rfe-forms/obs-group-test_form.json +137 -0
- package/__mocks__/forms/rfe-forms/obs-list-data.ts +37 -0
- package/__mocks__/forms/rfe-forms/post-submission-test-form.json +116 -0
- package/__mocks__/forms/rfe-forms/reference-by-mapping-form.json +54 -0
- package/__mocks__/forms/rfe-forms/required-form.json +50 -0
- package/__mocks__/forms/rfe-forms/sample_fields.json +36 -0
- package/__mocks__/forms/rfe-forms/test-enrolment-form.json +241 -0
- package/__mocks__/forms/rfe-forms/treatment-end-date-test-form.json +121 -0
- package/__mocks__/forms/rfe-forms/viral-load-status-form.json +75 -0
- package/__mocks__/forms/rfe-forms/zscore-bmi-for-age-form.json +79 -0
- package/__mocks__/forms/rfe-forms/zscore-height-for-age-form.json +79 -0
- package/__mocks__/forms/rfe-forms/zscore-weight-height-form.json +77 -0
- package/__mocks__/packages/hiv/forms/hts_poc/1.0.json +8 -0
- package/__mocks__/packages/hiv/forms/hts_poc/1.1.json +91 -0
- package/__mocks__/packages/test-forms-registry.ts +12 -0
- package/__mocks__/patient.mock.ts +173 -0
- package/__mocks__/react-i18next.js +49 -0
- package/__mocks__/react-markdown.tsx +5 -0
- package/__mocks__/session.mock.ts +117 -0
- package/__mocks__/single-spa-react.js +11 -0
- package/__mocks__/use-initial-values/encounter.mock.json +963 -0
- package/__mocks__/use-initial-values/patient.mock.json +73 -0
- package/__mocks__/visit.mock.ts +19 -0
- package/dist/openmrs-esm-form-engine-lib.js +1 -0
- package/jest.config.js +30 -0
- package/package.json +104 -0
- package/prettier.config.js +8 -0
- package/readme/form-engine.jpeg +0 -0
- package/src/adapters/control-adapter.ts +29 -0
- package/src/adapters/encounter-datetime-adapter.ts +38 -0
- package/src/adapters/encounter-location-adapter.ts +39 -0
- package/src/adapters/encounter-provider-adapter.ts +48 -0
- package/src/adapters/encounter-role-adapter.ts +54 -0
- package/src/adapters/inline-date-adapter.ts +58 -0
- package/src/adapters/obs-adapter.ts +280 -0
- package/src/adapters/obs-comment-adapter.ts +60 -0
- package/src/adapters/orders-adapter.ts +75 -0
- package/src/adapters/patient-identifier-adapter.ts +40 -0
- package/src/adapters/program-state-adapter.ts +52 -0
- package/src/api/index.ts +178 -0
- package/src/components/error/error-modal.component.tsx +37 -0
- package/src/components/error/error.scss +4 -0
- package/src/components/extension/extension-parcel.component.tsx +32 -0
- package/src/components/field-label/field-label.component.tsx +32 -0
- package/src/components/field-label/field-label.scss +11 -0
- package/src/components/group/obs-group.component.tsx +29 -0
- package/src/components/group/obs-group.scss +12 -0
- package/src/components/inputs/content-switcher/content-switcher.component.tsx +71 -0
- package/src/components/inputs/content-switcher/content-switcher.scss +55 -0
- package/src/components/inputs/date/date.component.tsx +149 -0
- package/src/components/inputs/date/date.scss +36 -0
- package/src/components/inputs/file/camera/camera.component.tsx +34 -0
- package/src/components/inputs/file/camera/camera.scss +3 -0
- package/src/components/inputs/file/file.component.tsx +159 -0
- package/src/components/inputs/file/file.scss +101 -0
- package/src/components/inputs/fixed-value/fixed-value.component.tsx +19 -0
- package/src/components/inputs/markdown/markdown-wrapper.component.tsx +14 -0
- package/src/components/inputs/markdown/markdown.component.tsx +8 -0
- package/src/components/inputs/multi-select/multi-select.component.tsx +151 -0
- package/src/components/inputs/multi-select/multi-select.scss +25 -0
- package/src/components/inputs/multi-select/multi-select.test.tsx +90 -0
- package/src/components/inputs/number/number.component.tsx +69 -0
- package/src/components/inputs/number/number.scss +15 -0
- package/src/components/inputs/radio/radio.component.tsx +79 -0
- package/src/components/inputs/radio/radio.scss +36 -0
- package/src/components/inputs/select/dropdown.component.tsx +73 -0
- package/src/components/inputs/select/dropdown.scss +11 -0
- package/src/components/inputs/select/dropdown.test.tsx +120 -0
- package/src/components/inputs/text/text.component.tsx +65 -0
- package/src/components/inputs/text/text.scss +15 -0
- package/src/components/inputs/text/text.test.tsx +104 -0
- package/src/components/inputs/text-area/text-area.component.tsx +63 -0
- package/src/components/inputs/text-area/text-area.scss +11 -0
- package/src/components/inputs/toggle/toggle.component.tsx +66 -0
- package/src/components/inputs/toggle/toggle.scss +12 -0
- package/src/components/inputs/tooltip/tooltip.component.tsx +23 -0
- package/src/components/inputs/tooltip/tooltip.scss +8 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.component.tsx +187 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.scss +15 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx +211 -0
- package/src/components/inputs/unspecified/unspecified.component.tsx +74 -0
- package/src/components/inputs/unspecified/unspecified.scss +7 -0
- package/src/components/inputs/unspecified/unspecified.test.tsx +95 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.component.tsx +35 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.scss +15 -0
- package/src/components/label/label.component.tsx +20 -0
- package/src/components/label/label.scss +11 -0
- package/src/components/loaders/loader.component.tsx +16 -0
- package/src/components/loaders/loader.scss +20 -0
- package/src/components/patient-banner/patient-banner.component.tsx +20 -0
- package/src/components/patient-banner/patient-banner.scss +12 -0
- package/src/components/previous-value-review/previous-value-review.component.tsx +49 -0
- package/src/components/previous-value-review/previous-value-review.scss +36 -0
- package/src/components/processor-factory/form-processor-factory.component.tsx +127 -0
- package/src/components/renderer/custom-hooks-renderer.component.tsx +30 -0
- package/src/components/renderer/field/fieldLogic.ts +214 -0
- package/src/components/renderer/field/form-field-renderer.component.tsx +281 -0
- package/src/components/renderer/field/form-field-renderer.scss +5 -0
- package/src/components/renderer/form/form-renderer.component.tsx +89 -0
- package/src/components/renderer/form/state.ts +54 -0
- package/src/components/renderer/page/page.renderer.component.tsx +50 -0
- package/src/components/renderer/page/page.renderer.scss +36 -0
- package/src/components/renderer/section/section-renderer.component.tsx +21 -0
- package/src/components/renderer/section/section-renderer.scss +19 -0
- package/src/components/repeat/helpers.test.ts +29 -0
- package/src/components/repeat/helpers.ts +68 -0
- package/src/components/repeat/repeat-controls.component.tsx +38 -0
- package/src/components/repeat/repeat-controls.scss +7 -0
- package/src/components/repeat/repeat.component.tsx +201 -0
- package/src/components/repeat/repeat.scss +30 -0
- package/src/components/repeat/repeat.test.ts +29 -0
- package/src/components/sidebar/sidebar.component.tsx +134 -0
- package/src/components/sidebar/sidebar.scss +121 -0
- package/src/components/value/value.component.tsx +27 -0
- package/src/components/value/value.scss +17 -0
- package/src/components/value/view/field-value-view.component.tsx +33 -0
- package/src/components/value/view/field-value-view.scss +31 -0
- package/src/constants.ts +12 -0
- package/src/datasources/concept-data-source.ts +42 -0
- package/src/datasources/data-source.ts +23 -0
- package/src/datasources/encounter-role-datasource.ts +15 -0
- package/src/datasources/historical-data-source.ts +11 -0
- package/src/datasources/location-data-source.ts +27 -0
- package/src/datasources/provider-datasource.ts +15 -0
- package/src/datasources/select-concept-answers-datasource.ts +15 -0
- package/src/declarations.d.ts +4 -0
- package/src/external-function-context.tsx +8 -0
- package/src/form-context.tsx +42 -0
- package/src/form-engine.component.tsx +178 -0
- package/src/form-engine.scss +140 -0
- package/src/form-engine.test.tsx +817 -0
- package/src/globals.ts +1 -0
- package/src/hooks/useClobData.tsx +21 -0
- package/src/hooks/useConcepts.tsx +55 -0
- package/src/hooks/useDatasourceDependentValue.ts +16 -0
- package/src/hooks/useEncounter.tsx +32 -0
- package/src/hooks/useEncounterRole.tsx +15 -0
- package/src/hooks/useEvaluateFormFieldExpressions.ts +138 -0
- package/src/hooks/useFieldValidationResults.ts +18 -0
- package/src/hooks/useFormCollapse.tsx +36 -0
- package/src/hooks/useFormFieldValidators.ts +22 -0
- package/src/hooks/useFormFieldValueAdapters.ts +24 -0
- package/src/hooks/useFormFields.ts +37 -0
- package/src/hooks/useFormFieldsMeta.ts +48 -0
- package/src/hooks/useFormJson.test.tsx +173 -0
- package/src/hooks/useFormJson.tsx +237 -0
- package/src/hooks/useFormStateHelpers.ts +50 -0
- package/src/hooks/useFormsConfig.tsx +27 -0
- package/src/hooks/useInitialValues.ts +38 -0
- package/src/hooks/usePatientData.tsx +32 -0
- package/src/hooks/usePatientPrograms.ts +32 -0
- package/src/hooks/usePostSubmissionActions.test.tsx +42 -0
- package/src/hooks/usePostSubmissionActions.ts +31 -0
- package/src/hooks/useProcessorDependencies.ts +30 -0
- package/src/hooks/useRestMaxResultsCount.ts +5 -0
- package/src/hooks/useSystemSetting.ts +36 -0
- package/src/hooks/useWorkspaceLayout.ts +29 -0
- package/src/index.ts +12 -0
- package/src/lifecycle.ts +33 -0
- package/src/post-submission-actions/program-enrollment-action.ts +138 -0
- package/src/processors/encounter/encounter-form-processor.ts +337 -0
- package/src/processors/encounter/encounter-processor-helper.ts +320 -0
- package/src/processors/form-processor.ts +41 -0
- package/src/provider/form-factory-helper.ts +100 -0
- package/src/provider/form-factory-provider.tsx +169 -0
- package/src/provider/form-provider.tsx +37 -0
- package/src/registry/inbuilt-components/InbuiltPostSubmissionActions.ts +9 -0
- package/src/registry/inbuilt-components/control-templates.ts +57 -0
- package/src/registry/inbuilt-components/inbuiltControls.ts +99 -0
- package/src/registry/inbuilt-components/inbuiltDataSources.ts +41 -0
- package/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +64 -0
- package/src/registry/inbuilt-components/inbuiltTransformers.ts +10 -0
- package/src/registry/inbuilt-components/inbuiltValidators.ts +33 -0
- package/src/registry/inbuilt-components/template-component-map.ts +28 -0
- package/src/registry/registry.test.ts +20 -0
- package/src/registry/registry.ts +261 -0
- package/src/routes.json +1 -0
- package/src/setupI18n.ts +16 -0
- package/src/setupTests.ts +5 -0
- package/src/transformers/default-schema-transformer.test.ts +155 -0
- package/src/transformers/default-schema-transformer.ts +239 -0
- package/src/types/domain.ts +129 -0
- package/src/types/index.ts +130 -0
- package/src/types/schema.ts +238 -0
- package/src/typings.d.ts +9 -0
- package/src/utils/boolean-utils.ts +25 -0
- package/src/utils/common-expression-helpers.ts +503 -0
- package/src/utils/common-utils.test.ts +136 -0
- package/src/utils/common-utils.ts +55 -0
- package/src/utils/error-utils.ts +37 -0
- package/src/utils/expression-parser.test.ts +308 -0
- package/src/utils/expression-parser.ts +158 -0
- package/src/utils/expression-runner.test.ts +387 -0
- package/src/utils/expression-runner.ts +219 -0
- package/src/utils/form-helper.test.ts +482 -0
- package/src/utils/form-helper.ts +210 -0
- package/src/utils/form-page-utils.ts +13 -0
- package/src/utils/forms-loader.test.ts +323 -0
- package/src/utils/forms-loader.ts +306 -0
- package/src/utils/post-submission-action-helper.ts +71 -0
- package/src/utils/test-utils.ts +54 -0
- package/src/utils/zscore-service.ts +59 -0
- package/src/validators/conditional-answered-validator.test.ts +61 -0
- package/src/validators/conditional-answered-validator.ts +17 -0
- package/src/validators/date-validator.test.ts +46 -0
- package/src/validators/date-validator.ts +19 -0
- package/src/validators/default-value-validator.test.ts +90 -0
- package/src/validators/default-value-validator.ts +36 -0
- package/src/validators/form-validator.test.ts +188 -0
- package/src/validators/form-validator.ts +95 -0
- package/src/validators/js-expression-validator.test.ts +118 -0
- package/src/validators/js-expression-validator.ts +44 -0
- package/src/validators/schema.ts +34 -0
- package/src/zscore/bfa_boys_5_above.json +2522 -0
- package/src/zscore/bfa_girls_5_above.json +2522 -0
- package/src/zscore/hfa_boys_5_above.json +2186 -0
- package/src/zscore/hfa_boys_below5.json +22286 -0
- package/src/zscore/hfa_girls_5_above.json +2186 -0
- package/src/zscore/hfa_girls_below5.json +22286 -0
- package/src/zscore/wfl_boys_below5.json +7814 -0
- package/src/zscore/wfl_girls_below5.json +7814 -0
- package/src/zscore-tests/bmi-age.test.tsx +88 -0
- package/src/zscore-tests/height-age.test.tsx +96 -0
- package/src/zscore-tests/weight-height.test.tsx +87 -0
- package/tools/i18next-parser.config.js +93 -0
- package/translations/en.json +47 -0
- package/translations/es.json +38 -0
- package/translations/fr.json +38 -0
- package/translations/km.json +38 -0
- package/tsconfig.json +19 -0
- package/turbo.json +15 -0
- package/webpack.config.js +1 -0
@@ -0,0 +1,280 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
import { ConceptTrue, codedTypes } from '../constants';
|
3
|
+
import {
|
4
|
+
type OpenmrsObs,
|
5
|
+
type FormField,
|
6
|
+
type OpenmrsEncounter,
|
7
|
+
type AttachmentResponse,
|
8
|
+
type Attachment,
|
9
|
+
type ValueAndDisplay,
|
10
|
+
} from '../types';
|
11
|
+
import { hasRendering, gracefullySetSubmission, clearSubmission, flattenObsList } from '../utils/common-utils';
|
12
|
+
import { parseToLocalDateTime } from '../utils/form-helper';
|
13
|
+
import { type FormContextProps } from '../provider/form-provider';
|
14
|
+
import { type FormFieldValueAdapter } from '../types';
|
15
|
+
import { isEmpty } from '../validators/form-validator';
|
16
|
+
import { getAttachmentByUuid } from '../api';
|
17
|
+
import { formatDate, restBaseUrl } from '@openmrs/esm-framework';
|
18
|
+
|
19
|
+
// Temporarily holds observations that have already been bound with matching fields
|
20
|
+
export let assignedObsIds: string[] = [];
|
21
|
+
|
22
|
+
export const ObsAdapter: FormFieldValueAdapter = {
|
23
|
+
async getInitialValue(field: FormField, sourceObject: any, context: FormContextProps) {
|
24
|
+
const encounter = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
|
25
|
+
if (hasRendering(field, 'file')) {
|
26
|
+
const ac = new AbortController();
|
27
|
+
const attachmentsResponse = await getAttachmentByUuid(context.patient.id, encounter.uuid, ac);
|
28
|
+
// TODO: This seems like a violation of the data model.
|
29
|
+
// I think we should instead use something like `formFieldPath` to do the mapping.
|
30
|
+
const rawAttachment = attachmentsResponse.results?.find((attachment) => attachment.comment === field.id);
|
31
|
+
return rawAttachment ? generateAttachment(rawAttachment) : null;
|
32
|
+
}
|
33
|
+
return extractFieldValue(field, findObsByFormField(flattenObsList(encounter.obs), assignedObsIds, field), true);
|
34
|
+
},
|
35
|
+
async getPreviousValue(field: FormField, sourceObject: any, context: FormContextProps): Promise<ValueAndDisplay> {
|
36
|
+
const encounter = sourceObject ?? (context.previousDomainObjectValue as OpenmrsEncounter);
|
37
|
+
if (encounter) {
|
38
|
+
const value = extractFieldValue(
|
39
|
+
field,
|
40
|
+
findObsByFormField(flattenObsList(encounter.obs), assignedObsIds, field),
|
41
|
+
true,
|
42
|
+
);
|
43
|
+
if (!isEmpty(value)) {
|
44
|
+
return {
|
45
|
+
value,
|
46
|
+
display: this.getDisplayValue(field, value),
|
47
|
+
};
|
48
|
+
}
|
49
|
+
}
|
50
|
+
return null;
|
51
|
+
},
|
52
|
+
getDisplayValue: (field: FormField, value: any) => {
|
53
|
+
const rendering = field.questionOptions.rendering;
|
54
|
+
if (isEmpty(value)) {
|
55
|
+
return value;
|
56
|
+
}
|
57
|
+
if (rendering == 'checkbox') {
|
58
|
+
return value.map(
|
59
|
+
(selected) => field.questionOptions.answers?.find((option) => option.concept == selected)?.label,
|
60
|
+
);
|
61
|
+
}
|
62
|
+
if (rendering === 'toggle') {
|
63
|
+
return value ? field.questionOptions.toggleOptions.labelTrue : field.questionOptions.toggleOptions.labelFalse;
|
64
|
+
}
|
65
|
+
if (codedTypes.includes(rendering)) {
|
66
|
+
return field.questionOptions.answers?.find((option) => option.concept == value)?.label;
|
67
|
+
}
|
68
|
+
return value;
|
69
|
+
},
|
70
|
+
transformFieldValue: (field: FormField, value: any, context: FormContextProps) => {
|
71
|
+
// clear previous submission
|
72
|
+
clearSubmission(field);
|
73
|
+
if (!field.meta.previousValue && isEmpty(value)) {
|
74
|
+
return null;
|
75
|
+
}
|
76
|
+
if (hasRendering(field, 'checkbox')) {
|
77
|
+
return handleMultiSelect(field, value);
|
78
|
+
}
|
79
|
+
if (!isEmpty(value) && hasPreviousObsValueChanged(field, value)) {
|
80
|
+
return gracefullySetSubmission(field, editObs(field, value), undefined);
|
81
|
+
}
|
82
|
+
if (field.meta.previousValue && isEmpty(value)) {
|
83
|
+
return gracefullySetSubmission(field, undefined, voidObs(field.meta.previousValue));
|
84
|
+
}
|
85
|
+
if (!isEmpty(value)) {
|
86
|
+
return gracefullySetSubmission(field, constructObs(field, value), undefined);
|
87
|
+
}
|
88
|
+
return null;
|
89
|
+
},
|
90
|
+
tearDown: function (): void {
|
91
|
+
assignedObsIds = [];
|
92
|
+
},
|
93
|
+
};
|
94
|
+
|
95
|
+
// Helpers
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Extracts field's primitive value from obs
|
99
|
+
*/
|
100
|
+
function extractFieldValue(field: FormField, obsList: OpenmrsObs[] = [], makeFieldDirty = false) {
|
101
|
+
const rendering = field.questionOptions.rendering;
|
102
|
+
if (!field.meta) {
|
103
|
+
field.meta = {
|
104
|
+
previousValue: null,
|
105
|
+
};
|
106
|
+
}
|
107
|
+
if (obsList.length) {
|
108
|
+
if (rendering == 'checkbox') {
|
109
|
+
assignedObsIds.push(...obsList.map((obs) => obs.uuid));
|
110
|
+
field.meta.previousValue = makeFieldDirty ? obsList : null;
|
111
|
+
return obsList.map((o) => o.value.uuid);
|
112
|
+
}
|
113
|
+
const obs = obsList[0];
|
114
|
+
if (makeFieldDirty) {
|
115
|
+
field.meta.previousValue = { ...obs };
|
116
|
+
}
|
117
|
+
assignedObsIds.push(obs.uuid);
|
118
|
+
if (typeof obs.value === 'string' || typeof obs.value === 'number') {
|
119
|
+
if (rendering.startsWith('date')) {
|
120
|
+
const dateObject = parseToLocalDateTime(obs.value as string);
|
121
|
+
if (makeFieldDirty) {
|
122
|
+
field.meta.previousValue.value = dayjs(dateObject).format('YYYY-MM-DD HH:mm');
|
123
|
+
}
|
124
|
+
return dateObject;
|
125
|
+
}
|
126
|
+
return obs.value;
|
127
|
+
}
|
128
|
+
if (rendering == 'toggle') {
|
129
|
+
return obs.value.uuid === ConceptTrue;
|
130
|
+
}
|
131
|
+
if (rendering == 'fixed-value') {
|
132
|
+
return field['fixedValue'];
|
133
|
+
}
|
134
|
+
return obs.value?.uuid;
|
135
|
+
}
|
136
|
+
return '';
|
137
|
+
}
|
138
|
+
|
139
|
+
export function constructObs(field: FormField, value: any): Partial<OpenmrsObs> {
|
140
|
+
if (isEmpty(value) && field.type !== 'obsGroup') {
|
141
|
+
return null;
|
142
|
+
}
|
143
|
+
const draftObs =
|
144
|
+
field.type === 'obsGroup'
|
145
|
+
? { groupMembers: [] }
|
146
|
+
: {
|
147
|
+
value: field.questionOptions.rendering.startsWith('date') ? formatDateByPickerType(field, value) : value,
|
148
|
+
};
|
149
|
+
return {
|
150
|
+
...draftObs,
|
151
|
+
concept: field.questionOptions.concept,
|
152
|
+
formFieldNamespace: 'rfe-forms',
|
153
|
+
formFieldPath: `rfe-forms-${field.id}`,
|
154
|
+
};
|
155
|
+
}
|
156
|
+
|
157
|
+
export function voidObs(obs: OpenmrsObs) {
|
158
|
+
return { uuid: obs.uuid, voided: true };
|
159
|
+
}
|
160
|
+
|
161
|
+
export function editObs(field: FormField, newValue: any) {
|
162
|
+
const oldObs = field.meta.previousValue;
|
163
|
+
const formattedValue = field.questionOptions.rendering.startsWith('date')
|
164
|
+
? formatDateByPickerType(field, newValue)
|
165
|
+
: newValue;
|
166
|
+
return {
|
167
|
+
uuid: oldObs.uuid,
|
168
|
+
value: formattedValue,
|
169
|
+
formFieldNamespace: 'rfe-forms',
|
170
|
+
formFieldPath: `rfe-forms-${field.id}`,
|
171
|
+
};
|
172
|
+
}
|
173
|
+
|
174
|
+
function formatDateByPickerType(field: FormField, value: Date) {
|
175
|
+
if (field.datePickerFormat) {
|
176
|
+
switch (field.datePickerFormat) {
|
177
|
+
case 'calendar':
|
178
|
+
return dayjs(value).format('YYYY-MM-DD');
|
179
|
+
case 'timer':
|
180
|
+
return dayjs(value).format('HH:mm');
|
181
|
+
case 'both':
|
182
|
+
return dayjs(value).format('YYYY-MM-DD HH:mm');
|
183
|
+
default:
|
184
|
+
return dayjs(value).format('YYYY-MM-DD');
|
185
|
+
}
|
186
|
+
}
|
187
|
+
return value;
|
188
|
+
}
|
189
|
+
|
190
|
+
export function hasPreviousObsValueChanged(field: FormField, newValue: any) {
|
191
|
+
const previousObs = field.meta.previousValue;
|
192
|
+
if (isEmpty(previousObs)) {
|
193
|
+
return false;
|
194
|
+
}
|
195
|
+
if (codedTypes.includes(field.questionOptions.rendering)) {
|
196
|
+
return previousObs.value.uuid !== newValue;
|
197
|
+
}
|
198
|
+
if (hasRendering(field, 'date')) {
|
199
|
+
return dayjs(newValue).diff(dayjs(previousObs.value), 'D') !== 0;
|
200
|
+
}
|
201
|
+
if (hasRendering(field, 'datetime') || field.datePickerFormat === 'both') {
|
202
|
+
return dayjs(newValue).diff(dayjs(previousObs.value), 'minute') !== 0;
|
203
|
+
}
|
204
|
+
if (hasRendering(field, 'toggle')) {
|
205
|
+
return (previousObs.value.uuid === ConceptTrue) !== newValue;
|
206
|
+
}
|
207
|
+
return previousObs.value !== newValue;
|
208
|
+
}
|
209
|
+
|
210
|
+
function handleMultiSelect(field: FormField, values: Array<string> = []) {
|
211
|
+
// three possible scenarios
|
212
|
+
// 1. we have a previous value and an empty current value
|
213
|
+
// 2. a mix of both (previous and current)
|
214
|
+
// 3. we only have a current value
|
215
|
+
|
216
|
+
if (field.meta.previousValue && isEmpty(values)) {
|
217
|
+
// we assume the user cleared the existing value(s)
|
218
|
+
// so we void all previous values
|
219
|
+
return gracefullySetSubmission(
|
220
|
+
field,
|
221
|
+
null,
|
222
|
+
field.meta.previousValue.map((previousValue) => voidObs(previousValue)),
|
223
|
+
);
|
224
|
+
}
|
225
|
+
if (field.meta.previousValue && !isEmpty(values)) {
|
226
|
+
const toBeVoided = field.meta.previousValue.filter((obs) => !values.includes(obs.value.uuid));
|
227
|
+
const toBeCreated = values.filter((v) => !field.meta.previousValue.some((obs) => obs.value.uuid === v));
|
228
|
+
return gracefullySetSubmission(
|
229
|
+
field,
|
230
|
+
toBeCreated.map((value) => constructObs(field, value)),
|
231
|
+
toBeVoided.map((obs) => voidObs(obs)),
|
232
|
+
);
|
233
|
+
}
|
234
|
+
return gracefullySetSubmission(
|
235
|
+
field,
|
236
|
+
values.map((value) => constructObs(field, value)),
|
237
|
+
undefined,
|
238
|
+
);
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Retrieves a list of observations from a given `obsList` that correspond to the specified field.
|
243
|
+
*
|
244
|
+
* Notes:
|
245
|
+
* If the query by field-path returns an empty list, the function falls back to querying
|
246
|
+
* by concept and uses `claimedObsIds` to exclude already assigned observations.
|
247
|
+
*/
|
248
|
+
export function findObsByFormField(
|
249
|
+
obsList: Array<OpenmrsObs>,
|
250
|
+
claimedObsIds: string[],
|
251
|
+
field: FormField,
|
252
|
+
): OpenmrsObs[] {
|
253
|
+
const obs = obsList.filter(
|
254
|
+
(o) => o.formFieldPath == `rfe-forms-${field.id}` && o.concept.uuid == field.questionOptions.concept,
|
255
|
+
);
|
256
|
+
|
257
|
+
// We shall fall back to mapping by the associated concept
|
258
|
+
// That being said, we shall find all matching obs and pick the one that wasn't previously claimed.
|
259
|
+
if (!obs?.length) {
|
260
|
+
const obsByConcept = obsList.filter((obs) => obs.concept.uuid == field.questionOptions.concept);
|
261
|
+
return claimedObsIds?.length ? obsByConcept.filter((obs) => !claimedObsIds.includes(obs.uuid)) : obsByConcept;
|
262
|
+
}
|
263
|
+
|
264
|
+
return obs;
|
265
|
+
}
|
266
|
+
|
267
|
+
function generateAttachment(rawAttachment: AttachmentResponse): Attachment {
|
268
|
+
const attachmentUrl = `${restBaseUrl}/attachment`;
|
269
|
+
return {
|
270
|
+
id: rawAttachment.uuid,
|
271
|
+
src: `${window.openmrsBase}${attachmentUrl}/${rawAttachment.uuid}/bytes`,
|
272
|
+
title: rawAttachment.comment,
|
273
|
+
description: '',
|
274
|
+
dateTime: formatDate(new Date(rawAttachment.dateTime), {
|
275
|
+
mode: 'wide',
|
276
|
+
}),
|
277
|
+
bytesMimeType: rawAttachment.bytesMimeType,
|
278
|
+
bytesContentFamily: rawAttachment.bytesContentFamily,
|
279
|
+
};
|
280
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
2
|
+
import { type FormContextProps } from '../provider/form-provider';
|
3
|
+
import { type FormField, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types';
|
4
|
+
import { hasSubmission } from '../utils/common-utils';
|
5
|
+
import { isEmpty } from '../validators/form-validator';
|
6
|
+
import { editObs, hasPreviousObsValueChanged } from './obs-adapter';
|
7
|
+
|
8
|
+
export const ObsCommentAdapter: FormFieldValueAdapter = {
|
9
|
+
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
|
10
|
+
const targetField = context.getFormField(field.meta.targetField);
|
11
|
+
const targetFieldCurrentValue = context.methods.getValues(targetField.id);
|
12
|
+
|
13
|
+
if (targetField.meta.submission?.newValue) {
|
14
|
+
if (isEmpty(value) && !isNewSubmissionEffective(targetField, targetFieldCurrentValue)) {
|
15
|
+
// clear submission
|
16
|
+
targetField.meta.submission.newValue = null;
|
17
|
+
} else {
|
18
|
+
targetField.meta.submission.newValue.comment = value;
|
19
|
+
}
|
20
|
+
} else if (!hasSubmission(targetField) && targetField.meta.previousValue) {
|
21
|
+
if (isEmpty(value) && isEmpty(targetField.meta.previousValue.comment)) {
|
22
|
+
return null;
|
23
|
+
}
|
24
|
+
// generate submission
|
25
|
+
const newSubmission = editObs(targetField, targetFieldCurrentValue);
|
26
|
+
targetField.meta.submission = {
|
27
|
+
newValue: {
|
28
|
+
...newSubmission,
|
29
|
+
comment: value,
|
30
|
+
},
|
31
|
+
};
|
32
|
+
}
|
33
|
+
return null;
|
34
|
+
},
|
35
|
+
getInitialValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) {
|
36
|
+
const encounter = sourceObject ?? context.domainObjectValue;
|
37
|
+
if (encounter) {
|
38
|
+
const targetFieldId = field.id.split('_obs_comment')[0];
|
39
|
+
const targetField = context.formFields.find((field) => field.id === targetFieldId);
|
40
|
+
return targetField?.meta.previousValue?.comment;
|
41
|
+
}
|
42
|
+
return null;
|
43
|
+
},
|
44
|
+
getPreviousValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) {
|
45
|
+
return null;
|
46
|
+
},
|
47
|
+
getDisplayValue: function (field: FormField, value: string) {
|
48
|
+
return value;
|
49
|
+
},
|
50
|
+
tearDown: function (): void {
|
51
|
+
return;
|
52
|
+
},
|
53
|
+
};
|
54
|
+
|
55
|
+
export function isNewSubmissionEffective(targetField: FormField, targetFieldCurrentValue: any) {
|
56
|
+
return (
|
57
|
+
hasPreviousObsValueChanged(targetField, targetFieldCurrentValue) ||
|
58
|
+
!isEmpty(targetField.meta.submission.newValue.obsDatetime)
|
59
|
+
);
|
60
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
2
|
+
import { type FormFieldValueAdapter, type FormProcessorContextProps } from '..';
|
3
|
+
import { type FormContextProps } from '../provider/form-provider';
|
4
|
+
import { type FormField } from '../types';
|
5
|
+
import { clearSubmission, gracefullySetSubmission } from '../utils/common-utils';
|
6
|
+
|
7
|
+
export let assignedOrderIds: string[] = [];
|
8
|
+
const defaultOrderType = 'testorder';
|
9
|
+
const defaultCareSetting = '6f0c9a92-6f24-11e3-af88-005056821db0';
|
10
|
+
|
11
|
+
export const OrdersAdapter: FormFieldValueAdapter = {
|
12
|
+
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
|
13
|
+
if (context.sessionMode == 'edit' && field.meta?.previousValue?.uuid) {
|
14
|
+
return editOrder(value, field, context.currentProvider.uuid);
|
15
|
+
}
|
16
|
+
const newValue = constructNewOrder(value, field, context.currentProvider.uuid);
|
17
|
+
gracefullySetSubmission(field, newValue, null);
|
18
|
+
return newValue;
|
19
|
+
},
|
20
|
+
getInitialValue: function (
|
21
|
+
field: FormField,
|
22
|
+
sourceObject: OpenmrsResource,
|
23
|
+
context: FormProcessorContextProps,
|
24
|
+
): Promise<any> {
|
25
|
+
const availableOrderables = field.questionOptions.answers?.map((answer) => answer.concept) || [];
|
26
|
+
const matchedOrder = sourceObject?.orders
|
27
|
+
.filter((order) => !assignedOrderIds.includes(order.uuid) && !order.voided)
|
28
|
+
.find((order) => availableOrderables.includes(order.concept.uuid));
|
29
|
+
if (matchedOrder) {
|
30
|
+
field.meta = { ...(field.meta || {}), previousValue: matchedOrder };
|
31
|
+
assignedOrderIds.push(matchedOrder.uuid);
|
32
|
+
return matchedOrder.concept.uuid;
|
33
|
+
}
|
34
|
+
return null;
|
35
|
+
},
|
36
|
+
getPreviousValue: function (
|
37
|
+
field: FormField,
|
38
|
+
sourceObject: OpenmrsResource,
|
39
|
+
context: FormProcessorContextProps,
|
40
|
+
): Promise<any> {
|
41
|
+
return null;
|
42
|
+
},
|
43
|
+
getDisplayValue: (field: FormField, value: any) => {
|
44
|
+
return field.questionOptions.answers?.find((option) => option.concept == value)?.label || value;
|
45
|
+
},
|
46
|
+
tearDown: function (): void {
|
47
|
+
assignedOrderIds = [];
|
48
|
+
},
|
49
|
+
};
|
50
|
+
|
51
|
+
function constructNewOrder(value: any, field: FormField, orderer: string) {
|
52
|
+
if (!value) {
|
53
|
+
return null;
|
54
|
+
}
|
55
|
+
return {
|
56
|
+
action: 'NEW',
|
57
|
+
concept: value,
|
58
|
+
type: field?.questionOptions?.orderType || defaultOrderType,
|
59
|
+
careSetting: field?.questionOptions?.orderSettingUuid || defaultCareSetting,
|
60
|
+
orderer: orderer,
|
61
|
+
};
|
62
|
+
}
|
63
|
+
|
64
|
+
function editOrder(newOrder: any, field: FormField, orderer: string) {
|
65
|
+
if (newOrder === field.meta.previousValue?.concept?.uuid) {
|
66
|
+
clearSubmission(field);
|
67
|
+
return null;
|
68
|
+
}
|
69
|
+
const voided = {
|
70
|
+
uuid: field.meta.previousValue?.uuid,
|
71
|
+
voided: true,
|
72
|
+
};
|
73
|
+
gracefullySetSubmission(field, constructNewOrder(newOrder, field, orderer), voided);
|
74
|
+
return field.meta.submission.newValue || null;
|
75
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
2
|
+
import { type FormContextProps } from '../provider/form-provider';
|
3
|
+
import { type FormField, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types';
|
4
|
+
import { clearSubmission } from '../utils/common-utils';
|
5
|
+
import { isEmpty } from '../validators/form-validator';
|
6
|
+
|
7
|
+
export const PatientIdentifierAdapter: FormFieldValueAdapter = {
|
8
|
+
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
|
9
|
+
clearSubmission(field);
|
10
|
+
if (field.meta?.previousValue?.value === value || isEmpty(value)) {
|
11
|
+
return null;
|
12
|
+
}
|
13
|
+
field.meta.submission.newValue = {
|
14
|
+
identifier: value,
|
15
|
+
identifierType: field.questionOptions.identifierType,
|
16
|
+
uuid: field.meta.previousValue?.id,
|
17
|
+
location: context.location,
|
18
|
+
};
|
19
|
+
return field.meta.submission.newValue;
|
20
|
+
},
|
21
|
+
getInitialValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) {
|
22
|
+
const latestIdentifier = context.patient?.identifier?.find(
|
23
|
+
(identifier) => identifier.type?.coding[0]?.code === field.questionOptions.identifierType,
|
24
|
+
);
|
25
|
+
field.meta = { ...(field.meta || {}), previousValue: latestIdentifier };
|
26
|
+
return latestIdentifier?.value;
|
27
|
+
},
|
28
|
+
getPreviousValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) {
|
29
|
+
return null;
|
30
|
+
},
|
31
|
+
getDisplayValue: function (field: FormField, value: any) {
|
32
|
+
if (value?.display) {
|
33
|
+
return value.display;
|
34
|
+
}
|
35
|
+
return value;
|
36
|
+
},
|
37
|
+
tearDown: function (): void {
|
38
|
+
return;
|
39
|
+
},
|
40
|
+
};
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
2
|
+
import { type FormContextProps } from '../provider/form-provider';
|
3
|
+
import { type FormField, type FormProcessorContextProps, type FormFieldValueAdapter } from '../types';
|
4
|
+
import dayjs from 'dayjs';
|
5
|
+
import { clearSubmission } from '../utils/common-utils';
|
6
|
+
import { isEmpty } from '../validators/form-validator';
|
7
|
+
|
8
|
+
export const ProgramStateAdapter: FormFieldValueAdapter = {
|
9
|
+
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
|
10
|
+
clearSubmission(field);
|
11
|
+
if (field.meta?.previousValue?.uuid === value || isEmpty(value)) {
|
12
|
+
return null;
|
13
|
+
}
|
14
|
+
field.meta.submission.newValue = {
|
15
|
+
state: value,
|
16
|
+
startDate: dayjs().format(),
|
17
|
+
};
|
18
|
+
},
|
19
|
+
getInitialValue: function (
|
20
|
+
field: FormField,
|
21
|
+
sourceObject: OpenmrsResource,
|
22
|
+
context: FormProcessorContextProps,
|
23
|
+
): Promise<any> {
|
24
|
+
const program = context.customDependencies.patientPrograms?.find(
|
25
|
+
(program) => program.program.uuid === field.questionOptions.programUuid,
|
26
|
+
);
|
27
|
+
if (program?.states?.length > 0) {
|
28
|
+
const currentState = program.states
|
29
|
+
.filter((state) => !state.endDate)
|
30
|
+
.find((state) => state.state.programWorkflow?.uuid === field.questionOptions.workflowUuid)?.state;
|
31
|
+
field.meta = { ...(field.meta || {}), previousValue: currentState };
|
32
|
+
return currentState.uuid;
|
33
|
+
}
|
34
|
+
return null;
|
35
|
+
},
|
36
|
+
getPreviousValue: function (
|
37
|
+
field: FormField,
|
38
|
+
sourceObject: OpenmrsResource,
|
39
|
+
context: FormProcessorContextProps,
|
40
|
+
): Promise<any> {
|
41
|
+
return null;
|
42
|
+
},
|
43
|
+
getDisplayValue: function (field: FormField, value: any) {
|
44
|
+
if (value?.display) {
|
45
|
+
return value.display;
|
46
|
+
}
|
47
|
+
return value;
|
48
|
+
},
|
49
|
+
tearDown: function (): void {
|
50
|
+
return;
|
51
|
+
},
|
52
|
+
};
|
package/src/api/index.ts
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
import { fhirBaseUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
2
|
+
import { encounterRepresentation } from '../constants';
|
3
|
+
import { type OpenmrsForm, type PatientIdentifier, type PatientProgramPayload } from '../types';
|
4
|
+
import { isUuid } from '../utils/boolean-utils';
|
5
|
+
|
6
|
+
export function saveEncounter(abortController: AbortController, payload, encounterUuid?: string) {
|
7
|
+
const url = encounterUuid
|
8
|
+
? `${restBaseUrl}/encounter/${encounterUuid}?v=${encounterRepresentation}`
|
9
|
+
: `${restBaseUrl}/encounter?v=${encounterRepresentation}`;
|
10
|
+
|
11
|
+
return openmrsFetch(url, {
|
12
|
+
headers: {
|
13
|
+
'Content-Type': 'application/json',
|
14
|
+
},
|
15
|
+
method: 'POST',
|
16
|
+
body: payload,
|
17
|
+
signal: abortController.signal,
|
18
|
+
});
|
19
|
+
}
|
20
|
+
|
21
|
+
export function saveAttachment(patientUuid, field, conceptUuid, date, encounterUUID, abortController) {
|
22
|
+
const url = `${restBaseUrl}/attachment`;
|
23
|
+
|
24
|
+
const content = field.meta.submission?.newValue?.value;
|
25
|
+
const cameraUploadType = typeof content === 'string' && content?.split(';')[0].split(':')[1].split('/')[1];
|
26
|
+
|
27
|
+
const formData = new FormData();
|
28
|
+
const fileCaption = field.id;
|
29
|
+
|
30
|
+
formData.append('fileCaption', fileCaption);
|
31
|
+
formData.append('patient', patientUuid);
|
32
|
+
|
33
|
+
if (typeof content === 'object') {
|
34
|
+
formData.append('file', content);
|
35
|
+
} else {
|
36
|
+
formData.append('file', new File([''], `camera-upload.${cameraUploadType}`), `camera-upload.${cameraUploadType}`);
|
37
|
+
formData.append('base64Content', content);
|
38
|
+
}
|
39
|
+
formData.append('encounter', encounterUUID);
|
40
|
+
formData.append('obsDatetime', date);
|
41
|
+
|
42
|
+
return openmrsFetch(url, {
|
43
|
+
method: 'POST',
|
44
|
+
signal: abortController.signal,
|
45
|
+
body: formData,
|
46
|
+
});
|
47
|
+
}
|
48
|
+
|
49
|
+
export function getAttachmentByUuid(patientUuid: string, encounterUuid: string, abortController: AbortController) {
|
50
|
+
const attachmentUrl = `${restBaseUrl}/attachment`;
|
51
|
+
return openmrsFetch(`${attachmentUrl}?patient=${patientUuid}&encounter=${encounterUuid}`, {
|
52
|
+
signal: abortController.signal,
|
53
|
+
}).then((response) => response.data);
|
54
|
+
}
|
55
|
+
|
56
|
+
export function getConcept(conceptUuid: string, v: string) {
|
57
|
+
return openmrsFetch(`${restBaseUrl}/concept/${conceptUuid}?v=${v}`).then(({ data }) => data.results);
|
58
|
+
}
|
59
|
+
|
60
|
+
export function getLocationsByTag(tag: string) {
|
61
|
+
return openmrsFetch(`${restBaseUrl}/location?tag=${tag}&v=custom:(uuid,display)`).then(({ data }) => data.results);
|
62
|
+
}
|
63
|
+
|
64
|
+
export function getAllLocations() {
|
65
|
+
return openmrsFetch<{ results }>(`${restBaseUrl}/location?v=custom:(uuid,display)`).then(({ data }) => data.results);
|
66
|
+
}
|
67
|
+
|
68
|
+
export async function getPreviousEncounter(patientUuid: string, encounterType: string) {
|
69
|
+
const query = `patient=${patientUuid}&_sort=-date&_count=1&type=${encounterType}`;
|
70
|
+
let response = await openmrsFetch(`${fhirBaseUrl}/Encounter?${query}`);
|
71
|
+
if (response?.data?.entry?.length) {
|
72
|
+
const latestEncounter = response.data.entry[0].resource.id;
|
73
|
+
response = await openmrsFetch(`${restBaseUrl}/encounter/${latestEncounter}?v=${encounterRepresentation}`);
|
74
|
+
return response.data;
|
75
|
+
}
|
76
|
+
return null;
|
77
|
+
}
|
78
|
+
|
79
|
+
export function getLatestObs(patientUuid: string, conceptUuid: string, encounterTypeUuid?: string) {
|
80
|
+
let params = `patient=${patientUuid}&code=${conceptUuid}${
|
81
|
+
encounterTypeUuid ? `&encounter.type=${encounterTypeUuid}` : ''
|
82
|
+
}`;
|
83
|
+
// the latest obs
|
84
|
+
params += '&_sort=-date&_count=1';
|
85
|
+
return openmrsFetch(`${fhirBaseUrl}/Observation?${params}`).then(({ data }) => {
|
86
|
+
return data.entry?.length ? data.entry[0].resource : null;
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Fetches an OpenMRS form using either its name or UUID.
|
92
|
+
* @param {string} nameOrUUID - The form's name or UUID.
|
93
|
+
* @returns {Promise<OpenmrsForm | null>} - A Promise that resolves to the fetched OpenMRS form or null if not found.
|
94
|
+
*/
|
95
|
+
export async function fetchOpenMRSForm(nameOrUUID: string): Promise<OpenmrsForm | null> {
|
96
|
+
if (!nameOrUUID) {
|
97
|
+
return null;
|
98
|
+
}
|
99
|
+
|
100
|
+
const { url, isUUID } = isUuid(nameOrUUID)
|
101
|
+
? { url: `${restBaseUrl}/form/${nameOrUUID}?v=full`, isUUID: true }
|
102
|
+
: { url: `${restBaseUrl}/form?q=${nameOrUUID}&v=full`, isUUID: false };
|
103
|
+
|
104
|
+
const { data: openmrsFormResponse } = await openmrsFetch(url);
|
105
|
+
if (isUUID) {
|
106
|
+
return openmrsFormResponse;
|
107
|
+
}
|
108
|
+
|
109
|
+
return openmrsFormResponse.results?.length
|
110
|
+
? openmrsFormResponse.results.find((form) => form.retired === false)
|
111
|
+
: new Error(`Form with ${nameOrUUID} was not found`);
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Fetches ClobData for a given OpenMRS form.
|
116
|
+
* @param {OpenmrsForm} form - The OpenMRS form object.
|
117
|
+
* @returns {Promise<any | null>} - A Promise that resolves to the fetched ClobData or null if not found.
|
118
|
+
*/
|
119
|
+
export async function fetchClobData(form: OpenmrsForm): Promise<any | null> {
|
120
|
+
if (!form) {
|
121
|
+
return null;
|
122
|
+
}
|
123
|
+
|
124
|
+
const jsonSchemaResource = form.resources.find(({ name }) => name === 'JSON schema');
|
125
|
+
if (!jsonSchemaResource) {
|
126
|
+
return null;
|
127
|
+
}
|
128
|
+
|
129
|
+
const clobDataUrl = `${restBaseUrl}/clobdata/${jsonSchemaResource.valueReference}`;
|
130
|
+
const { data: clobDataResponse } = await openmrsFetch(clobDataUrl);
|
131
|
+
|
132
|
+
return clobDataResponse;
|
133
|
+
}
|
134
|
+
|
135
|
+
// Program Enrollment
|
136
|
+
export function getPatientEnrolledPrograms(patientUuid: string) {
|
137
|
+
return openmrsFetch(
|
138
|
+
`${restBaseUrl}/programenrollment?patient=${patientUuid}&v=custom:(uuid,display,program:(uuid,name,allWorkflows),dateEnrolled,dateCompleted,location:(uuid,display),states:(state:(uuid,name,concept:(uuid),programWorkflow:(uuid)))`,
|
139
|
+
).then(({ data }) => {
|
140
|
+
if (data) {
|
141
|
+
return data;
|
142
|
+
}
|
143
|
+
return null;
|
144
|
+
});
|
145
|
+
}
|
146
|
+
|
147
|
+
export function saveProgramEnrollment(payload: PatientProgramPayload, abortController: AbortController) {
|
148
|
+
if (!payload) {
|
149
|
+
throw new Error('Program enrollment cannot be created because no payload is supplied');
|
150
|
+
}
|
151
|
+
const url = payload.uuid ? `${restBaseUrl}/programenrollment/${payload.uuid}` : `${restBaseUrl}/programenrollment`;
|
152
|
+
return openmrsFetch(url, {
|
153
|
+
method: 'POST',
|
154
|
+
headers: {
|
155
|
+
'Content-Type': 'application/json',
|
156
|
+
},
|
157
|
+
body: JSON.stringify(payload),
|
158
|
+
signal: abortController.signal,
|
159
|
+
});
|
160
|
+
}
|
161
|
+
|
162
|
+
export function savePatientIdentifier(patientIdentifier: PatientIdentifier, patientUuid: string) {
|
163
|
+
let url: string;
|
164
|
+
|
165
|
+
if (patientIdentifier.uuid) {
|
166
|
+
url = `${restBaseUrl}/patient/${patientUuid}/identifier/${patientIdentifier.uuid}`;
|
167
|
+
} else {
|
168
|
+
url = `${restBaseUrl}/patient/${patientUuid}/identifier`;
|
169
|
+
}
|
170
|
+
|
171
|
+
return openmrsFetch(url, {
|
172
|
+
headers: {
|
173
|
+
'Content-Type': 'application/json',
|
174
|
+
},
|
175
|
+
method: 'POST',
|
176
|
+
body: JSON.stringify(patientIdentifier),
|
177
|
+
});
|
178
|
+
}
|