@openmrs/esm-form-engine-lib 3.0.1-pre.1676 → 3.0.1-pre.1681
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/__mocks__/forms/rfe-forms/diagnosis-test-form.json +60 -0
- package/__mocks__/forms/rfe-forms/mockHistoricalvisitsEncounter.json +40 -0
- package/dist/openmrs-esm-form-engine-lib.js +1 -1
- package/package.json +1 -1
- package/src/adapters/encounter-diagnosis-adapter.test.ts +232 -0
- package/src/adapters/encounter-diagnosis-adapter.ts +116 -0
- package/src/components/repeat/repeat.component.tsx +1 -0
- package/src/constants.ts +1 -0
- package/src/form-engine.test.tsx +170 -3
- package/src/processors/encounter/encounter-form-processor.ts +13 -3
- package/src/processors/encounter/encounter-processor-helper.ts +41 -0
- package/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +5 -0
- package/src/transformers/default-schema-transformer.ts +32 -0
- package/src/types/domain.ts +32 -0
- package/src/types/schema.ts +6 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
{
|
2
|
+
"name": "Diagnosis test",
|
3
|
+
"pages": [
|
4
|
+
{
|
5
|
+
"label": "1",
|
6
|
+
"sections": [
|
7
|
+
{
|
8
|
+
"label": "Select diagnosis",
|
9
|
+
"isExpanded": "true",
|
10
|
+
"questions": [
|
11
|
+
{
|
12
|
+
"label": "Test Diagnosis 1",
|
13
|
+
"id": "diagnosis1",
|
14
|
+
"type": "diagnosis",
|
15
|
+
"questionOptions": {
|
16
|
+
"rendering": "repeating",
|
17
|
+
"dataSource": "diagnoses",
|
18
|
+
"isSearchable": "true",
|
19
|
+
"diagnosis": {
|
20
|
+
"isConfirmed": "true",
|
21
|
+
"rank": 1,
|
22
|
+
"conceptClasses": [
|
23
|
+
"8d4918b0-c2cc-11de-8d13-0010c6dffd0f",
|
24
|
+
"8d492954-c2cc-11de-8d13-0010c6dffd0f",
|
25
|
+
"8d492b2a-c2cc-11de-8d13-0010c6dffd0f"
|
26
|
+
]
|
27
|
+
}
|
28
|
+
}
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"label": "Test Diagnosis 2",
|
32
|
+
"id": "diagnosis2",
|
33
|
+
"type": "diagnosis",
|
34
|
+
"questionOptions": {
|
35
|
+
"rendering": "repeating",
|
36
|
+
"dataSource": "diagnoses",
|
37
|
+
"isSearchable": "false",
|
38
|
+
"diagnosis": {
|
39
|
+
"isConfirmed": "true",
|
40
|
+
"rank": 1,
|
41
|
+
"conceptClasses": [
|
42
|
+
"8d4918b0-c2cc-11de-8d13-0010c6dffd0f",
|
43
|
+
"8d492954-c2cc-11de-8d13-0010c6dffd0f",
|
44
|
+
"8d492b2a-c2cc-11de-8d13-0010c6dffd0f"
|
45
|
+
]
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
]
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
],
|
54
|
+
"processor": "EncounterFormProcessor",
|
55
|
+
"encounterType": "e22e39fd-7db2-45e7-80f1-60fa0d5a4378",
|
56
|
+
"referencedForms": [],
|
57
|
+
"uuid": "29ce67d1-892b-45be-84ce-8e36aa9ca37f",
|
58
|
+
"description": "re",
|
59
|
+
"version": "1.0"
|
60
|
+
}
|
@@ -85,5 +85,45 @@
|
|
85
85
|
]
|
86
86
|
}
|
87
87
|
}
|
88
|
+
],
|
89
|
+
"diagnoses": [
|
90
|
+
{
|
91
|
+
"uuid": "95690fb4-0398-42d9-9ffc-8a134e6d829d",
|
92
|
+
"diagnosis": {
|
93
|
+
"coded": {
|
94
|
+
"uuid": "stage-1-uuid",
|
95
|
+
"display": "stage 1"
|
96
|
+
}
|
97
|
+
},
|
98
|
+
"condition": null,
|
99
|
+
"encounter": "d2edc41a-5abf-48f8-bb62-ca5507dd2e4c",
|
100
|
+
"certainty": "CONFIRMED",
|
101
|
+
"rank": 1,
|
102
|
+
"voided": false,
|
103
|
+
"display": "stage 1",
|
104
|
+
"patient": "d8b38f21-db01-4ded-93bf-6a9470206461",
|
105
|
+
"formFieldNamespace": "rfe-forms",
|
106
|
+
"formFieldPath": "rfe-forms-diagnosis1",
|
107
|
+
"resourceVersion": "1.8"
|
108
|
+
},
|
109
|
+
{
|
110
|
+
"uuid": "95690fb3-0398-42d9-9ffc-8a134e6d829d",
|
111
|
+
"diagnosis": {
|
112
|
+
"coded": {
|
113
|
+
"uuid": "stage-2-uuid",
|
114
|
+
"display": "stage 2"
|
115
|
+
}
|
116
|
+
},
|
117
|
+
"condition": null,
|
118
|
+
"encounter": "d2edc41a-5abf-48f8-bb62-ca5507dd2e4c",
|
119
|
+
"certainty": "CONFIRMED",
|
120
|
+
"rank": 1,
|
121
|
+
"voided": false,
|
122
|
+
"display": "stage 2",
|
123
|
+
"patient": "d8b38f21-db01-4ded-93bf-6a9470206461",
|
124
|
+
"formFieldNamespace": "rfe-forms",
|
125
|
+
"formFieldPath": "rfe-forms-diagnosis2",
|
126
|
+
"resourceVersion": "1.8"
|
127
|
+
}
|
88
128
|
]
|
89
129
|
}
|
@@ -1 +1 @@
|
|
1
|
-
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b,y,w,_={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(277),t.e(260),t.e(72),t.e(465),t.e(299),t.e(
|
1
|
+
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b,y,w,_={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(277),t.e(260),t.e(72),t.e(465),t.e(299),t.e(524)]).then((()=>()=>t(524)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},P={};function S(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,loaded:!1,exports:{}};return _[e].call(t.exports,t,t.exports,S),t.loaded=!0,t.exports}S.m=_,S.c=P,S.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return S.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,S.t=function(t,n){if(1&n&&(t=this(t)),8&n)return t;if("object"==typeof t&&t){if(4&n&&t.__esModule)return t;if(16&n&&"function"==typeof t.then)return t}var o=Object.create(null);S.r(o);var i={};e=e||[null,r({}),r([]),r(r)];for(var a=2&n&&t;"object"==typeof a&&!~e.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>t[e]));return i.default=()=>t,S.d(o,i),o},S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce(((r,t)=>(S.f[t](e,r),r)),[])),S.u=e=>e+".js",S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",S.l=(e,r,o,i)=>{if(t[e])t[e].push(r);else{var a,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var f=s[u];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+o){a=f;break}}a||(l=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,S.nc&&a.setAttribute("nonce",S.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var p=(r,n)=>{a.onerror=a.onload=null,clearTimeout(d);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},d=setTimeout(p.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=p.bind(null,a.onerror),a.onload=p.bind(null,a.onload),l&&document.head.appendChild(a)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},S.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{S.S={};var e={},r={};S.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];S.o(S.S,t)||(S.S[t]={});var i=S.S[t],a="@openmrs/esm-form-engine-lib",l=(e,r,t,n)=>{var o=i[e]=i[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:a>l.from))&&(o[r]={get:t,from:a,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","6.0.3-pre.2587",(()=>Promise.all([S.e(151),S.e(72),S.e(766)]).then((()=>()=>S(5151))))),l("@openmrs/esm-patient-common-lib","9.0.1-pre.6367",(()=>Promise.all([S.e(727),S.e(277),S.e(72),S.e(465),S.e(299),S.e(499)]).then((()=>()=>S(7727))))),l("dayjs","1.11.13",(()=>S.e(353).then((()=>()=>S(4353))))),l("i18next","23.16.0",(()=>S.e(635).then((()=>()=>S(2635))))),l("react-i18next","11.18.6",(()=>Promise.all([S.e(979),S.e(72)]).then((()=>()=>S(2979))))),l("react","18.3.1",(()=>S.e(540).then((()=>()=>S(6540))))),l("swr/_internal","2.2.5",(()=>Promise.all([S.e(993),S.e(72)]).then((()=>()=>S(4993))))),l("swr/immutable","2.2.5",(()=>Promise.all([S.e(72),S.e(465),S.e(225)]).then((()=>()=>S(4225))))),l("swr/infinite","2.2.5",(()=>Promise.all([S.e(72),S.e(465),S.e(41)]).then((()=>()=>S(3041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;S.g.importScripts&&(e=S.g.location+"");var r=S.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),S.p=e})(),o=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},i=(e,r)=>{e=o(e),r=o(r);for(var t=0;;){if(t>=e.length)return t<r.length&&"u"!=(typeof r[t])[0];var n=e[t],i=(typeof n)[0];if(t>=r.length)return"u"==i;var a=r[t],l=(typeof a)[0];if(i!=l)return"o"==i&&"n"==l||"s"==l||"u"==i;if("o"!=i&&"u"!=i&&n!=a)return n<a;t++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():a(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},l=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var i=0,a=1,s=!0;;a++,i++){var u,f,p=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==p?a>t&&!n:""==p!=n);if("u"==f){if(!s||"u"!=p)return!1}else if(s)if(p==f)if(a<=t){if(u!=e[a])return!1}else{if(n?u>e[a]:u<e[a])return!1;u!=e[a]&&(s=!1)}else if("s"!=p&&"n"!=p){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<p!=n)return!1;s=!1}else"s"!=p&&"n"!=p&&(s=!1,a--)}}var d=[],c=d.pop.bind(d);for(i=1;i<e.length;i++){var h=e[i];d.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>e&&S.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),p=(e,r,t)=>{var n=t?f(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&i(e,r)?r:e),0)},d=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",c=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,i){var a=S.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,S.S[r],t,!1,o,i)):e(r,S.S[r],t,n,o,i)})(((e,r,t,n,o,i)=>{if(!s(r,t))return m(e,t,i);var a=p(r,t,n);return l(o,a)||h(d(r,t,a,o)),u(r[t][a])})),g={},b={6072:()=>v("default","react",!1,[1,18],(()=>S.e(540).then((()=>()=>S(6540))))),6766:()=>v("default","i18next",!1,[1,23],(()=>S.e(635).then((()=>()=>S(2635))))),8465:()=>v("default","swr/_internal",!1,[1,2],(()=>S.e(993).then((()=>()=>S(4993))))),2731:()=>v("default","@openmrs/esm-patient-common-lib",!1,[1,9],(()=>S.e(727).then((()=>()=>S(7727))))),3941:()=>v("default","react-i18next",!1,[1,11],(()=>S.e(979).then((()=>()=>S(2979))))),9197:()=>v("default","@openmrs/esm-framework",!1,[1,6],(()=>Promise.all([S.e(151),S.e(766)]).then((()=>()=>S(5151))))),4209:()=>v("default","swr/immutable",!1,[1,2],(()=>S.e(606).then((()=>()=>S(4225))))),231:()=>v("default","dayjs",!1,[1,1],(()=>S.e(353).then((()=>()=>S(4353))))),6339:()=>v("default","swr/infinite",!1,[1,2],(()=>S.e(422).then((()=>()=>S(3041)))))},y={72:[6072],299:[2731,3941,9197],465:[8465],499:[4209],524:[231,4209,6339],766:[6766]},w={},S.f.consumes=(e,r)=>{S.o(y,e)&&y[e].forEach((e=>{if(S.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,S.m[e]=t=>{delete S.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],S.m[e]=t=>{throw delete S.c[e],r}};try{var o=b[e]();o.then?r.push(g[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};S.f.j=(r,t)=>{var n=S.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(299|465|72|766)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=S.p+S.u(r),a=new Error;S.l(i,(t=>{if(S.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,l]=t,s=0;if(i.some((r=>0!==e[r]))){for(n in a)S.o(a,n)&&(S.m[n]=a[n]);l&&l(S)}for(r&&r(t);s<i.length;s++)o=i[s],S.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_form_engine_lib=globalThis.webpackChunk_openmrs_esm_form_engine_lib||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),S.nc=void 0;var j=S(8008);_openmrs_esm_form_engine_lib=j})();
|
package/package.json
CHANGED
@@ -0,0 +1,232 @@
|
|
1
|
+
import { type FormContextProps } from '../provider/form-provider';
|
2
|
+
import { type FormField } from '../types';
|
3
|
+
import { EncounterDiagnosisAdapter } from './encounter-diagnosis-adapter';
|
4
|
+
|
5
|
+
const formContext = {
|
6
|
+
methods: null,
|
7
|
+
workspaceLayout: 'maximized',
|
8
|
+
isSubmitting: false,
|
9
|
+
patient: {
|
10
|
+
id: '833db896-c1f0-11eb-8529-0242ac130003',
|
11
|
+
},
|
12
|
+
formJson: null,
|
13
|
+
visit: null,
|
14
|
+
sessionMode: 'enter',
|
15
|
+
sessionDate: new Date(),
|
16
|
+
location: {
|
17
|
+
uuid: '41e6e516-c1f0-11eb-8529-0242ac130003',
|
18
|
+
},
|
19
|
+
currentProvider: null,
|
20
|
+
layoutType: 'small-desktop',
|
21
|
+
domainObjectValue: {
|
22
|
+
uuid: '873455da-3ec4-453c-b565-7c1fe35426be',
|
23
|
+
obs: [],
|
24
|
+
diagnoses: [],
|
25
|
+
},
|
26
|
+
previousDomainObjectValue: null,
|
27
|
+
processor: null,
|
28
|
+
formFields: [],
|
29
|
+
formFieldAdapters: null,
|
30
|
+
formFieldValidators: null,
|
31
|
+
customDependencies: {
|
32
|
+
patientPrograms: [],
|
33
|
+
},
|
34
|
+
deletedFields: [],
|
35
|
+
getFormField: jest.fn(),
|
36
|
+
addFormField: jest.fn(),
|
37
|
+
updateFormField: jest.fn(),
|
38
|
+
removeFormField: () => {},
|
39
|
+
addInvalidField: jest.fn(),
|
40
|
+
removeInvalidField: jest.fn(),
|
41
|
+
setInvalidFields: jest.fn(),
|
42
|
+
setForm: jest.fn(),
|
43
|
+
setDeletedFields: jest.fn(),
|
44
|
+
} as FormContextProps;
|
45
|
+
|
46
|
+
const field = {
|
47
|
+
label: 'Test Diagnosis',
|
48
|
+
id: 'DiagNosIS',
|
49
|
+
type: 'diagnosis',
|
50
|
+
questionOptions: {
|
51
|
+
rendering: 'repeating',
|
52
|
+
diagnosis: {
|
53
|
+
rank: 1,
|
54
|
+
isConfirmed: false,
|
55
|
+
},
|
56
|
+
datasource: {
|
57
|
+
name: 'problem_datasource',
|
58
|
+
config: {
|
59
|
+
class: [
|
60
|
+
'8d4918b0-c2cc-11de-8d13-0010c6dffd0f',
|
61
|
+
'8d492954-c2cc-11de-8d13-0010c6dffd0f',
|
62
|
+
'8d492b2a-c2cc-11de-8d13-0010c6dffd0f',
|
63
|
+
],
|
64
|
+
},
|
65
|
+
},
|
66
|
+
},
|
67
|
+
meta: {
|
68
|
+
submission: {
|
69
|
+
newValue: null,
|
70
|
+
},
|
71
|
+
previousValue: null,
|
72
|
+
},
|
73
|
+
validators: [
|
74
|
+
{
|
75
|
+
type: 'form_field',
|
76
|
+
},
|
77
|
+
{
|
78
|
+
type: 'default_value',
|
79
|
+
},
|
80
|
+
],
|
81
|
+
isHidden: false,
|
82
|
+
isRequired: false,
|
83
|
+
isDisabled: false,
|
84
|
+
} as FormField;
|
85
|
+
|
86
|
+
const diagnoses = [
|
87
|
+
{
|
88
|
+
uuid: '8d975f9e-e9e6-452f-be7c-0e87c047f056',
|
89
|
+
diagnosis: {
|
90
|
+
coded: {
|
91
|
+
uuid: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
92
|
+
display: 'Schistosoma Mansonii Infection',
|
93
|
+
links: [],
|
94
|
+
},
|
95
|
+
},
|
96
|
+
condition: null,
|
97
|
+
encounter: {
|
98
|
+
uuid: '9a4b06bd-d655-414f-b9ce-69e940c337ce',
|
99
|
+
},
|
100
|
+
certainty: 'CONFIRMED',
|
101
|
+
rank: 1,
|
102
|
+
voided: false,
|
103
|
+
display: 'Schistosoma Mansonii Infection',
|
104
|
+
patient: {
|
105
|
+
uuid: '00affa97-0010-417c-87f5-de48362de915',
|
106
|
+
display: '1000VKV - Bett Tett',
|
107
|
+
},
|
108
|
+
formFieldNamespace: 'rfe-forms',
|
109
|
+
formFieldPath: 'rfe-forms-DiagNosIS_1',
|
110
|
+
links: [],
|
111
|
+
resourceVersion: '1.8',
|
112
|
+
},
|
113
|
+
{
|
114
|
+
uuid: 'b2d0e95b-d2f6-49d1-a477-acc7026edbd7',
|
115
|
+
diagnosis: {
|
116
|
+
coded: {
|
117
|
+
uuid: '137329AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
118
|
+
display: 'Infection due to Entamoeba Histolytica',
|
119
|
+
links: [],
|
120
|
+
},
|
121
|
+
},
|
122
|
+
condition: null,
|
123
|
+
encounter: {
|
124
|
+
uuid: '9a4b06bd-d655-414f-b9ce-69e940c337ce',
|
125
|
+
},
|
126
|
+
certainty: 'PROVISIONAL',
|
127
|
+
rank: 1,
|
128
|
+
voided: false,
|
129
|
+
display: 'Infection due to Entamoeba Histolytica',
|
130
|
+
patient: {
|
131
|
+
uuid: '00affa97-0010-417c-87f5-de48362de915',
|
132
|
+
display: '1000VKV - Bett Tett',
|
133
|
+
},
|
134
|
+
formFieldNamespace: 'rfe-forms',
|
135
|
+
formFieldPath: 'rfe-forms-DiagNosIS',
|
136
|
+
links: [],
|
137
|
+
resourceVersion: '1.8',
|
138
|
+
},
|
139
|
+
];
|
140
|
+
|
141
|
+
describe('EncounterDiagnosisAdapter', () => {
|
142
|
+
it('should should handle submission of a diagnosis field', async () => {
|
143
|
+
const value = '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
144
|
+
EncounterDiagnosisAdapter.transformFieldValue(field, value, formContext);
|
145
|
+
expect(field.meta.submission.newValue).toEqual({
|
146
|
+
patient: '833db896-c1f0-11eb-8529-0242ac130003',
|
147
|
+
condition: null,
|
148
|
+
diagnosis: {
|
149
|
+
coded: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
150
|
+
},
|
151
|
+
certainty: 'PROVISIONAL',
|
152
|
+
rank: 1,
|
153
|
+
formFieldPath: 'rfe-forms-DiagNosIS',
|
154
|
+
formFieldNamespace: 'rfe-forms',
|
155
|
+
});
|
156
|
+
});
|
157
|
+
|
158
|
+
it('should get initial value for the diagnosis', async () => {
|
159
|
+
formContext.domainObjectValue.diagnoses.push(...diagnoses);
|
160
|
+
const diagnosis = await EncounterDiagnosisAdapter.getInitialValue(field, null, formContext);
|
161
|
+
expect(diagnosis).toEqual('137329AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
162
|
+
});
|
163
|
+
|
164
|
+
it('should return null for getPreviousValue', async () => {
|
165
|
+
const previousValue = await EncounterDiagnosisAdapter.getPreviousValue(field, null, formContext);
|
166
|
+
expect(previousValue).toBeNull();
|
167
|
+
});
|
168
|
+
|
169
|
+
it('should execute tearDown without issues', () => {
|
170
|
+
expect(() => EncounterDiagnosisAdapter.tearDown()).not.toThrow();
|
171
|
+
});
|
172
|
+
|
173
|
+
it('should edit a diagnosis value', () => {
|
174
|
+
formContext.sessionMode = 'edit';
|
175
|
+
|
176
|
+
const value = '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
177
|
+
field.meta = {
|
178
|
+
initialValue: {
|
179
|
+
omrsObject: {
|
180
|
+
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
|
181
|
+
diagnosis: {
|
182
|
+
coded: {
|
183
|
+
uuid: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
184
|
+
display: 'Schistosoma Mansonii Infection',
|
185
|
+
},
|
186
|
+
},
|
187
|
+
},
|
188
|
+
refinedValue: null,
|
189
|
+
},
|
190
|
+
};
|
191
|
+
|
192
|
+
EncounterDiagnosisAdapter.transformFieldValue(field, value, formContext);
|
193
|
+
expect(field.meta.submission.newValue).toEqual({
|
194
|
+
patient: '833db896-c1f0-11eb-8529-0242ac130003',
|
195
|
+
condition: null,
|
196
|
+
diagnosis: {
|
197
|
+
coded: '128138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
198
|
+
},
|
199
|
+
certainty: 'PROVISIONAL',
|
200
|
+
rank: 1,
|
201
|
+
formFieldPath: 'rfe-forms-DiagNosIS',
|
202
|
+
formFieldNamespace: 'rfe-forms',
|
203
|
+
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
|
204
|
+
});
|
205
|
+
expect(field.meta.submission.voidedValue).toBe(undefined);
|
206
|
+
});
|
207
|
+
|
208
|
+
it('should void removed diagnosis in edit mode', () => {
|
209
|
+
formContext.sessionMode = 'edit';
|
210
|
+
|
211
|
+
field.meta = {
|
212
|
+
initialValue: {
|
213
|
+
omrsObject: {
|
214
|
+
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
|
215
|
+
diagnosis: {
|
216
|
+
coded: {
|
217
|
+
uuid: '127133AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
218
|
+
display: 'Schistosoma Mansonii Infection',
|
219
|
+
},
|
220
|
+
},
|
221
|
+
},
|
222
|
+
refinedValue: null,
|
223
|
+
},
|
224
|
+
};
|
225
|
+
|
226
|
+
EncounterDiagnosisAdapter.transformFieldValue(field, null, formContext);
|
227
|
+
expect(field.meta.submission.voidedValue).toEqual({
|
228
|
+
voided: true,
|
229
|
+
uuid: '0e20bb67-5d7f-41e0-96a1-751efc21a96f',
|
230
|
+
});
|
231
|
+
});
|
232
|
+
});
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
2
|
+
import { type OpenmrsObs, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types';
|
3
|
+
import { type FormContextProps } from '../provider/form-provider';
|
4
|
+
import { type OpenmrsEncounter, type FormField } from '../types';
|
5
|
+
import { gracefullySetSubmission } from '../utils/common-utils';
|
6
|
+
import { isEmpty } from '../validators/form-validator';
|
7
|
+
import { isTrue } from '../utils/boolean-utils';
|
8
|
+
|
9
|
+
export let assignedDiagnosesIds: string[] = [];
|
10
|
+
|
11
|
+
export const EncounterDiagnosisAdapter: FormFieldValueAdapter = {
|
12
|
+
transformFieldValue: function (field: FormField, value: any, context: FormContextProps) {
|
13
|
+
if (field.meta.initialValue?.omrsObject && isEmpty(value)) {
|
14
|
+
return gracefullySetSubmission(field, undefined, voidDiagnosis(field.meta.initialValue.omrsObject as OpenmrsObs));
|
15
|
+
}
|
16
|
+
if (!isEmpty(value)) {
|
17
|
+
const previousDiagnosis = field.meta.initialValue?.omrsObject as OpenmrsResource;
|
18
|
+
if (hasPreviousDiagnosisValueChanged(previousDiagnosis, value)) {
|
19
|
+
return gracefullySetSubmission(
|
20
|
+
field,
|
21
|
+
editDiagnosis(value, field, previousDiagnosis, context.patient.id),
|
22
|
+
undefined,
|
23
|
+
);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
const newValue = constructNewDiagnosis(value, field, context.patient.id);
|
27
|
+
gracefullySetSubmission(field, newValue, null);
|
28
|
+
return newValue;
|
29
|
+
},
|
30
|
+
getInitialValue: function (
|
31
|
+
field: FormField,
|
32
|
+
sourceObject: OpenmrsResource,
|
33
|
+
context: FormProcessorContextProps,
|
34
|
+
): Promise<any> {
|
35
|
+
const encounter = sourceObject ?? (context.domainObjectValue as OpenmrsEncounter);
|
36
|
+
const matchedDiagnosis = encounter.diagnoses.find(
|
37
|
+
(diagnosis) => diagnosis.formFieldPath === `rfe-forms-${field.id}`,
|
38
|
+
);
|
39
|
+
|
40
|
+
if (matchedDiagnosis) {
|
41
|
+
field.meta = {
|
42
|
+
...(field.meta || {}),
|
43
|
+
initialValue: {
|
44
|
+
omrsObject: matchedDiagnosis,
|
45
|
+
refinedValue: matchedDiagnosis.diagnosis?.coded.uuid,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
if (!assignedDiagnosesIds.includes(matchedDiagnosis.diagnosis?.coded?.uuid)) {
|
49
|
+
assignedDiagnosesIds.push(matchedDiagnosis.diagnosis?.coded?.uuid);
|
50
|
+
}
|
51
|
+
return matchedDiagnosis.diagnosis?.coded.uuid;
|
52
|
+
}
|
53
|
+
return null;
|
54
|
+
},
|
55
|
+
getPreviousValue: function (
|
56
|
+
field: FormField,
|
57
|
+
sourceObject: OpenmrsResource,
|
58
|
+
context: FormProcessorContextProps,
|
59
|
+
): Promise<any> {
|
60
|
+
return null;
|
61
|
+
},
|
62
|
+
getDisplayValue: (field: FormField, value: any) => {
|
63
|
+
return field.questionOptions.answers?.find((option) => option.concept == value)?.label || value;
|
64
|
+
},
|
65
|
+
tearDown: function (): void {
|
66
|
+
assignedDiagnosesIds = [];
|
67
|
+
},
|
68
|
+
};
|
69
|
+
|
70
|
+
const constructNewDiagnosis = (value: string, field: FormField, patientUuid: string) => {
|
71
|
+
if (!value) {
|
72
|
+
return null;
|
73
|
+
}
|
74
|
+
return {
|
75
|
+
patient: patientUuid,
|
76
|
+
condition: null,
|
77
|
+
diagnosis: {
|
78
|
+
coded: value,
|
79
|
+
},
|
80
|
+
certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL',
|
81
|
+
rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary
|
82
|
+
formFieldPath: `rfe-forms-${field.id}`,
|
83
|
+
formFieldNamespace: 'rfe-forms',
|
84
|
+
};
|
85
|
+
};
|
86
|
+
|
87
|
+
function editDiagnosis(
|
88
|
+
newEncounterDiagnosis: string,
|
89
|
+
field: FormField,
|
90
|
+
previousDiagnosis: OpenmrsResource,
|
91
|
+
patientUuid: string,
|
92
|
+
) {
|
93
|
+
return {
|
94
|
+
patient: patientUuid,
|
95
|
+
condition: null,
|
96
|
+
diagnosis: {
|
97
|
+
coded: newEncounterDiagnosis,
|
98
|
+
},
|
99
|
+
certainty: isTrue(field.questionOptions?.diagnosis?.isConfirmed) ? 'CONFIRMED' : 'PROVISIONAL',
|
100
|
+
rank: field.questionOptions.diagnosis?.rank ?? 1, // rank 1 denotes a diagnosis is primary, else secondary
|
101
|
+
formFieldPath: `rfe-forms-${field.id}`,
|
102
|
+
formFieldNamespace: 'rfe-forms',
|
103
|
+
uuid: previousDiagnosis.uuid,
|
104
|
+
};
|
105
|
+
}
|
106
|
+
|
107
|
+
export function hasPreviousDiagnosisValueChanged(previousDiagnosis: OpenmrsResource, newValue: string) {
|
108
|
+
if (isEmpty(previousDiagnosis)) {
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
return previousDiagnosis.value !== newValue;
|
112
|
+
}
|
113
|
+
|
114
|
+
export function voidDiagnosis(diagnosis: OpenmrsResource) {
|
115
|
+
return { uuid: diagnosis.uuid, voided: true };
|
116
|
+
}
|
@@ -15,6 +15,7 @@ import { useFormFactory } from '../../provider/form-factory-provider';
|
|
15
15
|
const renderingByTypeMap: Record<string, RenderType> = {
|
16
16
|
obsGroup: 'group',
|
17
17
|
testOrder: 'select',
|
18
|
+
diagnosis: 'ui-select-extended',
|
18
19
|
};
|
19
20
|
|
20
21
|
const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
|
package/src/constants.ts
CHANGED
@@ -5,6 +5,7 @@ export const encounterRepresentation =
|
|
5
5
|
'custom:(uuid,encounterDatetime,encounterType:(uuid,name,description),location:(uuid,name),' +
|
6
6
|
'patient:(uuid,display),encounterProviders:(uuid,provider:(uuid,name),encounterRole:(uuid,name)),' +
|
7
7
|
'orders:(uuid,display,concept:(uuid,display),voided),' +
|
8
|
+
'diagnoses:(uuid,certainty,condition,formFieldPath,formFieldNamespace,display,rank,voided,diagnosis:(coded:(uuid,display))),' +
|
8
9
|
'obs:(uuid,obsDatetime,comment,voided,groupMembers,formFieldNamespace,formFieldPath,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' +
|
9
10
|
'names:(uuid,conceptNameType,name))))';
|
10
11
|
export const FormsStore = 'forms-engine-store';
|
package/src/form-engine.test.tsx
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import dayjs from 'dayjs';
|
3
3
|
import userEvent from '@testing-library/user-event';
|
4
|
-
import { act, cleanup, render, screen, within } from '@testing-library/react';
|
4
|
+
import { act, cleanup, render, screen, waitFor, within } from '@testing-library/react';
|
5
5
|
import {
|
6
6
|
ExtensionSlot,
|
7
7
|
OpenmrsDatePicker,
|
@@ -44,9 +44,11 @@ import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-for
|
|
44
44
|
import readOnlyValidationForm from '__mocks__/forms/rfe-forms/read-only-validation-form.json';
|
45
45
|
import jsExpressionValidationForm from '__mocks__/forms/rfe-forms/js-expression-validation-form.json';
|
46
46
|
import hidePagesAndSectionsForm from '__mocks__/forms/rfe-forms/hide-pages-and-sections-form.json';
|
47
|
+
import diagnosisForm from '__mocks__/forms/rfe-forms/diagnosis-test-form.json';
|
47
48
|
|
48
49
|
import FormEngine from './form-engine.component';
|
49
|
-
import { type SessionMode } from './types';
|
50
|
+
import { type FormSchema, type OpenmrsEncounter, type SessionMode } from './types';
|
51
|
+
import { useEncounter } from './hooks/useEncounter';
|
50
52
|
|
51
53
|
const patientUUID = '8673ee4f-e2ab-4077-ba55-4980f408773e';
|
52
54
|
const visit = mockVisit;
|
@@ -59,6 +61,7 @@ const mockExtensionSlot = jest.mocked(ExtensionSlot);
|
|
59
61
|
const mockUsePatient = jest.mocked(usePatient);
|
60
62
|
const mockUseSession = jest.mocked(useSession);
|
61
63
|
const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker);
|
64
|
+
const mockUseEncounter = jest.mocked(useEncounter);
|
62
65
|
|
63
66
|
mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange, isInvalid, invalidText }) => {
|
64
67
|
return (
|
@@ -79,6 +82,48 @@ mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange, isIn
|
|
79
82
|
when(mockOpenmrsFetch).calledWith(formsResourcePath).mockReturnValue({ data: demoHtsOpenmrsForm });
|
80
83
|
when(mockOpenmrsFetch).calledWith(clobDataResourcePath).mockReturnValue({ data: demoHtsForm });
|
81
84
|
|
85
|
+
jest.mock('lodash-es/debounce', () => jest.fn((fn) => fn));
|
86
|
+
|
87
|
+
jest.mock('lodash-es', () => ({
|
88
|
+
...jest.requireActual('lodash-es'),
|
89
|
+
debounce: jest.fn((fn) => fn),
|
90
|
+
}));
|
91
|
+
|
92
|
+
jest.mock('./registry/registry', () => {
|
93
|
+
const originalModule = jest.requireActual('./registry/registry');
|
94
|
+
return {
|
95
|
+
...originalModule,
|
96
|
+
getRegisteredDataSource: jest.fn().mockResolvedValue({
|
97
|
+
fetchData: jest.fn().mockImplementation((...args) => {
|
98
|
+
if (args[1].class?.length && !args[1].referencedValue?.key) {
|
99
|
+
// concept DS
|
100
|
+
return Promise.resolve([
|
101
|
+
{
|
102
|
+
uuid: 'stage-1-uuid',
|
103
|
+
display: 'stage 1',
|
104
|
+
},
|
105
|
+
{
|
106
|
+
uuid: 'stage-2-uuid',
|
107
|
+
display: 'stage 2',
|
108
|
+
},
|
109
|
+
{
|
110
|
+
uuid: 'stage-3-uuid',
|
111
|
+
display: 'stage 3',
|
112
|
+
},
|
113
|
+
]);
|
114
|
+
}
|
115
|
+
}),
|
116
|
+
fetchSingleItem: jest.fn().mockImplementation((uuid: string) => {
|
117
|
+
return Promise.resolve({
|
118
|
+
uuid,
|
119
|
+
display: 'stage 1',
|
120
|
+
});
|
121
|
+
}),
|
122
|
+
toUuidAndDisplay: (data) => data,
|
123
|
+
}),
|
124
|
+
};
|
125
|
+
});
|
126
|
+
|
82
127
|
jest.mock('../src/api', () => {
|
83
128
|
const originalModule = jest.requireActual('../src/api');
|
84
129
|
|
@@ -117,6 +162,16 @@ jest.mock('./hooks/useConcepts', () => ({
|
|
117
162
|
}),
|
118
163
|
}));
|
119
164
|
|
165
|
+
jest.mock('./hooks/useEncounter', () => ({
|
166
|
+
useEncounter: jest.fn().mockImplementation((formJson: FormSchema) => {
|
167
|
+
return {
|
168
|
+
encounter: formJson.encounter ? (mockHxpEncounter as OpenmrsEncounter) : null,
|
169
|
+
isLoading: false,
|
170
|
+
error: undefined,
|
171
|
+
};
|
172
|
+
}),
|
173
|
+
}));
|
174
|
+
|
120
175
|
describe('Form engine component', () => {
|
121
176
|
const user = userEvent.setup();
|
122
177
|
|
@@ -1047,7 +1102,118 @@ describe('Form engine component', () => {
|
|
1047
1102
|
});
|
1048
1103
|
});
|
1049
1104
|
|
1050
|
-
|
1105
|
+
describe('Encounter diagnosis', () => {
|
1106
|
+
it('should test addition of a diagnosis', async () => {
|
1107
|
+
await act(async () => {
|
1108
|
+
renderForm(null, diagnosisForm);
|
1109
|
+
});
|
1110
|
+
|
1111
|
+
const testDiagnosis1AddButton = screen.getAllByRole('button', { name: 'Add' })[0];
|
1112
|
+
await user.click(testDiagnosis1AddButton);
|
1113
|
+
|
1114
|
+
await waitFor(() => {
|
1115
|
+
expect(screen.getAllByRole('combobox', { name: /^test diagnosis 1$/i }).length).toEqual(2);
|
1116
|
+
});
|
1117
|
+
|
1118
|
+
expect(screen.getByRole('button', { name: /Remove/i })).toBeInTheDocument();
|
1119
|
+
});
|
1120
|
+
|
1121
|
+
it('should render all diagnosis fields', async () => {
|
1122
|
+
await act(async () => {
|
1123
|
+
renderForm(null, diagnosisForm);
|
1124
|
+
});
|
1125
|
+
const diagnosisFields = screen.getAllByRole('combobox', { name: /test diagnosis 1|test diagnosis 2/i });
|
1126
|
+
expect(diagnosisFields.length).toBe(2);
|
1127
|
+
});
|
1128
|
+
|
1129
|
+
it('should be possible to delete cloned fields', async () => {
|
1130
|
+
await act(async () => {
|
1131
|
+
renderForm(null, diagnosisForm);
|
1132
|
+
});
|
1133
|
+
|
1134
|
+
const testDiagnosis1AddButton = screen.getAllByRole('button', { name: 'Add' })[0];
|
1135
|
+
await user.click(testDiagnosis1AddButton);
|
1136
|
+
|
1137
|
+
await waitFor(() => {
|
1138
|
+
expect(screen.getAllByRole('combobox', { name: /^test diagnosis 1$/i }).length).toEqual(2);
|
1139
|
+
});
|
1140
|
+
const removeButton = screen.getByRole('button', { name: /Remove/i });
|
1141
|
+
|
1142
|
+
await user.click(removeButton);
|
1143
|
+
|
1144
|
+
expect(removeButton).not.toBeInTheDocument();
|
1145
|
+
});
|
1146
|
+
|
1147
|
+
it('should save diagnosis field on form submission', async () => {
|
1148
|
+
await act(async () => {
|
1149
|
+
renderForm(null, diagnosisForm);
|
1150
|
+
});
|
1151
|
+
|
1152
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
1153
|
+
const combobox = await findSelectInput(screen, 'Test Diagnosis 1');
|
1154
|
+
expect(combobox).toHaveAttribute('placeholder', 'Search...');
|
1155
|
+
|
1156
|
+
await user.click(combobox);
|
1157
|
+
await user.type(combobox, 'stage');
|
1158
|
+
|
1159
|
+
expect(screen.getByText(/stage 1/)).toBeInTheDocument();
|
1160
|
+
expect(screen.getByText(/stage 2/)).toBeInTheDocument();
|
1161
|
+
expect(screen.getByText(/stage 3/)).toBeInTheDocument();
|
1162
|
+
|
1163
|
+
await user.click(screen.getByText('stage 1'));
|
1164
|
+
await user.click(screen.getByRole('button', { name: /save/i }));
|
1165
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
1166
|
+
const [_, encounter] = saveEncounterMock.mock.calls[0];
|
1167
|
+
expect(encounter.diagnoses.length).toBe(1);
|
1168
|
+
expect(encounter.diagnoses[0]).toEqual({
|
1169
|
+
patient: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
1170
|
+
condition: null,
|
1171
|
+
diagnosis: {
|
1172
|
+
coded: 'stage-1-uuid',
|
1173
|
+
},
|
1174
|
+
certainty: 'CONFIRMED',
|
1175
|
+
rank: 1,
|
1176
|
+
formFieldPath: `rfe-forms-diagnosis1`,
|
1177
|
+
formFieldNamespace: 'rfe-forms',
|
1178
|
+
});
|
1179
|
+
});
|
1180
|
+
|
1181
|
+
it('should edit diagnosis field on form submission', async () => {
|
1182
|
+
await act(async () => {
|
1183
|
+
renderForm(null, diagnosisForm, null, 'edit', mockHxpEncounter.uuid);
|
1184
|
+
});
|
1185
|
+
mockUseEncounter.mockImplementation(() => ({ encounter: mockHxpEncounter, error: null, isLoading: false }));
|
1186
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
1187
|
+
|
1188
|
+
const field1 = await findSelectInput(screen, 'Test Diagnosis 1');
|
1189
|
+
expect(field1).toHaveValue('stage 1');
|
1190
|
+
|
1191
|
+
await user.click(field1);
|
1192
|
+
await user.type(field1, 'stage');
|
1193
|
+
expect(screen.getByText(/stage 1/)).toBeInTheDocument();
|
1194
|
+
expect(screen.getByText(/stage 2/)).toBeInTheDocument();
|
1195
|
+
expect(screen.getByText(/stage 3/)).toBeInTheDocument();
|
1196
|
+
await user.click(screen.getByText(/stage 3/));
|
1197
|
+
await user.click(screen.getByRole('button', { name: /save/i }));
|
1198
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
1199
|
+
const [_, encounter] = saveEncounterMock.mock.calls[0];
|
1200
|
+
expect(encounter.diagnoses.length).toBe(1);
|
1201
|
+
expect(encounter.diagnoses[0]).toEqual({
|
1202
|
+
patient: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
1203
|
+
condition: null,
|
1204
|
+
diagnosis: {
|
1205
|
+
coded: 'stage-3-uuid',
|
1206
|
+
},
|
1207
|
+
certainty: 'CONFIRMED',
|
1208
|
+
rank: 1,
|
1209
|
+
formFieldPath: `rfe-forms-diagnosis1`,
|
1210
|
+
formFieldNamespace: 'rfe-forms',
|
1211
|
+
uuid: '95690fb4-0398-42d9-9ffc-8a134e6d829d',
|
1212
|
+
});
|
1213
|
+
});
|
1214
|
+
});
|
1215
|
+
|
1216
|
+
function renderForm(formUUID, formJson, intent?: string, mode?: SessionMode, encounterUUID?: string) {
|
1051
1217
|
render(
|
1052
1218
|
<FormEngine
|
1053
1219
|
formJson={formJson}
|
@@ -1055,6 +1221,7 @@ describe('Form engine component', () => {
|
|
1055
1221
|
patientUUID={patientUUID}
|
1056
1222
|
formSessionIntent={intent}
|
1057
1223
|
visit={visit}
|
1224
|
+
encounterUUID={encounterUUID}
|
1058
1225
|
mode={mode ? mode : 'enter'}
|
1059
1226
|
/>,
|
1060
1227
|
);
|
@@ -162,11 +162,21 @@ export class EncounterFormProcessor extends FormProcessor {
|
|
162
162
|
// save encounter
|
163
163
|
try {
|
164
164
|
const { data: savedEncounter } = await saveEncounter(abortController, encounter, encounter.uuid);
|
165
|
-
const
|
166
|
-
|
165
|
+
const savedOrders = savedEncounter.orders.map((order) => order.orderNumber);
|
166
|
+
const savedDiagnoses = savedEncounter.diagnoses.map((diagnosis) => diagnosis.display);
|
167
|
+
if (savedOrders.length) {
|
167
168
|
showSnackbar({
|
168
169
|
title: translateFn('ordersSaved', 'Order(s) saved successfully'),
|
169
|
-
subtitle:
|
170
|
+
subtitle: savedOrders.join(', '),
|
171
|
+
kind: 'success',
|
172
|
+
isLowContrast: true,
|
173
|
+
});
|
174
|
+
}
|
175
|
+
// handle diagnoses
|
176
|
+
if (savedDiagnoses.length) {
|
177
|
+
showSnackbar({
|
178
|
+
title: translateFn('diagnosisSaved', 'Diagnosis(es) saved successfully'),
|
179
|
+
subtitle: savedDiagnoses.join(', '),
|
170
180
|
kind: 'success',
|
171
181
|
isLowContrast: true,
|
172
182
|
});
|
@@ -17,6 +17,7 @@ import { DefaultValueValidator } from '../../validators/default-value-validator'
|
|
17
17
|
import { cloneRepeatField } from '../../components/repeat/helpers';
|
18
18
|
import { assignedOrderIds } from '../../adapters/orders-adapter';
|
19
19
|
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
20
|
+
import { assignedDiagnosesIds } from '../../adapters/encounter-diagnosis-adapter';
|
20
21
|
|
21
22
|
export function prepareEncounter(
|
22
23
|
context: FormContextProps,
|
@@ -30,6 +31,8 @@ export function prepareEncounter(
|
|
30
31
|
const obsForSubmission = [];
|
31
32
|
prepareObs(obsForSubmission, allFormFields);
|
32
33
|
const ordersForSubmission = prepareOrders(allFormFields);
|
34
|
+
const diagnosesForSubmission = prepareDiagnosis(allFormFields);
|
35
|
+
|
33
36
|
let encounterForSubmission: OpenmrsEncounter = {};
|
34
37
|
|
35
38
|
if (encounter) {
|
@@ -59,6 +62,7 @@ export function prepareEncounter(
|
|
59
62
|
}
|
60
63
|
encounterForSubmission.obs = obsForSubmission;
|
61
64
|
encounterForSubmission.orders = ordersForSubmission;
|
65
|
+
encounterForSubmission.diagnoses = diagnosesForSubmission;
|
62
66
|
} else {
|
63
67
|
encounterForSubmission = {
|
64
68
|
patient: patient.id,
|
@@ -77,6 +81,7 @@ export function prepareEncounter(
|
|
77
81
|
},
|
78
82
|
visit: visit?.uuid,
|
79
83
|
orders: ordersForSubmission,
|
84
|
+
diagnoses: diagnosesForSubmission,
|
80
85
|
};
|
81
86
|
}
|
82
87
|
return encounterForSubmission;
|
@@ -314,6 +319,33 @@ export async function hydrateRepeatField(
|
|
314
319
|
}),
|
315
320
|
);
|
316
321
|
}
|
322
|
+
|
323
|
+
const unMappedDiagnoses = encounter.diagnoses.filter((diagnosis) => {
|
324
|
+
return (
|
325
|
+
!diagnosis.voided &&
|
326
|
+
!assignedDiagnosesIds.includes(diagnosis?.diagnosis?.coded.uuid) &&
|
327
|
+
diagnosis.formFieldPath.startsWith(`rfe-forms-${field.id}_`)
|
328
|
+
);
|
329
|
+
});
|
330
|
+
|
331
|
+
if (field.type === 'diagnosis') {
|
332
|
+
return Promise.all(
|
333
|
+
unMappedDiagnoses.map(async (diagnosis) => {
|
334
|
+
const idSuffix = parseInt(diagnosis.formFieldPath.split('_')[1]);
|
335
|
+
const clone = cloneRepeatField(field, diagnosis, idSuffix);
|
336
|
+
initialValues[clone.id] = await formFieldAdapters[field.type].getInitialValue(
|
337
|
+
clone,
|
338
|
+
{ diagnoses: [diagnosis] } as any,
|
339
|
+
context,
|
340
|
+
);
|
341
|
+
if (!assignedDiagnosesIds.includes(diagnosis.diagnosis.coded.uuid)) {
|
342
|
+
assignedDiagnosesIds.push(diagnosis.diagnosis.coded.uuid);
|
343
|
+
}
|
344
|
+
|
345
|
+
return clone;
|
346
|
+
}),
|
347
|
+
);
|
348
|
+
}
|
317
349
|
// handle obs groups
|
318
350
|
return Promise.all(
|
319
351
|
unMappedGroups.map(async (group) => {
|
@@ -332,3 +364,12 @@ export async function hydrateRepeatField(
|
|
332
364
|
}),
|
333
365
|
).then((results) => results.flat());
|
334
366
|
}
|
367
|
+
|
368
|
+
function prepareDiagnosis(fields: FormField[]) {
|
369
|
+
const diagnoses = fields
|
370
|
+
.filter((field) => field.type === 'diagnosis' && hasSubmission(field))
|
371
|
+
.map((field) => field.meta.submission.newValue || field.meta.submission.voidedValue)
|
372
|
+
.filter((o) => o);
|
373
|
+
|
374
|
+
return diagnoses;
|
375
|
+
}
|
@@ -10,6 +10,7 @@ import { ObsCommentAdapter } from '../../adapters/obs-comment-adapter';
|
|
10
10
|
import { OrdersAdapter } from '../../adapters/orders-adapter';
|
11
11
|
import { PatientIdentifierAdapter } from '../../adapters/patient-identifier-adapter';
|
12
12
|
import { ProgramStateAdapter } from '../../adapters/program-state-adapter';
|
13
|
+
import { EncounterDiagnosisAdapter } from '../../adapters/encounter-diagnosis-adapter';
|
13
14
|
import { type FormFieldValueAdapter } from '../../types';
|
14
15
|
|
15
16
|
export const inbuiltFieldValueAdapters: RegistryItem<FormFieldValueAdapter>[] = [
|
@@ -61,4 +62,8 @@ export const inbuiltFieldValueAdapters: RegistryItem<FormFieldValueAdapter>[] =
|
|
61
62
|
type: 'patientIdentifier',
|
62
63
|
component: PatientIdentifierAdapter,
|
63
64
|
},
|
65
|
+
{
|
66
|
+
type: 'diagnosis',
|
67
|
+
component: EncounterDiagnosisAdapter,
|
68
|
+
},
|
64
69
|
];
|
@@ -148,6 +148,9 @@ function transformByType(question: FormField) {
|
|
148
148
|
? 'date'
|
149
149
|
: question.questionOptions.rendering;
|
150
150
|
break;
|
151
|
+
case 'diagnosis':
|
152
|
+
handleDiagnosis(question);
|
153
|
+
break;
|
151
154
|
}
|
152
155
|
}
|
153
156
|
|
@@ -276,3 +279,32 @@ function handleQuestionsWithObsComments(sectionQuestions: Array<FormField>): Arr
|
|
276
279
|
|
277
280
|
return augmentedQuestions;
|
278
281
|
}
|
282
|
+
|
283
|
+
function handleDiagnosis(question: FormField) {
|
284
|
+
if (
|
285
|
+
('dataSource' in question.questionOptions && question.questionOptions['dataSource'] === 'diagnoses') ||
|
286
|
+
question.type === 'diagnosis'
|
287
|
+
) {
|
288
|
+
question.questionOptions.datasource = {
|
289
|
+
name: 'problem_datasource',
|
290
|
+
config: {
|
291
|
+
class: question.questionOptions.diagnosis?.conceptClasses,
|
292
|
+
},
|
293
|
+
};
|
294
|
+
if (question.questionOptions.diagnosis?.conceptSet) {
|
295
|
+
question.questionOptions = {
|
296
|
+
...question.questionOptions,
|
297
|
+
concept: question.questionOptions.diagnosis?.conceptSet,
|
298
|
+
datasource: {
|
299
|
+
name: 'problem_datasource',
|
300
|
+
config: {
|
301
|
+
useSetMembersByConcept: true,
|
302
|
+
},
|
303
|
+
},
|
304
|
+
};
|
305
|
+
}
|
306
|
+
question.questionOptions.isSearchable = true;
|
307
|
+
|
308
|
+
delete question.questionOptions['dataSource'];
|
309
|
+
}
|
310
|
+
}
|
package/src/types/domain.ts
CHANGED
@@ -12,6 +12,7 @@ export interface OpenmrsEncounter {
|
|
12
12
|
visit?: OpenmrsResource | string;
|
13
13
|
encounterProviders?: Array<Record<string, any>>;
|
14
14
|
form?: OpenmrsFormResource;
|
15
|
+
diagnoses?: Array<Diagnosis>;
|
15
16
|
}
|
16
17
|
|
17
18
|
export interface OpenmrsObs extends OpenmrsResource {
|
@@ -191,3 +192,34 @@ export interface PatientIdentifier {
|
|
191
192
|
location?: string;
|
192
193
|
preferred?: boolean;
|
193
194
|
}
|
195
|
+
|
196
|
+
export interface DiagnosisPayload {
|
197
|
+
patient: string;
|
198
|
+
condition: null;
|
199
|
+
diagnosis: {
|
200
|
+
coded: string;
|
201
|
+
};
|
202
|
+
certainty: string;
|
203
|
+
rank: number;
|
204
|
+
formFieldNamespace?: string;
|
205
|
+
formFieldPath?: string;
|
206
|
+
uuid?: string;
|
207
|
+
encounter?: string;
|
208
|
+
}
|
209
|
+
|
210
|
+
export interface Diagnosis {
|
211
|
+
encounter: string;
|
212
|
+
patient: string;
|
213
|
+
diagnosis: {
|
214
|
+
coded: {
|
215
|
+
uuid: string;
|
216
|
+
};
|
217
|
+
};
|
218
|
+
certainty: string;
|
219
|
+
rank: number;
|
220
|
+
display: string;
|
221
|
+
voided: boolean;
|
222
|
+
uuid: string;
|
223
|
+
formFieldNamespace?: string;
|
224
|
+
formFieldPath?: string;
|
225
|
+
}
|
package/src/types/schema.ts
CHANGED
@@ -192,6 +192,12 @@ export interface FormQuestionOptions {
|
|
192
192
|
comment?: string;
|
193
193
|
orientation?: 'vertical' | 'horizontal';
|
194
194
|
shownCommentOptions?: { validators?: Array<Record<string, any>>; hide?: { hideWhenExpression: string } };
|
195
|
+
diagnosis?: {
|
196
|
+
rank?: number;
|
197
|
+
isConfirmed?: boolean;
|
198
|
+
conceptClasses?: Array<string>;
|
199
|
+
conceptSet?: string;
|
200
|
+
};
|
195
201
|
}
|
196
202
|
|
197
203
|
export interface QuestionAnswerOption {
|