diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/bpe/v1/service/QuestionnaireResponseHelperImpl.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/bpe/v1/service/QuestionnaireResponseHelperImpl.java index 64773dc77..7f6b4fb14 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/bpe/v1/service/QuestionnaireResponseHelperImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/bpe/v1/service/QuestionnaireResponseHelperImpl.java @@ -5,10 +5,12 @@ import java.util.stream.Stream; import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; @@ -90,6 +92,12 @@ public Type transformQuestionTypeToAnswerType(Questionnaire.QuestionnaireItemCom case DATETIME -> new DateTimeType("1900-01-01T00:00:00.000Z"); case URL -> new UriType("http://example.org/foo"); case REFERENCE -> new Reference("http://example.org/fhir/Placeholder/id"); + case CHOICE -> new Coding().setSystem("http://example.org/fhir/CodeSystem/name").setCode("code"); + case QUANTITY -> new Quantity().setValue(0).setUnit("unit") + .setSystem("http://example.org/fhir/CodeSystem/name").setCode("code"); + // TODO: False positive validation error for QuestionnaireResponse.item.answer.valueQuantity.comparator, + // add comparator to Quantity as soon as https://github.com/hapifhir/org.hl7.fhir.core/issues/2224 is fixed + // .setComparator(Quantity.QuantityComparator.LESS_OR_EQUAL); default -> throw new RuntimeException("Type '" + question.getType().getDisplay() + "' in Questionnaire.item is not supported as answer type"); diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/test/java/dev/dsf/bpe/v1/service/QuestionnaireResponseTest.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/test/java/dev/dsf/bpe/v1/service/QuestionnaireResponseTest.java index ce8bcf89f..110e503a4 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/test/java/dev/dsf/bpe/v1/service/QuestionnaireResponseTest.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/test/java/dev/dsf/bpe/v1/service/QuestionnaireResponseTest.java @@ -6,10 +6,12 @@ import java.util.List; import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; @@ -175,7 +177,7 @@ public void testQuestionTypeReferenceToAnswerType() assertTrue(type instanceof Reference); } - @Test(expected = RuntimeException.class) + @Test public void testQuestionTypeChoiceToAnswerType() { QuestionnaireResponseHelper qrh = new QuestionnaireResponseHelperImpl("https://foo/fhir"); @@ -183,7 +185,8 @@ public void testQuestionTypeChoiceToAnswerType() Questionnaire.QuestionnaireItemComponent question = new Questionnaire.QuestionnaireItemComponent() .setType(Questionnaire.QuestionnaireItemType.CHOICE); - qrh.transformQuestionTypeToAnswerType(question); + Type type = qrh.transformQuestionTypeToAnswerType(question); + assertTrue(type instanceof Coding); } @Test(expected = RuntimeException.class) @@ -197,7 +200,7 @@ public void testQuestionTypeOpenChoiceToAnswerType() qrh.transformQuestionTypeToAnswerType(question); } - @Test(expected = RuntimeException.class) + @Test public void testQuestionTypeQuantityToAnswerType() { QuestionnaireResponseHelper qrh = new QuestionnaireResponseHelperImpl("https://foo/fhir"); @@ -205,7 +208,8 @@ public void testQuestionTypeQuantityToAnswerType() Questionnaire.QuestionnaireItemComponent question = new Questionnaire.QuestionnaireItemComponent() .setType(Questionnaire.QuestionnaireItemType.QUANTITY); - qrh.transformQuestionTypeToAnswerType(question); + Type type = qrh.transformQuestionTypeToAnswerType(question); + assertTrue(type instanceof Quantity); } @Test(expected = RuntimeException.class) diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/QuestionnaireResponseHelperImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/QuestionnaireResponseHelperImpl.java index 7247732ab..3a7b5bc61 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/QuestionnaireResponseHelperImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/QuestionnaireResponseHelperImpl.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; @@ -95,6 +96,12 @@ public Type transformQuestionTypeToAnswerType(Questionnaire.QuestionnaireItemCom case DATETIME -> new DateTimeType("1900-01-01T00:00:00.000Z"); case URL -> new UriType("http://example.org/foo"); case REFERENCE -> new Reference("http://example.org/fhir/Placeholder/id"); + case CHOICE -> new Coding().setSystem("http://example.org/fhir/CodeSystem/name").setCode("code"); + case QUANTITY -> new Quantity().setValue(0).setUnit("unit") + .setSystem("http://example.org/fhir/CodeSystem/name").setCode("code"); + // TODO: False positive validation error for QuestionnaireResponse.item.answer.valueQuantity.comparator, + // add comparator to Quantity as soon as https://github.com/hapifhir/org.hl7.fhir.core/issues/2224 is fixed + // .setComparator(Quantity.QuantityComparator.LESS_OR_EQUAL); default -> throw new RuntimeException("Type '" + question.getType().getDisplay() + "' in Questionnaire.item is not supported as answer type"); diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java index b1e7239a9..abb7eee33 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; @@ -192,6 +193,11 @@ else if ("identifiers".equals(type)) .setValue("External_Test_Organization"))); case "boolean-example" -> set(item, new BooleanType(true)); + + case "choice-example" -> + set(item, new Coding().setSystem("http://example.org/fhir/CodeSystem/name").setCode("code")); + + case "quantity-example" -> set(item, new Quantity().setValue(0).setUnit("unit")); } }); diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java index 1adeb1bd5..a0478c66e 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java @@ -15,6 +15,7 @@ import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DecimalType; @@ -22,6 +23,7 @@ import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; @@ -125,6 +127,11 @@ public void checkQuestionnaireResponse(ProcessPluginApi api) throws Exception .setValue("External_Test_Organization"))); case "boolean-example" -> test(item, new BooleanType(true)); + + case "choice-example" -> + test(item, new Coding().setSystem("http://example.org/fhir/CodeSystem/name").setCode("code")); + + case "quantity-example" -> test(item, new Quantity().setValue(0).setUnit("unit")); } }); @@ -159,6 +166,21 @@ private void test(QuestionnaireResponseItemComponent item, Type expected) expectTrue(r.getIdentifier().hasValue()); expectSame(((Reference) expected).getIdentifier().getSystem(), r.getIdentifier().getSystem()); expectSame(((Reference) expected).getIdentifier().getValue(), r.getIdentifier().getValue()); + + } + + case Coding c -> { + expectTrue(c.hasSystem()); + expectSame(((Coding) expected).getSystem(), c.getSystem()); + expectTrue(c.hasCode()); + expectSame(((Coding) expected).getCode(), c.getCode()); + } + + case Quantity q -> { + expectTrue(q.hasValue()); + expectSame(((Quantity) expected).getValue(), q.getValue()); + expectTrue(q.hasUnit()); + expectSame(((Quantity) expected).getUnit(), q.getUnit()); } default -> diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/fhir/Questionnaire/test.xml b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/fhir/Questionnaire/test.xml index 073c694af..68027dcde 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/fhir/Questionnaire/test.xml +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/fhir/Questionnaire/test.xml @@ -91,4 +91,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ElementQuantityValue.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ElementQuantityValue.java new file mode 100644 index 000000000..097af7e10 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ElementQuantityValue.java @@ -0,0 +1,57 @@ +package dev.dsf.fhir.adapter; + +import java.math.BigDecimal; + +import org.hl7.fhir.r4.model.Quantity; + +public final class ElementQuantityValue +{ + public static ElementQuantityValue from(R element) + { + return new ElementQuantityValue(element.hasSystem() ? element.getSystem() : null, + element.hasCode() ? element.getCode() : null, element.hasUnit() ? element.getUnit() : null, + element.hasValue() ? element.getValue() : null, + element.hasComparator() ? element.getComparator() : null); + } + + private final String system; + private final String code; + private final String unit; + private final BigDecimal value; + private final Quantity.QuantityComparator comparator; + + private ElementQuantityValue(String system, String code, String unit, BigDecimal value, + Quantity.QuantityComparator comparator) + { + this.system = system; + this.code = code; + this.unit = unit; + this.value = value; + this.comparator = comparator; + } + + public String getSystem() + { + return system; + } + + public String getCode() + { + return code; + } + + public String getUnit() + { + return unit; + } + + public BigDecimal getValue() + { + return value; + } + + public String getComparator() + { + return comparator != null ? comparator.toCode() : null; + } +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceQuestionnaireResponse.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceQuestionnaireResponse.java index 32f5ccbe2..039540e44 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceQuestionnaireResponse.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceQuestionnaireResponse.java @@ -10,6 +10,7 @@ import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; @@ -39,7 +40,7 @@ private record Element(String questionnaire, String businessKey, String userTask } private record Item(boolean show, String id, String type, String label, String fhirType, String stringValue, - ElementSystemValue systemValueValue, Boolean booleanValue) + ElementSystemValue systemValueValue, Boolean booleanValue, ElementQuantityValue quantityValue) { } @@ -103,7 +104,7 @@ private Item toItem(QuestionnaireResponseItemComponent i) if (i.hasAnswer() && i.getAnswer().size() == 1) return toItem(show, linkId, text, i.getAnswerFirstRep().getValue()); else - return new Item(show, linkId, null, text, null, null, null, null); + return new Item(show, linkId, null, text, null, null, null, null, null); } private Item toItem(boolean show, String id, String label, Type typedValue) @@ -113,39 +114,42 @@ private Item toItem(boolean show, String id, String label, Type typedValue) return switch (typedValue) { case BooleanType b -> - new Item(show, id, "boolean", label, fhirType, null, null, b.hasValue() ? b.getValue() : null); + new Item(show, id, "boolean", label, fhirType, null, null, b.hasValue() ? b.getValue() : null, null); case DecimalType d -> new Item(show, id, "number", label, fhirType, - d.hasValue() ? String.valueOf(d.getValue()) : null, null, null); + d.hasValue() ? String.valueOf(d.getValue()) : null, null, null, null); case IntegerType i -> new Item(show, id, "number", label, fhirType, - i.hasValue() ? String.valueOf(i.getValue()) : null, null, null); + i.hasValue() ? String.valueOf(i.getValue()) : null, null, null, null); case DateType d -> new Item(show, id, "date", label, fhirType, - d.hasValue() ? format(d.getValue(), DATE_FORMAT) : null, null, null); + d.hasValue() ? format(d.getValue(), DATE_FORMAT) : null, null, null, null); case DateTimeType dt -> new Item(show, id, "datetime-local", label, fhirType, - dt.hasValue() ? format(dt.getValue(), DATE_TIME_FORMAT) : null, null, null); + dt.hasValue() ? format(dt.getValue(), DATE_TIME_FORMAT) : null, null, null, null); case TimeType t -> - new Item(show, id, "time", label, fhirType, t.hasValue() ? t.getValue() : null, null, null); + new Item(show, id, "time", label, fhirType, t.hasValue() ? t.getValue() : null, null, null, null); case StringType s -> - new Item(show, id, "text", label, fhirType, s.hasValue() ? s.getValue() : null, null, null); + new Item(show, id, "text", label, fhirType, s.hasValue() ? s.getValue() : null, null, null, null); case UriType u -> - new Item(show, id, "url", label, fhirType, u.hasValue() ? u.getValue() : null, null, null); + new Item(show, id, "url", label, fhirType, u.hasValue() ? u.getValue() : null, null, null, null); - case Coding c -> new Item(show, id, "coding", label, fhirType, null, ElementSystemValue.from(c), null); + case Coding c -> + new Item(show, id, "coding", label, fhirType, null, ElementSystemValue.from(c), null, null); case Reference r when r.hasReferenceElement() -> new Item(show, id, "url", label, fhirType + ".reference", - r.getReferenceElement().hasValue() ? r.getReferenceElement().getValue() : null, null, null); + r.getReferenceElement().hasValue() ? r.getReferenceElement().getValue() : null, null, null, null); case Reference r when r.hasIdentifier() -> new Item(show, id, "identifier", label, fhirType + ".identifier", - null, ElementSystemValue.from(r.getIdentifier()), null); + null, ElementSystemValue.from(r.getIdentifier()), null, null); + + case Quantity q -> + new Item(show, id, "quantity", label, fhirType, null, null, null, ElementQuantityValue.from(q)); // TODO case Attachment a -> - // TODO case Quantity q -> default -> { logger.warn("Element of type {}, not supported", fhirType); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceTask.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceTask.java index 97e24429f..20ec92075 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceTask.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ResourceTask.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Task; @@ -56,17 +57,19 @@ private record Element(String process, String messageName, String businessKey, S } private record InputItem(String id, String type, String label, String labelTitle, String fhirType, - String stringValue, ElementSystemValue systemValueValue, Boolean booleanValue) + String stringValue, ElementSystemValue systemValueValue, Boolean booleanValue, + ElementQuantityValue quantityValue) { } private record OutputItem(String id, String type, String label, String labelTitle, String stringValue, - ElementSystemValue systemValueValue, Boolean booleanValue, List extension) + ElementSystemValue systemValueValue, Boolean booleanValue, ElementQuantityValue quantityValue, + List extension) { } private record ExtensionItem(String id, String type, String url, String stringValue, - ElementSystemValue systemValueValue, Boolean booleanValue) + ElementSystemValue systemValueValue, Boolean booleanValue, ElementQuantityValue quantityValue) { } @@ -177,11 +180,13 @@ private InputItem toItem(String id, String label, String labelTitle, Type typedV String stringValue = getStringValue(typedValue); ElementSystemValue systemValueValue = getSystemValueValue(typedValue); Boolean booleanValue = getBooleanValue(typedValue); + ElementQuantityValue quantityValue = getQuantityValue(typedValue); - if (stringValue == null && systemValueValue == null && booleanValue == null) - logger.warn("Output parameter with {} value, not supported", fhirType); + if (stringValue == null && systemValueValue == null && booleanValue == null && quantityValue == null) + logger.warn("Input parameter with {} value, not supported", fhirType); - return new InputItem(id, type, label, labelTitle, fhirType, stringValue, systemValueValue, booleanValue); + return new InputItem(id, type, label, labelTitle, fhirType, stringValue, systemValueValue, booleanValue, + quantityValue); } private OutputItem toOutputItem(TaskOutputComponent o) @@ -208,13 +213,14 @@ private OutputItem toOutputItem(String label, String labelTitle, Type typedValue String stringValue = getStringValue(typedValue); ElementSystemValue systemValueValue = getSystemValueValue(typedValue); Boolean booleanValue = getBooleanValue(typedValue); + ElementQuantityValue quantityValue = getQuantityValue(typedValue); - if (stringValue == null && systemValueValue == null && booleanValue == null) + if (stringValue == null && systemValueValue == null && booleanValue == null && quantityValue == null) logger.warn("Output parameter with {} value, not supported", typedValue.getClass().getAnnotation(DatatypeDef.class).name()); return new OutputItem(UUID.randomUUID().toString(), type, label, labelTitle, stringValue, systemValueValue, - booleanValue, extension); + booleanValue, quantityValue, extension); } private String getHtmlInputType(Type typedValue) @@ -232,6 +238,7 @@ private String getHtmlInputType(Type typedValue) case UriType _ -> "url"; case Coding _ -> "coding"; case Identifier _ -> "identifier"; + case Quantity _ -> "quantity"; case Reference r when r.hasReferenceElement() -> "url"; case Reference r when r.hasIdentifier() -> "identifier"; @@ -294,6 +301,14 @@ private Boolean getBooleanValue(Type typedValue) return null; } + private ElementQuantityValue getQuantityValue(Type typedValue) + { + if (typedValue instanceof Quantity q) + return ElementQuantityValue.from(q); + else + return null; + } + private List toExtensionItems(List extensions) { List items = new ArrayList<>(); @@ -310,10 +325,11 @@ private void addExtensionItem(String baseUrl, Extension extension, List addExtensionItem(url, e, items)); diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/dsf.css index ea9a6a51c..5403cdb3e 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/dsf.css @@ -1158,31 +1158,21 @@ div.row-text { background: var(--color-row-border-blue); } -.flex-element { +.flex-container { display: flex; flex-direction: row; } -.flex-element-100 { - width: 100%; - margin-right: 0em; -} - -.flex-element-67 { - width: 67%; -} - -.flex-element-50 { - width: 50%; - margin-right: 0em; +.flex-child { + flex: 1; } -.flex-element-33 { - width: 33%; +.flex-child-margin-right { + margin-right: 0.5em } -.flex-element-margin { - margin-right: 0.7em; +.comparator { + flex: 0; } .authorization { diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.css index ab99ca6fa..8672e9bff 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.css @@ -223,7 +223,7 @@ input[type=number] { display: none; } -input.identifier-coding-code { +.identifier-coding-code { margin-top: 6px; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.js b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.js index 73d023846..e30f407cb 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/static/form.js @@ -66,7 +66,6 @@ function readAndValidateTaskInput(input, row) { else return newTaskInputTyped(input.type, id, "value" + inputFhirType.charAt(0).toUpperCase() + inputFhirType.slice(1), htmlInputs[0].value, optional) } - else if (htmlInputs?.length === 2) { const input0FhirType = htmlInputs[0].getAttribute("fhir-type") const input1FhirType = htmlInputs[1].getAttribute("fhir-type") @@ -80,6 +79,13 @@ function readAndValidateTaskInput(input, row) { else if ("boolean.true" === input0FhirType && "boolean.false" == input1FhirType) return newTaskInputBoolean(input.type, id, htmlInputs[0].checked, htmlInputs[1].checked, optional) } + else if (htmlInputs?.length === 5) { + const input0FhirType = htmlInputs[0].getAttribute("fhir-type") + + if (input0FhirType.startsWith("Quantity")) { + return new newTaskInputQuantity(input.type, id, htmlInputs[0].value, htmlInputs[1].value, htmlInputs[2].value, htmlInputs[3].value, htmlInputs[4].value, optional) + } + } return { input: null, valid: false } } @@ -193,6 +199,27 @@ function newTaskInputBoolean(type, id, checkedTrue, checkedFalse, optional) { return { input: null, valid: optional } } +function newTaskInputQuantity(type, id, comparator, value, unit, system, code, optional) { + const result = validateQuantity(id, comparator, value, unit, system, code, optional, "Input") + + if (result.valid && result.value !== null) { + return { + input: { + type: type, + valueQuantity: { + comparator: result.value.comparator, + value: result.value.value, + unit: result.value.unit, + system: result.value.system, + code: result.value.code + } + }, + valid: true + } + } else + return { input: null, valid: result.valid } +} + function completeQuestionnaireResponse() { const questionnaireResponse = readQuestionnaireResponseAnswersFromForm() @@ -265,6 +292,16 @@ function readAndValidateQuestionnaireResponseItem(item, id) { else if ("boolean.true" === input0FhirType && "boolean.false" == input1FhirType) return newQuestionnaireResponseItemBoolean(item.text, id, htmlInputs[0].checked, htmlInputs[1].checked, optional) } + // TODO: False positive validation error for QuestionnaireResponse.item.answer.valueQuantity.comparator, + // adapt to === 5, remove "" and add htmlInputs[4].value before optional as soon as + // https://github.com/hapifhir/org.hl7.fhir.core/issues/2224 is fixed + else if (htmlInputs?.length === 4) { + const input0FhirType = htmlInputs[0].getAttribute("fhir-type") + + if (input0FhirType.startsWith("Quantity")) { + return new newQuestionnaireResponseItemQuantity(item.text, id, "", htmlInputs[0].value, htmlInputs[1].value, htmlInputs[2].value, htmlInputs[3].value, optional) + } + } return { item: null, valid: false } } @@ -373,6 +410,27 @@ function newQuestionnaireResponseItemBoolean(text, id, checkedTrue, checkedFalse } } +function newQuestionnaireResponseItemQuantity(text, id, comparator, value, unit, system, code, optional) { + const result = validateQuantity(id, comparator, value, unit, system, code, optional, "Item") + if (result.valid && result.value !== null) { + const item = { + linkId: id, + text: text, + answer: [{ + valueQuantity: { + comparator: result.value.comparator, + value: result.value.value, + unit: result.value.unit, + system: result.value.system, + code: result.value.code + } + }] + } + return { item: item, valid: true } + } else + return { input: null, valid: result.valid } +} + function validateAndConvert(id, fhirType, inputValue, optional, valueName) { const errorListElement = document.querySelector(`ul[for="${CSS.escape(id)}"]`) @@ -452,6 +510,43 @@ function validateIdentifier(id, system, value, optional, valueName) { } } +function validateQuantity(id, comparator, value, unit, system, code, optional, valueName) { + const valueEmpty = value === null || value.trim() === "" + const unitEmpty = unit === null || unit.trim() === "" + const systemEmpty = system === null || system.trim() === "" + const codeEmpty = code === null || code.trim() === "" + + if (optional && valueEmpty && unitEmpty) + return { value: null, valid: true } + else { + const errorListElement = document.querySelector(`ul[for="${CSS.escape(id)}"]`) + + const resultComparator = validateStringInList(errorListElement, comparator, ["<", "<=", ">=", ">"], true, valueName + " comparator") + + const valueIsDecimal = !valueEmpty && (value.includes(".") || value.includes(",")) + const resultValue = valueIsDecimal ? validateDecimal(errorListElement, value, false, valueName + " value") : validateInteger(errorListElement, value, false, valueName + " value") + const resultUnit = validateString(errorListElement, unit, false, valueName + " unit") + + const resultSystem = validateUrl(errorListElement, system, (systemEmpty && codeEmpty), valueName + " system" + (!codeEmpty ? " (if code set)" : "")) + const resultCode = validateString(errorListElement, code, (systemEmpty && codeEmpty), valueName + " code (if system set)") + + if (resultComparator.valid && resultValue.valid && resultUnit.valid && resultSystem.valid && resultCode.valid) { + return { + value: { + comparator: resultComparator.value, + value: resultValue.value, + unit: resultUnit.value, + system: resultSystem.value, + code: resultCode.value + }, + valid: true + } + } + else + return { value: null, valid: false } + } +} + function validateType(errorListElement, value, optional, valueName, typeValid, typeSpecificError, toType) { const stringValid = value !== null && value.trim() !== "" @@ -475,21 +570,23 @@ function validateString(errorListElement, value, optional, valueName) { return validateType(errorListElement, value, optional, valueName, () => true, null, v => v) } +function validateStringInList(errorListElement, value, list, optional, valueName) { + const valueInList = s => list.includes(s) + return validateType(errorListElement, value, optional, valueName, valueInList, "not in [" + list.toString() + "]" , v => v) +} + function validateInteger(errorListElement, value, optional, valueName) { const integerValid = v => Number.isSafeInteger(parseInt(v)) && parseInt(v) == v - return validateType(errorListElement, value, optional, valueName, integerValid, "not an integer", parseInt) } function validateDecimal(errorListElement, value, optional, valueName) { const decimalValid = v => !isNaN(parseFloat(v)) && parseFloat(v) == v - return validateType(errorListElement, value, optional, valueName, decimalValid, "not a decimal", parseFloat) } function validateDate(errorListElement, value, optional, valueName) { const dateValid = v => !isNaN(new Date(v)) - return validateType(errorListElement, value, optional, valueName, dateValid, "is not a date", v => new Date(v).toISOString().substring(0, 10)) } @@ -506,13 +603,11 @@ function validateDateTime(errorListElement, value, optional, valueName) { // TODO precision YYYY, YYYY-MM, YYYY-MM-DD also valid const dateValid = v => !isNaN(new Date(v)) - return validateType(errorListElement, value, optional, valueName, dateValid, "is not a date-time", v => new Date(v).toISOString()) } function validateInstant(errorListElement, value, optional, valueName) { const dateValid = v => !isNaN(new Date(v)) - return validateType(errorListElement, value, optional, valueName, dateValid, "is not a date-time", v => new Date(v).toISOString()) } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceElements.html b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceElements.html index 85ca233da..c5bd23736 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceElements.html +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceElements.html @@ -32,6 +32,19 @@ +
+ + + [[${quantity.value}]] [[${quantity.unit}]] ([[${quantity.system}]] | [[${quantity.code}]]) + [[${quantity.value}]] [[${quantity.code}]] ([[${quantity.system}]]) + [[${quantity.value}]] [[${quantity.unit}]] ([[${quantity.system}]]) + [[${quantity.value}]] [[${quantity.unit}]] + [[${quantity.value}]] [[${quantity.code}]] + [[${quantity.value}]] [[${quantity.unit}]] + [[${quantity.value}]] + +
+
id diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceQuestionnaireResponse.html b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceQuestionnaireResponse.html index 526ea5f88..95bae0cc4 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceQuestionnaireResponse.html +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceQuestionnaireResponse.html @@ -67,6 +67,41 @@ Copy to Clipboard
+ + +
+ + +
+ + Use placeholder value + Copy to Clipboard +
+
+ + Use placeholder value + Copy to Clipboard +
+
+
+
+ + Use placeholder value + Copy to Clipboard +
+
+ + Use placeholder value + Copy to Clipboard +
+
+
    @@ -75,7 +110,7 @@
    - - + + \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceTask.html b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceTask.html index d518e9aa3..f2de044e0 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceTask.html +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/fhir/template/resourceTask.html @@ -73,6 +73,38 @@

    Input

    Copy to Clipboard + + +
    +
    + + Insert Placeholder Value + Copy to Clipboard +
    +
    + + Insert Placeholder Value + Copy to Clipboard +
    +
    + + Insert Placeholder Value + Copy to Clipboard +
    +
    +
    +
    + + Insert Placeholder Value + Copy to Clipboard +
    +
    + + Insert Placeholder Value + Copy to Clipboard +
    +
    +
      @@ -130,6 +162,33 @@

      Output

      Copy to Clipboard
      + + +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      +
      @@ -175,6 +234,33 @@

      Output

      Copy to Clipboard
      + + +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      +
      +
      + + Copy to Clipboard +
      +
      + + Copy to Clipboard +
      +
      +
      diff --git a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-2.0.0.xml b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-2.0.0.xml index e8ed87978..46953f62d 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-2.0.0.xml +++ b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-2.0.0.xml @@ -68,7 +68,9 @@ (type = 'time') or (type = 'dateTime') or (type = 'reference') or - (type = 'url')" /> + (type = 'url') or + (type = 'choice') or + (type = 'quantity') " /> diff --git a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-response-2.0.0.xml b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-response-2.0.0.xml index 5b2c8a21d..ae99addd7 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-response-2.0.0.xml +++ b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-questionnaire-response-2.0.0.xml @@ -252,6 +252,12 @@ + + + + + + diff --git a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/QuestionnaireProfileTest.java b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/QuestionnaireProfileTest.java index 3bf7d0086..ffa314efa 100644 --- a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/QuestionnaireProfileTest.java +++ b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/QuestionnaireProfileTest.java @@ -99,6 +99,18 @@ public void testQuestionnaireValidTypeDisplay() testQuestionnaireValidType(Questionnaire.QuestionnaireItemType.DISPLAY); } + @Test + public void testQuestionnaireValidTypeChoice() + { + testQuestionnaireValidType(Questionnaire.QuestionnaireItemType.CHOICE); + } + + @Test + public void testQuestionnaireInvalidTypeQuantity() + { + testQuestionnaireValidType(Questionnaire.QuestionnaireItemType.QUANTITY); + } + private void testQuestionnaireValidType(Questionnaire.QuestionnaireItemType type) { Questionnaire res = createQuestionnaire(type); @@ -122,12 +134,6 @@ public void testQuestionnaireInvalidTypeQuestion() testQuestionnaireInvalidType(Questionnaire.QuestionnaireItemType.QUESTION); } - @Test - public void testQuestionnaireInvalidTypeChoice() - { - testQuestionnaireInvalidType(Questionnaire.QuestionnaireItemType.CHOICE); - } - @Test public void testQuestionnaireInvalidTypeOpenChoice() { @@ -140,12 +146,6 @@ public void testQuestionnaireInvalidTypeAttachment() testQuestionnaireInvalidType(Questionnaire.QuestionnaireItemType.ATTACHMENT); } - @Test - public void testQuestionnaireInvalidTypeQuantity() - { - testQuestionnaireInvalidType(Questionnaire.QuestionnaireItemType.QUANTITY); - } - private void testQuestionnaireInvalidType(Questionnaire.QuestionnaireItemType type) { Questionnaire q = createQuestionnaire(Questionnaire.QuestionnaireItemType.STRING);