diff --git a/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormData.java b/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormData.java index 44a034a849e5b5a34f20f4a369122aac82cf6d80..474be54dfd8c56eb60f5d9b2fb535742273f0fe8 100644 --- a/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormData.java +++ b/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormData.java @@ -26,6 +26,7 @@ package de.ozgcloud.eingang.common.formdata; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import jakarta.validation.constraints.NotNull; @@ -62,4 +63,19 @@ public class FormData { @Singular private List<IncomingFile> representations; + private FormDataControl control; + + @Getter + @Builder + public static class FormDataControl { + private Optional<Representations> representations; + } + + @Getter + @Builder + public static class Representations { + private String primaryFormDataRepresentation; + private String primaryFormDataPdfRepresentation; + private String formDataModell; + } } diff --git a/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormHeader.java b/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormHeader.java index 24b0e72766bf67f7702ea1974e7b2a2f69f1f22d..868b83b069bace798dc63571766f04308188d53f 100644 --- a/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormHeader.java +++ b/common/src/main/java/de/ozgcloud/eingang/common/formdata/FormHeader.java @@ -43,6 +43,8 @@ public class FormHeader { private String formId; private String formName; private String sender; + + private String channel; private String formEngineName; private ServiceKonto serviceKonto; diff --git a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/EnterpriseAdapterConfiguration.java b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/EnterpriseAdapterConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..0ac48af3cdb7119aa93b35dc674bdfd31458502e --- /dev/null +++ b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/EnterpriseAdapterConfiguration.java @@ -0,0 +1,19 @@ +package de.ozgcloud.eingang.enterprise; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import de.ozgcloud.eingang.semantik.enginebased.EngineBasedSemantikAdapter; +import de.ozgcloud.eingang.semantik.enginebased.a12.A12EngineBasedAdapter; +import de.ozgcloud.eingang.semantik.enginebased.a12.A12EngineBasedMapper; + +@Configuration +class EnterpriseAdapterConfiguration { + + @Bean + EngineBasedSemantikAdapter engineBasedAdapter(List<A12EngineBasedMapper> mapper) { + return new A12EngineBasedAdapter(mapper); + } +} diff --git a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryController.java b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryController.java index 20b6563bd1a8f6dd48a55340a4f803b05b9e542d..66431be6bf498d341a7b10ca8d38c7de46f42da3 100644 --- a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryController.java +++ b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryController.java @@ -1,8 +1,12 @@ package de.ozgcloud.eingang.enterprise.entry; import java.io.IOException; +import java.io.InputStream; import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Objects; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; @@ -13,8 +17,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.multipart.MultipartFile; +import de.ozgcloud.common.binaryfile.TempFileUtils; +import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.eingang.common.formdata.FormData; +import de.ozgcloud.eingang.common.formdata.IncomingFile; +import de.ozgcloud.eingang.common.formdata.IncomingFileGroup; import de.ozgcloud.eingang.common.vorgang.VorgangNummerSupplier; import de.ozgcloud.eingang.enterprise.entry.EntryResponse.ResponseVorgang; import de.ozgcloud.eingang.semantik.SemantikAdapter; @@ -25,6 +34,7 @@ import de.ozgcloud.eingang.semantik.SemantikAdapter; public class EntryController { private static final String STARTING_STATUS = "NEU"; + private static final String OTHER_FILE_GROUP_NAME = "Sonstige"; @Autowired private EntryDataMapper mapper; @@ -36,9 +46,14 @@ public class EntryController { @ResponseStatus(HttpStatus.ACCEPTED) @PostMapping(consumes = "multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE) - public EntryResponse receiveAntrag(@RequestPart Resource formData) throws IOException { + public EntryResponse receiveAntrag(@RequestPart("formData") Resource formData, + @RequestPart(name = "representation", required = false) Collection<MultipartFile> representations, + @RequestPart(name = "attachment", required = false) Collection<MultipartFile> attachment) + throws IOException { var mapped = mapper.mapEntryData(formData.getInputStream()); mapped = addVorgangNummer(mapped); + mapped = addRepresentations(representations, mapped); + mapped = addAttachments(attachment, mapped); var vorgangId = semantikAdapter.processFormData(mapped); @@ -50,6 +65,50 @@ public class EntryController { return formData.toBuilder().header(header).build(); } + private FormData addRepresentations(Collection<MultipartFile> representations, FormData formData) { + if (Objects.isNull(representations)) { + return formData; + } + + var builder = formData.toBuilder(); + representations.stream().map(this::buildIncomingFile).forEach(builder::representation); + return builder.numberOfRepresentations(representations.size()).build(); + } + + FormData addAttachments(Collection<MultipartFile> attachments, FormData formData) { + if (CollectionUtils.isEmpty(attachments)) { + return formData; + } + + IncomingFileGroup group = buildSonstigeGroup(attachments); + return formData.toBuilder().attachment(group).numberOfAttachments(group.getFiles().size()).build(); + } + + private IncomingFileGroup buildSonstigeGroup(Collection<MultipartFile> attachments) { + var builder = IncomingFileGroup.builder().name(OTHER_FILE_GROUP_NAME); + attachments.stream().map(this::buildIncomingFile).forEach(builder::file); + return builder.build(); + } + + private IncomingFile buildIncomingFile(MultipartFile multipartFile) { + InputStream inStream = getInputStream(multipartFile); + + return IncomingFile.builder() + .name(multipartFile.getOriginalFilename()) + .contentType(multipartFile.getContentType()) + .size(multipartFile.getSize()) + .file(TempFileUtils.writeTmpFile(inStream)) + .build(); + } + + private InputStream getInputStream(MultipartFile multipartFile) { + try { + return multipartFile.getInputStream(); + } catch (IOException e) { + throw new TechnicalException("Error reading incoming multipart File.", e); + } + } + EntryResponse buildResponse(FormData formData, String vorgangId) { return EntryResponse.builder() .transactionId(formData.getHeader().getRequestId()) diff --git a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryData.java b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryData.java index 619986d45e4ad01ac04dd5df7979adaea0a907a5..a25b788b39590e1f86375981374c668dd1319e37 100644 --- a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryData.java +++ b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/EntryData.java @@ -29,12 +29,22 @@ public class EntryData { private String formName; private Servicekonto serviceKonto; + private Representations representations; + @Builder @Getter @Jacksonized public static class ResultEndpoint { private String address; } + + @Builder + @Getter + @Jacksonized + public static class Representations { + private String formData; + private String formDataModell; + } } @Builder diff --git a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapper.java b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapper.java index 4ecc509d8213d160b34c0af4e8bfc5c98afebbe3..b671d184d9f6a90939b2d4e10be29722cb365e3f 100644 --- a/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapper.java +++ b/enterprise-adapter/src/main/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapper.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; @@ -11,6 +12,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import de.ozgcloud.eingang.common.formdata.FormData; +import de.ozgcloud.eingang.common.formdata.FormData.Representations; import de.ozgcloud.eingang.common.formdata.FormHeader; import de.ozgcloud.eingang.common.formdata.PostfachAddressIdentifier; import de.ozgcloud.eingang.common.formdata.ServiceKonto.PostfachAddress; @@ -37,14 +39,23 @@ public interface FormDataMapper { @Mapping(target = "zustaendigeStelle", source = "control.zustaendigeStelle") FormData mapEntryData(EntryData entryData); + default <T> Optional<T> mapOptional(T obj) { + return Optional.ofNullable(obj); + } + @Mapping(target = "vorgangNummer", ignore = true) @Mapping(target = "createdAt", ignore = true) - @Mapping(target = "formEngineName", constant = "EnterpriseSoftware") + @Mapping(target = "channel", constant = "B2G") + @Mapping(target = "formEngineName", constant = "A12") // TODO @Mapping(target = "requestId", source = "transactionId") @Mapping(target = "sender", ignore = true) // TODO fill from authentication @Mapping(target = "serviceKonto.postfachAddresses", ignore = true) FormHeader mapHeader(EntryData.ControlData controlData); + @Mapping(target = "primaryFormDataPdfRepresentation", ignore = true) + @Mapping(target = "primaryFormDataRepresentation", source = "formData") + Representations mapRepresentations(EntryData.ControlData.Representations representations); + default ZustaendigeStelle fromId(String organisationsEinheitenId) { return ZustaendigeStelle.builder().organisationseinheitenId(organisationsEinheitenId).build(); } diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/ControlDataTestFactory.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/ControlDataTestFactory.java index d2b7a571e5d619310929af44261dbf1be7347122..ece8a0aaf9f4f39a2044d8af1590f4b2ad638579 100644 --- a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/ControlDataTestFactory.java +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/ControlDataTestFactory.java @@ -1,6 +1,7 @@ package de.ozgcloud.eingang.enterprise.entry; import de.ozgcloud.eingang.enterprise.entry.EntryData.ControlData; +import de.ozgcloud.eingang.enterprise.entry.EntryData.ControlData.Representations; import de.ozgcloud.eingang.enterprise.entry.EntryData.ControlData.ResultEndpoint; public class ControlDataTestFactory { @@ -26,6 +27,10 @@ public class ControlDataTestFactory { .resultEndpoint(ResultEndpoint.builder().address(RESULT_ENDPOIN_ADDRESS).build()) .formId(FORM_ID) .formName(NAME) - .serviceKonto(ServicekontoTestFactory.create()); + .serviceKonto(ServicekontoTestFactory.create()) + .representations(Representations.builder() + .formData(FormDataControlTestFactory.PRIMARY_FORM_DATA_REPRESENTATION) + .formDataModell(FormDataControlTestFactory.FORM_DATA_MODELL) + .build()); } } diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EnterpriseEntryITCase.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EnterpriseEntryITCase.java index 026a2dba306f8d22f02b5bf4ede8c5ab64322e4b..86d4309910b98191d19bfc9a33b8b97fed0f33c9 100644 --- a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EnterpriseEntryITCase.java +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EnterpriseEntryITCase.java @@ -1,14 +1,19 @@ package de.ozgcloud.eingang.enterprise.entry; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @@ -16,6 +21,7 @@ import org.springframework.test.web.servlet.ResultActions; import de.ozgcloud.common.test.ITCase; import de.ozgcloud.common.test.TestUtils; +import de.ozgcloud.eingang.common.formdata.FormData; import de.ozgcloud.eingang.router.VorgangService; import lombok.SneakyThrows; @@ -40,7 +46,60 @@ class EnterpriseEntryITCase { @SneakyThrows private ResultActions doPostRequest() { return mockMvc.perform(multipart("/antrag") - .file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/simple.json").getBytes()))) + .file(new MockMultipartFile("formData", "simple.json", MediaType.APPLICATION_JSON.toString(), + TestUtils.loadTextFile("request/simple.json").getBytes()))) .andExpect(status().is2xxSuccessful()); } + + @Nested + class A12Entry { + + @Captor + private ArgumentCaptor<FormData> formDataCaptor; + + @Test + void shouldCreateVorgang() { + var formData = doPostRequest(); + + assertThat(formData).isNotNull(); + } + + void shouldHaveNoOfRepresentations() { + var formData = doPostRequest(); + + assertThat(formData.getNumberOfRepresentations()).isEqualTo(2); + } + + @Test + void shouldHaveFormData() { + var formData = doPostRequest(); + + assertThat(formData.getFormData()).containsKeys("Haushaltsjahr", "Anlage_1", "Anlage_2"); + } + + @Test + void shouldHaveMukServiceKonto() { + var formData = doPostRequest(); + + assertThat(formData.getHeader().getServiceKonto().getType()).isEqualTo("MUK"); + } + + @SneakyThrows + private FormData doPostRequest() { + mockMvc.perform(multipart("/antrag") + .file(buildJsonFileMock("formData", "a12_entry.json")) + .file(buildJsonFileMock("representation", "document_JaS.json")) + .file(buildJsonFileMock("representation", "jugendsozialarbeit_an_schulen-DM.json"))) + .andExpect(status().is2xxSuccessful()); + + verify(vorgangService).createVorgang(formDataCaptor.capture()); + return formDataCaptor.getValue(); + } + + } + + private MockMultipartFile buildJsonFileMock(String name, String origName) { + var fileToLoad = "request/%s".formatted(origName); + return new MockMultipartFile(name, origName, MediaType.APPLICATION_JSON.toString(), TestUtils.loadTextFile(fileToLoad).getBytes()); + } } diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryControllerTest.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryControllerTest.java index 6e3bcb26596a888594f0560cf7bc5cea7c963ba2..5d1515ab2e9d1b8b2d765e1e16408273803fc3da 100644 --- a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryControllerTest.java +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryControllerTest.java @@ -1,12 +1,15 @@ package de.ozgcloud.eingang.enterprise.entry; import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.InstanceOfAssertFactories.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.io.InputStream; +import java.util.Collections; +import java.util.List; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; @@ -28,6 +31,7 @@ import de.ozgcloud.eingang.common.formdata.FormData; import de.ozgcloud.eingang.common.formdata.FormDataTestFactory; import de.ozgcloud.eingang.common.formdata.FormHeader; import de.ozgcloud.eingang.common.formdata.FormHeaderTestFactory; +import de.ozgcloud.eingang.common.formdata.IncomingFileGroup; import de.ozgcloud.eingang.common.vorgang.VorgangNummerSupplier; import de.ozgcloud.eingang.semantik.SemantikAdapter; import lombok.SneakyThrows; @@ -56,7 +60,7 @@ class EntryControllerTest { class ReceiveAntrag { private final FormHeader header = FormHeaderTestFactory.createBuilder().vorgangNummer(null).build(); - private final FormData formData = FormDataTestFactory.createBuilder().header(header).build(); + private final FormData formData = FormDataTestFactory.createBuilder().header(header).clearAttachments().clearRepresentations().build(); @Captor private ArgumentCaptor<InputStream> streamCaptor; @@ -108,7 +112,7 @@ class EntryControllerTest { @Test @SneakyThrows void shouldReturnResponse() { - var response = controller.receiveAntrag(mock(Resource.class)); + var response = controller.receiveAntrag(mock(Resource.class), Collections.emptyList(), Collections.emptyList()); assertThat(response).isSameAs(this.response); } @@ -119,6 +123,112 @@ class EntryControllerTest { .file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/simple.json").getBytes()))) .andExpect(status().is2xxSuccessful()); } + + @Nested + class WithRepresentations { + + @Captor + private ArgumentCaptor<FormData> formDataCaptor; + + @Test + void shouldAddRepresentations() { + var formData = doPostRequest(); + + assertThat(formData.getRepresentations()).hasSize(2); + } + + @Test + void shouldSetNumberOfRepresentations() { + var formData = doPostRequest(); + + assertThat(formData.getNumberOfRepresentations()).isEqualTo(2); + } + + @SneakyThrows + private FormData doPostRequest() { + mockMvc.perform(multipart("/antrag") + .file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/a12_entry.json").getBytes())) + .file(new MockMultipartFile("representation", TestUtils.loadTextFile("request/document_JaS.json").getBytes())) + .file(new MockMultipartFile("representation", + TestUtils.loadTextFile("request/jugendsozialarbeit_an_schulen-DM.json").getBytes()))) + .andExpect(status().is2xxSuccessful()); + + verify(semantikAdapter).processFormData(formDataCaptor.capture()); + return formDataCaptor.getValue(); + } + } + + @Nested + class WithAttachments { + @Test + void shouldAddAttachments() { + var formData = doPostRequest(); + + assertThat(formData.getAttachments()).hasSize(1).first().extracting(IncomingFileGroup::getFiles).asInstanceOf(LIST).hasSize(1); + } + + @Test + void shouldSetNumberOfAttachments() { + var formData = doPostRequest(); + + assertThat(formData.getNumberOfAttachments()).isEqualTo(1); + } + + @SneakyThrows + private FormData doPostRequest() { + mockMvc.perform(multipart("/antrag") + .file(new MockMultipartFile("formData", TestUtils.loadTextFile("request/a12_entry.json").getBytes())) + .file(new MockMultipartFile("attachment", TestUtils.loadTextFile("request/document_JaS.json").getBytes()))) + .andExpect(status().is2xxSuccessful()); + + verify(semantikAdapter).processFormData(formDataCaptor.capture()); + return formDataCaptor.getValue(); + } + + } + + } + + @Nested + class TestAddAttachments { + + private MockMultipartFile file = new MockMultipartFile("attachment", TestUtils.loadTextFile("request/document_JaS.json").getBytes()); + + @Test + void shouldIgnoreNullCollection() { + var formData = FormDataTestFactory.create(); + + var result = controller.addAttachments(null, formData); + + assertThat(result).isSameAs(formData); + } + + @Test + void shouldIgnoreEmptyCollection() { + var formData = FormDataTestFactory.create(); + + var result = controller.addAttachments(Collections.emptyList(), formData); + + assertThat(result).isSameAs(formData); + } + + @Test + void shouldAddToSonstigeGroup() { + var formData = FormDataTestFactory.createBuilder().clearAttachments().build(); + + var result = controller.addAttachments(List.of(file), formData); + + assertThat(result.getAttachments()).hasSize(1).first().extracting(IncomingFileGroup::getName).isEqualTo("Sonstige"); + } + + @Test + void shouldAddFiles() { + var formData = FormDataTestFactory.createBuilder().clearAttachments().build(); + + var result = controller.addAttachments(List.of(file), formData); + + assertThat(result.getAttachments()).hasSize(1).first().extracting(IncomingFileGroup::getFiles).asInstanceOf(LIST).hasSize(1); + } } @Nested diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryDataMapperTest.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryDataMapperTest.java index de9426a23d153faf8c7c33ea35ae3087fcb16fdc..797f0ec78434b82586b90e31cc2c6932188d744c 100644 --- a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryDataMapperTest.java +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/EntryDataMapperTest.java @@ -46,7 +46,9 @@ class EntryDataMapperTest { void shouldReadJson() { var read = mapper.readRequest(jsonInput); - assertThat(read).usingRecursiveComparison().isEqualTo(EntryDataTestFactory.create()); + assertThat(read).usingRecursiveComparison() + .isEqualTo(EntryDataTestFactory.createBuilder().control(ControlDataTestFactory.createBuilder().representations(null).build()) + .build()); } } diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataControlTestFactory.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataControlTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..fe7e1a060b7216a5f466a5fcbd272d5bedbe1c33 --- /dev/null +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataControlTestFactory.java @@ -0,0 +1,25 @@ +package de.ozgcloud.eingang.enterprise.entry; + +import java.util.Optional; + +import de.ozgcloud.eingang.common.formdata.FormData.FormDataControl; +import de.ozgcloud.eingang.common.formdata.FormData.Representations; + +public class FormDataControlTestFactory { + + public static final String PRIMARY_FORM_DATA_REPRESENTATION = "document_JaS.json"; + public static final String PRIMARY_FORM_DATA_PDF_REPRESENTATION = "document.pdf"; + public static final String FORM_DATA_MODELL = "jugendsozialarbeit_an_schulen-DM.json"; + + public static FormDataControl create() { + return createBuilder().build(); + } + + public static FormDataControl.FormDataControlBuilder createBuilder() { + return FormDataControl.builder() + .representations(Optional.of(Representations.builder() + .primaryFormDataRepresentation(PRIMARY_FORM_DATA_REPRESENTATION) + .formDataModell(FORM_DATA_MODELL) + .build())); + } +} diff --git a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapperTest.java b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapperTest.java index f725885390ee66c6da08232950680b787884562c..3b4d6691965f4a8cc9dc273dd4b3be6ab7409bcb 100644 --- a/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapperTest.java +++ b/enterprise-adapter/src/test/java/de/ozgcloud/eingang/enterprise/entry/FormDataMapperTest.java @@ -23,4 +23,14 @@ class FormDataMapperTest { } } + @Nested + class TestMapRepresentations { + @Test + void shouldMapRepresentations() { + var mapped = mapper.mapRepresentations(ControlDataTestFactory.create().getRepresentations()); + + assertThat(mapped).usingRecursiveComparison().isEqualTo(FormDataControlTestFactory.create().getRepresentations().get()); + } + } + } diff --git a/enterprise-adapter/src/test/resources/request/a12_entry.json b/enterprise-adapter/src/test/resources/request/a12_entry.json new file mode 100644 index 0000000000000000000000000000000000000000..553b82dc2c082291705ae961c432a89e351e9ded --- /dev/null +++ b/enterprise-adapter/src/test/resources/request/a12_entry.json @@ -0,0 +1,27 @@ +{ + "control": { + "transactionId": "4e7a6ae7-4d0f-444d-8971-7cfc051c9924", + "zustaendigeStelle": "248240886", + "leikaIds": [ + "99108011000000", + "99108011153000" + ], + "resultEndpoint": { + "address": "https://idalabs.de/backend/api" + }, + "formId": "KFAS_LIVE_KI_10_Haltverbot_befristet", + "name": "Anmeldung zur Einrichtung einer zeitlich befristeten Haltverbotszone gem. § 45 Abs. 1 Straßenverkehrsordnung (StVO)", + "serviceKonto": { + "type": "MUK", + "postfachAddress": { + "identifier": "e155fcdc-a73e-4ae2-b148-b02e02331ebb", + "type": "unternehmen" + } + }, + "representations": { + "formData": "document_JaS.json", + "modell": "jugendsozialarbeit_an_schulen-DM.json" + } + }, + "formData": [] +} \ No newline at end of file diff --git a/enterprise-adapter/src/test/resources/request/document_JaS.json b/enterprise-adapter/src/test/resources/request/document_JaS.json new file mode 100644 index 0000000000000000000000000000000000000000..1245a457921a4f4d8f78de3265ec602ecf21bd38 --- /dev/null +++ b/enterprise-adapter/src/test/resources/request/document_JaS.json @@ -0,0 +1,99 @@ +{ + "root": { + "Haushaltsjahr": "2024", + "Anlage_2": { + "Finanzierungsplan": { + "Insgesamt_Finanzierungsplan": 117450, + "Eigenmittel_von_Hundert": 1.5, + "Eigenmittel_Betrag": 1500, + "Zuwendungen_kirchlicher_Stellen": 0, + "Angabe_der_kirchlichen_Stelle": "-", + "Zuwendungen_Bezirk": 600, + "Beantragter_Zuschuss": 700, + "Erlaeuterungen_Finanzierungsplan": "Das passt doch schon so.", + "Zuwendung_kommunale": { + "Gemeinden": "Eselstadt", + "Zuwendung_Gemeinden": 100000, + "Landkreise": "Esellandkreis", + "Zuwendung_Landkreise": 10000 + }, + "Sonstige_oeffentliche_Mittel": { + "Angabe_Bewilligungstelle_sonsitge_oeffentl_Mittel": "Amt für Eselangelegenheiten", + "Betrag_sonsitge_oeffentl_Mittel": 5000 + }, + "Fremdmittel": { + "Angabe_Bewilligungstelle_Fremdmittel": "Eselförderverband", + "Betrag_Fremdmittel": 250 + } + }, + "Kostenplan": { + "Insgesamt_Kostenplan": 175003, + "Personalkosten_JaS": 100000, + "Personalkosten_Sonstige": 15000, + "Sachkosten": 60000, + "Sonstige_Kosten": 3, + "Erlaeuterungen_Kostenplan": "Eseltrainer sind eben teuer." + } + }, + "Angaben_Antragssteller": { + "Name_Bezeichnung": "Eselschule Eselstadt", + "Strasse_Hausnummer": "Eselweg 12", + "PLZ": "01234", + "Ort": "Eselstadt", + "Telefon": "01234-5678", + "Fax": "01234-5679", + "EMail": "esel@esel.esel", + "Verantwortlicher_Vertreter": "Eva Esel", + "Zustaendiger_Sachbearbeiter": "Miriam Maultier", + "Bankverbindung": { + "Kreditinstitut": "Eselbank", + "Kontoinhaber": "Eselschule Eselstadt", + "IBAN": "DE62500105179236738719", + "BIC": "TDLJCAC5JPS" + }, + "Zustaendiger_Spitzenverband": { + "Bezeichnung": "Schulverband für liebe Esel", + "Strasse_Hausnummer": "Eselstraße 5", + "PLZ": "01234", + "Ort": "Eselstadt" + } + }, + "Angaben_zum_Projekt": { + "Bezeichnung": "Wandern mit lieben Eseln", + "Beginn_der_Massnahme": "2024-03-01", + "Verantwortlicher_Sachbearbeiter": "Eduard Esel", + "Strasse_Hausnummer": "Eselallee 77", + "PLZ": "01234", + "Ort": "Eselstadt", + "Telefon": "01234-9876", + "Fax": "01234-9875", + "EMail": "esel2@esel.esel" + }, + "Anlage_1": { + "Personalkosten": [ + { + "Lfd_Nr": 1, + "Name": "Leon Lieblich", + "Berufsausbildung_Funktion": "Eseltrainer", + "Beschaeftigungszeitraum": "01/99-", + "Woechentliche_Arbeitszeit": 24, + "Regelarbeitszeit": 24, + "Entgeltgruppe": "E14", + "Jahresaufwand": 134000, + "Zuschuss": 32000 + }, + { + "Lfd_Nr": 2, + "Name": "Viktoria Vergnüglich", + "Berufsausbildung_Funktion": "Eseltrainerin", + "Beschaeftigungszeitraum": "01/2023-", + "Woechentliche_Arbeitszeit": 38.5, + "Regelarbeitszeit": 38.5, + "Entgeltgruppe": "E14", + "Jahresaufwand": 123000, + "Zuschuss": 32000 + } + ] + } + } +} \ No newline at end of file diff --git a/enterprise-adapter/src/test/resources/request/jugendsozialarbeit_an_schulen-DM.json b/enterprise-adapter/src/test/resources/request/jugendsozialarbeit_an_schulen-DM.json new file mode 100644 index 0000000000000000000000000000000000000000..8d9d251181091c3279f5ca245fcd1da2c4790333 --- /dev/null +++ b/enterprise-adapter/src/test/resources/request/jugendsozialarbeit_an_schulen-DM.json @@ -0,0 +1,1279 @@ +{ + "header": { + "id": "jugendsozialarbeit_an_schulen-DM", + "modelType": "document", + "modelVersion": "26.0.0", + "locales": [ + { + "code": "de" + } + ], + "annotations": [ + { + "name": "roles", + "value": "admin" + } + ], + "modelReferences": [] + }, + "content": { + "modelInfo": { + "name": "jugendsozialarbeit_an_schulen-DM" + }, + "modelConfig": { + "decimalSeparator": ".", + "timeZone": "UTC" + }, + "modelRoot": { + "rootGroups": [ + { + "type": "Group", + "id": "group_e6cca", + "name": "root", + "Group": { + "repeatability": 1, + "required": true, + "elements": [ + { + "type": "Field", + "id": "field_89587", + "name": "Haushaltsjahr", + "Field": { + "fieldType": { + "type": "DateFragmentType", + "DateFragmentType": { + "formatOfFragment": "yyyy" + } + }, + "label": [ + { + "locale": "de", + "text": "Haushaltsjahr" + } + ] + } + }, + { + "type": "Group", + "id": "group_49b20", + "name": "Angaben_Antragssteller", + "Group": { + "repeatability": 1, + "required": true, + "elements": [ + { + "type": "Field", + "id": "field_65687", + "name": "Name_Bezeichnung", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Name/Bezeichnung" + } + ], + "required": true + } + }, + { + "type": "Field", + "id": "field_4a6d0", + "name": "Strasse_Hausnummer", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Straße, Hausnummer" + } + ] + } + }, + { + "type": "Field", + "id": "field_b2e13", + "name": "PLZ", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "errorMessage": [ + { + "locale": "de", + "text": "Bitte geben Sie eine valide Postleitzahl an." + } + ], + "pattern": "[\\d]{5}" + } + }, + "label": [ + { + "locale": "de", + "text": "PLZ" + } + ] + } + }, + { + "type": "Field", + "id": "field_a2612", + "name": "Ort", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Ort" + } + ] + } + }, + { + "type": "Field", + "id": "field_d1dc0", + "name": "Telefon", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Telefon" + } + ] + } + }, + { + "type": "Field", + "id": "field_3d3e0", + "name": "Fax", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Fax" + } + ] + } + }, + { + "type": "Field", + "id": "field_a7755", + "name": "EMail", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "E-Mail" + } + ] + } + }, + { + "type": "Field", + "id": "field_a7cef", + "name": "Verantwortlicher_Vertreter", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Verantwortliche Vertreterin/Verantwortlicher Vertreter" + } + ] + } + }, + { + "type": "Field", + "id": "field_293a6", + "name": "Zustaendiger_Sachbearbeiter", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Zuständige Sachbearbeiterin/Zuständiger Sachbearbeiter" + } + ] + } + }, + { + "type": "Group", + "id": "group_44061", + "name": "Bankverbindung", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_77875", + "name": "Kreditinstitut", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Kreditinstitut" + } + ] + } + }, + { + "type": "Field", + "id": "field_807c3", + "name": "Kontoinhaber", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Kontoinhaber" + } + ] + } + }, + { + "type": "Field", + "id": "field_fb270", + "name": "IBAN", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "errorMessage": [ + { + "locale": "de", + "text": "Bitte geben Sie eine valide IBAN an." + } + ], + "pattern": "DE\\d{2}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{4}[ ]\\d{2}|DE\\d{20}" + } + }, + "label": [ + { + "locale": "de", + "text": "IBAN" + } + ] + } + }, + { + "type": "Field", + "id": "field_d1499", + "name": "BIC", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "errorMessage": [ + { + "locale": "de", + "text": "Bitte geben Sie eine valide BIC an." + } + ], + "pattern": "[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}" + } + }, + "label": [ + { + "locale": "de", + "text": "BIC" + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_d267e", + "name": "Zustaendiger_Spitzenverband", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_e73fd", + "name": "Bezeichnung", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Bezeichnung" + } + ] + } + }, + { + "type": "Field", + "id": "field_ca3c1", + "name": "Strasse_Hausnummer", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Straße, Hausnummer" + } + ] + } + }, + { + "type": "Field", + "id": "field_f002d", + "name": "PLZ", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "errorMessage": [ + { + "locale": "de", + "text": "Bitte geben Sie eine valide Postleitzahl an." + } + ], + "pattern": "[\\d]{5}" + } + }, + "label": [ + { + "locale": "de", + "text": "PLZ" + } + ] + } + }, + { + "type": "Field", + "id": "field_47beb", + "name": "Ort", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Ort" + } + ] + } + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_66b19", + "name": "Angaben_zum_Projekt", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_de8f8", + "name": "Bezeichnung", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Bezeichnung der Maßnahme (Name der Schule)" + } + ] + } + }, + { + "type": "Field", + "id": "field_83207", + "name": "Beginn_der_Massnahme", + "Field": { + "fieldType": { + "type": "DateType", + "DateType": { + "format": "yyyy-MM-dd" + } + }, + "label": [ + { + "locale": "de", + "text": "Beginn der Maßnahme seit Genehmigung\nbzw. ggf. seit Aufstockung" + } + ] + } + }, + { + "type": "Field", + "id": "field_18de1", + "name": "Verantwortlicher_Sachbearbeiter", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Verantwortliche Sachbearbeiterin/Verantwortlicher Sachbearbeiter des Trägers der öffentl. Jugendhilfe (Landkreis/kreisfreie Stadt)" + } + ] + } + }, + { + "type": "Field", + "id": "field_dcaf0", + "name": "Strasse_Hausnummer", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Straße, Hausnummer" + } + ] + } + }, + { + "type": "Field", + "id": "field_a11e7", + "name": "PLZ", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "errorMessage": [ + { + "locale": "de", + "text": "Bitte geben Sie eine valide Postleitzahl an." + } + ], + "pattern": "[\\d]{5}" + } + }, + "label": [ + { + "locale": "de", + "text": "PLZ" + } + ] + } + }, + { + "type": "Field", + "id": "field_7afb0", + "name": "Ort", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Ort" + } + ] + } + }, + { + "type": "Field", + "id": "field_4f06a", + "name": "Telefon", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Telefon" + } + ] + } + }, + { + "type": "Field", + "id": "field_236e2", + "name": "Fax", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Fax" + } + ] + } + }, + { + "type": "Field", + "id": "field_b59d5", + "name": "EMail", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "E-Mail" + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_cade6", + "name": "Anlage_1", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Group", + "id": "group_7b67f", + "name": "Personalkosten", + "Group": { + "repeatability": 10, + "elements": [ + { + "type": "Field", + "id": "field_275b9", + "name": "Lfd_Nr", + "Field": { + "fieldType": { + "type": "NumberType" + }, + "label": [ + { + "locale": "de", + "text": "Lfd. Nr." + } + ] + } + }, + { + "type": "Field", + "id": "field_2d26e", + "name": "Name", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Vor- und Zuname der Fachkraft" + } + ] + } + }, + { + "type": "Field", + "id": "field_71e95", + "name": "Berufsausbildung_Funktion", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Berufsausbildung und Funktion im geförderten Projekt (sofern noch nicht vorliegend, bitte Ausbildungsnachweis beilegen)" + } + ] + } + }, + { + "type": "Field", + "id": "field_e19d7", + "name": "Beschaeftigungszeitraum", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Beschäftigungszeitraum" + } + ] + } + }, + { + "type": "Field", + "id": "field_79cef", + "name": "Woechentliche_Arbeitszeit", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "maxFractionalDigits": 1 + } + }, + "label": [ + { + "locale": "de", + "text": "wöchentl. Arbeitszeit" + } + ] + } + }, + { + "type": "Field", + "id": "field_d1447", + "name": "Regelarbeitszeit", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "maxFractionalDigits": 1 + } + }, + "label": [ + { + "locale": "de", + "text": "tarifliche Regelarbeitszeit beim Träger" + } + ] + } + }, + { + "type": "Field", + "id": "field_ff859", + "name": "Entgeltgruppe", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Entgeltgruppe" + } + ] + } + }, + { + "type": "Field", + "id": "field_f618b", + "name": "Jahresaufwand", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Jahresaufwand in €" + } + ] + } + }, + { + "type": "Field", + "id": "field_f994b", + "name": "Zuschuss", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Zuschuss Festbetragsförderung (nicht vom Antragsteller auszufüllen)" + } + ] + } + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_d7cfe", + "name": "Anlage_2", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Group", + "id": "group_55285", + "name": "Kostenplan", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_98416", + "name": "Personalkosten_JaS", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Personalkosten der JaS-Fachkraft/Fachkräfte" + } + ] + } + }, + { + "type": "Field", + "id": "field_ebdd5", + "name": "Personalkosten_Sonstige", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Sonstige Personalkosten" + } + ] + } + }, + { + "type": "Field", + "id": "field_20288", + "name": "Sachkosten", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Sachkosten für den lfd. Betrieb" + } + ] + } + }, + { + "type": "Field", + "id": "field_b7bb2", + "name": "Sonstige_Kosten", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Sonstige Kosten" + } + ] + } + }, + { + "type": "Field", + "id": "field_e4001", + "name": "Insgesamt_Kostenplan", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Insgesamt" + } + ] + } + }, + { + "type": "Computation", + "id": "computation_2098f", + "name": "Insgesamt_Kostenplan_Comp", + "Computation": { + "computedFieldId": "field_e4001", + "computationAlternatives": [ + { + "operation": "[Personalkosten_Sonstige]+[Personalkosten_JaS]+[Sachkosten]+[Sonstige_Kosten]" + } + ], + "errorMessage": [ + { + "locale": "de", + "text": "error text for computation of Insgesamt_Kostenplan_Comp" + } + ] + } + }, + { + "type": "Field", + "id": "field_d09ae", + "name": "Erlaeuterungen_Kostenplan", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "lineBreaksPermitted": true + } + }, + "label": [ + { + "locale": "de", + "text": "Erläuterungen" + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_60c8d", + "name": "Finanzierungsplan", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_7e8f3", + "name": "Eigenmittel_von_Hundert", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "percent" + } + }, + "label": [ + { + "locale": "de", + "text": "Eigenmittel von Hundert" + } + ] + } + }, + { + "type": "Field", + "id": "field_228aa", + "name": "Eigenmittel_Betrag", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Eigenmittel Betrag" + } + ] + } + }, + { + "type": "Field", + "id": "field_264d6", + "name": "Zuwendungen_kirchlicher_Stellen", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Zuwendungen kirchlicher Stellen" + } + ] + } + }, + { + "type": "Field", + "id": "field_55d96", + "name": "Angabe_der_kirchlichen_Stelle", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Angabe der Stelle" + } + ] + } + }, + { + "type": "Group", + "id": "group_cb4bd", + "name": "Zuwendung_kommunale", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_516a4", + "name": "Gemeinden", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Gemeinden" + } + ] + } + }, + { + "type": "Field", + "id": "field_d53ed", + "name": "Zuwendung_Gemeinden", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Betrag" + } + ] + } + }, + { + "type": "Field", + "id": "field_3216a", + "name": "Landkreise", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Landkreise" + } + ] + } + }, + { + "type": "Field", + "id": "field_830de", + "name": "Zuwendung_Landkreise", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Betrag" + } + ] + } + } + ] + } + }, + { + "type": "Field", + "id": "field_45f01", + "name": "Zuwendungen_Bezirk", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Zuwendungen des Bezirks" + } + ] + } + }, + { + "type": "Group", + "id": "group_dbe9b", + "name": "Sonstige_oeffentliche_Mittel", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_fe71d", + "name": "Angabe_Bewilligungstelle_sonsitge_oeffentl_Mittel", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Angabe der Bewilligungsstelle" + } + ] + } + }, + { + "type": "Field", + "id": "field_1c494", + "name": "Betrag_sonsitge_oeffentl_Mittel", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Betrag" + } + ] + } + } + ] + } + }, + { + "type": "Group", + "id": "group_4ec36", + "name": "Fremdmittel", + "Group": { + "repeatability": 1, + "elements": [ + { + "type": "Field", + "id": "field_68026", + "name": "Angabe_Bewilligungstelle_Fremdmittel", + "Field": { + "fieldType": { + "type": "StringType" + }, + "label": [ + { + "locale": "de", + "text": "Angabe der Bewilligungsstelle" + } + ] + } + }, + { + "type": "Field", + "id": "field_e59dd", + "name": "Betrag_Fremdmittel", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Betrag" + } + ] + } + } + ] + } + }, + { + "type": "Field", + "id": "field_81843", + "name": "Beantragter_Zuschuss", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Beantragter Zuschuss des Bayerischen Staatsministeriums für Familie, Arbeit und Soziales" + } + ] + } + }, + { + "type": "Field", + "id": "field_479e3", + "name": "Insgesamt_Finanzierungsplan", + "Field": { + "fieldType": { + "type": "NumberType", + "NumberType": { + "minFractionalDigits": 2, + "maxFractionalDigits": 2, + "trait": "amount" + } + }, + "label": [ + { + "locale": "de", + "text": "Insgesamt" + } + ] + } + }, + { + "type": "Computation", + "id": "computation_3849f", + "name": "Insgesamt_Kostenplan_Comp", + "Computation": { + "computedFieldId": "field_479e3", + "computationAlternatives": [ + { + "operation": "[Eigenmittel_Betrag]+[Zuwendungen_kirchlicher_Stellen]+[Zuwendung_kommunale/Zuwendung_Gemeinden]+[Zuwendung_kommunale/Zuwendung_Landkreise]+[Sonstige_oeffentliche_Mittel/Betrag_sonsitge_oeffentl_Mittel]+[Fremdmittel/Betrag_Fremdmittel]+[Beantragter_Zuschuss]" + } + ], + "errorMessage": [ + { + "locale": "de", + "text": "error text for computation of Insgesamt_Kostenplan_Comp" + } + ] + } + }, + { + "type": "Field", + "id": "field_b1b30", + "name": "Erlaeuterungen_Finanzierungsplan", + "Field": { + "fieldType": { + "type": "StringType", + "StringType": { + "lineBreaksPermitted": true + } + }, + "label": [ + { + "locale": "de", + "text": "Erläuterungen" + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12Eingang.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12Eingang.java new file mode 100644 index 0000000000000000000000000000000000000000..c9f93b0c0196b2bf9d97ff98043b48b04943de94 --- /dev/null +++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12Eingang.java @@ -0,0 +1,15 @@ +package de.ozgcloud.eingang.semantik.enginebased.a12; + +import java.util.Map; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; + +@Builder +@Getter +@Jacksonized +public class A12Eingang { + + private Map<String, Object> root; +} diff --git a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedAdapter.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ac42d7f7cfa1c7cdf7ded2fcac816cda943e42cc --- /dev/null +++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedAdapter.java @@ -0,0 +1,24 @@ +package de.ozgcloud.eingang.semantik.enginebased.a12; + +import java.util.List; + +import de.ozgcloud.eingang.common.formdata.FormData; +import de.ozgcloud.eingang.semantik.enginebased.EngineBasedSemantikAdapter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class A12EngineBasedAdapter implements EngineBasedSemantikAdapter { + + private final List<A12EngineBasedMapper> mappers; + + @Override + public FormData parseFormData(FormData formData) { + var processedFormData = formData; + + for (var mapper : mappers) { + processedFormData = mapper.parseFormData(processedFormData); + } + return processedFormData; + } + +} diff --git a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedMapper.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3f441525324679a29fc4adc0bec2574ef7b3df7a --- /dev/null +++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12EngineBasedMapper.java @@ -0,0 +1,7 @@ +package de.ozgcloud.eingang.semantik.enginebased.a12; + +import de.ozgcloud.eingang.semantik.enginebased.EngineBasedMapper; + +public interface A12EngineBasedMapper extends EngineBasedMapper { + +} diff --git a/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapper.java b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..688726362cc93f2b271f683f28ad7a929f725432 --- /dev/null +++ b/semantik-adapter/src/main/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapper.java @@ -0,0 +1,79 @@ +package de.ozgcloud.eingang.semantik.enginebased.a12; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.eingang.common.formdata.FormData; +import de.ozgcloud.eingang.common.formdata.FormData.FormDataControl; +import de.ozgcloud.eingang.common.formdata.FormData.Representations; +import de.ozgcloud.eingang.common.formdata.IncomingFile; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class A12FormDataMapper implements A12EngineBasedMapper { + + private final ObjectMapper objectMapper; + + @Override + public FormData parseFormData(FormData formData) { + return mergeReadFormData(formData, getFormDataFile(formData)); + } + + private FormData mergeReadFormData(FormData formData, Optional<IncomingFile> inFile) { + return inFile.map(in -> readEingang(in.getFile())) + .map(eingang -> eingang.getRoot()) + .map(formDataMap -> merge(formDataMap, formData.getFormData())) + .map(formDataMap -> formData.toBuilder().formData(formDataMap).build()) + .orElse(formData); + } + + Optional<IncomingFile> getFormDataFile(FormData formData) { + var inFile = Optional.ofNullable(formData.getControl()) + .flatMap(FormDataControl::getRepresentations) + .map(Representations::getPrimaryFormDataRepresentation) + .flatMap(fileName -> getByName(fileName, formData.getRepresentations())); + + logWarnIfEmpty(inFile); + return inFile; + } + + A12Eingang readEingang(File formDataJson) { + try { + return objectMapper.readValue(formDataJson, A12Eingang.class); + } catch (IOException e) { + throw new TechnicalException("Error parsing JSON from A12 FormDataFile", e); + } + } + + void logWarnIfEmpty(Optional<IncomingFile> inFile) { + if (inFile.isEmpty()) { + LOG.warn("No primary Form Data Representation found."); + } + } + + Optional<IncomingFile> getByName(String name, List<IncomingFile> inFiles) { + return inFiles.stream() + .filter(inFile -> StringUtils.equals(name, inFile.getName())) + .findFirst(); + } + + <A, B> Map<A, B> merge(Map<A, B> map1, Map<A, B> map2) { + var map = new HashMap<>(map1); + map.putAll(map2); + return Collections.unmodifiableMap(map); + } +} diff --git a/semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapperTest.java b/semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aa4ee4fda33b39b15dfac04de58939b1dfcc18d1 --- /dev/null +++ b/semantik-adapter/src/test/java/de/ozgcloud/eingang/semantik/enginebased/a12/A12FormDataMapperTest.java @@ -0,0 +1,93 @@ +package de.ozgcloud.eingang.semantik.enginebased.a12; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Spy; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.ozgcloud.common.binaryfile.TempFileUtils; +import de.ozgcloud.common.test.TestUtils; +import de.ozgcloud.eingang.common.formdata.FormData; +import de.ozgcloud.eingang.common.formdata.FormData.FormDataControl; +import de.ozgcloud.eingang.common.formdata.FormData.Representations; +import de.ozgcloud.eingang.common.formdata.FormDataTestFactory; +import de.ozgcloud.eingang.common.formdata.IncomingFile; +import de.ozgcloud.eingang.common.formdata.IncomingFileTestFactory; + +class A12FormDataMapperTest { + + @Spy + @InjectMocks + private A12FormDataMapper mapper; + + @Spy + private ObjectMapper objectMapper = new ObjectMapper(); + + @Nested + class TestParseFormData { + + @Test + void shouldGetFormDataFile() { + var formData = buildInputFormData(); + + mapper.parseFormData(formData); + + verify(mapper).getFormDataFile(same(formData)); + } + } + + @Nested + class TestGetFormDataFile { + @Test + void shouldReturnFile() { + var inFile = mapper.getFormDataFile(buildInputFormData()); + + assertThat(inFile).get().usingRecursiveComparison().ignoringFields("file").isEqualTo(buildFormDataInFile()); + } + + @Test + void shouldCheckIfFound() { + mapper.getFormDataFile(FormDataTestFactory.create()); + + verify(mapper).logWarnIfEmpty(Optional.empty()); + } + } + + private FormData buildInputFormData() { + return FormDataTestFactory.createBuilder() + .control(FormDataControl.builder() + .representations(Optional.of(Representations.builder().primaryFormDataRepresentation("A12FormData.json").build())).build()) + .representation(buildFormDataInFile()) + .build(); + } + + private IncomingFile buildFormDataInFile() { + return IncomingFileTestFactory.createBuilder() + .contentType("application/json") + .name("A12FormData.json") + .file(TempFileUtils.writeTmpFile(TestUtils.loadFile("A12/a12FormData.json"))) + .build(); + } + + @Nested + class TestReadEingang { + + @Test + void shouldReadJson() { + var file = TempFileUtils.writeTmpFile(TestUtils.loadFile("A12/a12FormData.json")); + + var result = mapper.readEingang(file); + + assertThat(result).isNotNull(); + } + } + +} diff --git a/semantik-adapter/src/test/resources/A12/a12FormData.json b/semantik-adapter/src/test/resources/A12/a12FormData.json new file mode 100644 index 0000000000000000000000000000000000000000..5b93978371da34f8de9145c444e0c9225e2debce --- /dev/null +++ b/semantik-adapter/src/test/resources/A12/a12FormData.json @@ -0,0 +1,16 @@ +{ + "root": { + "Haushaltsjahr": "2024", + "Anlage_2": { + "Finanzierungsplan": { + "Insgesamt_Finanzierungsplan": 117450, + "Eigenmittel_von_Hundert": 1.5, + "Erlaeuterungen_Finanzierungsplan": "Das passt doch schon so.", + "Zuwendung_kommunale": { + "Gemeinden": "Eselstadt", + "Zuwendung_Gemeinden": 100000 + } + } + } + } +} \ No newline at end of file