diff --git a/CHANGELOG.md b/CHANGELOG.md index 95688f4e2..6416104c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C - BPDM Pool: Post endpoint to fetch the BPNL/S/A based on the requested identifiers.([#1052](https://github.com/eclipse-tractusx/bpdm/issues/1052)) - BPDM Gate & Orchestrator: Enhance the error handling mechanism for the orchestrator and gate components by extending the list of available error codes.([#1003](https://github.com/eclipse-tractusx/bpdm/pull/1003#pullrequestreview-2477395867)) - BPDM System Test: Tester module which performs automated end-to-end tests on an existing golden record process.([#1070](https://github.com/eclipse-tractusx/bpdm/pull/1070)) +- BPDM Gate: Add endpoints for managing business partner relations ([#1027](https://github.com/eclipse-tractusx/bpdm/issues/1027)) ### Changed diff --git a/DEPENDENCIES b/DEPENDENCIES index caf0e9738..305bb42ae 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -55,7 +55,7 @@ maven/mavencentral/io.cucumber/cucumber-spring/7.18.1, MIT, approved, clearlydef maven/mavencentral/io.cucumber/datatable/7.18.1, Apache-2.0, approved, #15145 maven/mavencentral/io.cucumber/docstring/7.18.1, MIT, approved, clearlydefined maven/mavencentral/io.cucumber/gherkin/28.0.0, MIT, approved, #14276 -maven/mavencentral/io.cucumber/html-formatter/21.4.1, MIT, restricted, clearlydefined +maven/mavencentral/io.cucumber/html-formatter/21.4.1, Apache-2.0 AND MIT, approved, #17770 maven/mavencentral/io.cucumber/junit-xml-formatter/0.5.0, MIT, approved, clearlydefined maven/mavencentral/io.cucumber/messages/24.1.0, MIT, approved, #14274 maven/mavencentral/io.cucumber/query/12.2.0, MIT, approved, clearlydefined @@ -128,7 +128,7 @@ maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.33, Apache-2.0 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.33, Apache-2.0, approved, #6997 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.33, Apache-2.0, approved, #7920 maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, #17641 -maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #15252 +maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, EPL-1.0, approved, tools.aspectj maven/mavencentral/org.assertj/assertj-core/3.24.2, Apache-2.0, approved, #6161 maven/mavencentral/org.awaitility/awaitility/4.2.2, Apache-2.0, approved, #14178 maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined @@ -150,7 +150,7 @@ maven/mavencentral/org.glassfish.jaxb/jaxb-xjc/4.0.5, BSD-3-Clause, approved, ee maven/mavencentral/org.glassfish.jaxb/txw2/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.glassfish.jaxb/xsom/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined -maven/mavencentral/org.hamcrest/hamcrest/2.2, None, restricted, #17677 +maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, #17677 maven/mavencentral/org.hdrhistogram/HdrHistogram/2.2.2, BSD-2-Clause AND CC0-1.0 AND CC0-1.0, approved, #14828 maven/mavencentral/org.hibernate.common/hibernate-commons-annotations/7.0.3.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/org.hibernate.orm/hibernate-core/6.6.2.Final, (EPL-2.0 OR BSD-3-Clause) AND LGPL-2.1-or-later AND MIT, approved, #17553 @@ -169,8 +169,8 @@ maven/mavencentral/org.jboss.shrinkwrap/shrinkwrap-spi/1.2.6, Apache-2.0, approv maven/mavencentral/org.jboss/jandex/2.4.3.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jetbrains.kotlin/kotlin-reflect/2.1.0, Apache-2.0, approved, #17637 maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.8.0, Apache-2.0, approved, #8910 -maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.0, None, restricted, #17633 -maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.0, None, restricted, #17635 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.0, Apache-2.0, approved, #17633 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.0, Apache-2.0, approved, #17635 maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/2.1.0, Apache-2.0, approved, #17634 maven/mavencentral/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.8.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.8.1, Apache-2.0, approved, clearlydefined @@ -251,7 +251,7 @@ maven/mavencentral/org.testcontainers/jdbc/1.20.4, Apache-2.0, approved, #16621 maven/mavencentral/org.testcontainers/junit-jupiter/1.20.4, MIT, approved, #16552 maven/mavencentral/org.testcontainers/postgresql/1.20.4, MIT, approved, #16627 maven/mavencentral/org.testcontainers/testcontainers/1.20.4, MIT, approved, #15747 -maven/mavencentral/org.webjars/swagger-ui/5.18.2, Apache-2.0 AND (Apache-2.0 AND MIT) AND MIT AND DOC AND LicenseRef-scancode-proprietary-license, restricted, #17636 +maven/mavencentral/org.webjars/swagger-ui/5.18.2, Apache-2.0, approved, #17636 maven/mavencentral/org.webjars/webjars-locator-lite/1.0.1, MIT, approved, clearlydefined maven/mavencentral/org.xmlunit/xmlunit-core/2.10.0, Apache-2.0, approved, #14590 maven/mavencentral/org.yaml/snakeyaml/2.3, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #16046 diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt index ce298af1d..6acab2474 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/containers/KeycloakContextInitializer.kt @@ -21,8 +21,11 @@ package org.eclipse.tractusx.bpdm.test.containers import dasniko.testcontainers.keycloak.KeycloakContainer import org.eclipse.tractusx.bpdm.test.config.SelfClientConfigProperties +import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.TENANT_BPNL import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.keycloakContainer import org.keycloak.representations.idm.ClientRepresentation +import org.keycloak.representations.idm.ProtocolMapperRepresentation +import org.keycloak.representations.idm.UserRepresentation import org.springframework.boot.test.util.TestPropertyValues import org.springframework.context.ApplicationContextInitializer import org.springframework.context.ConfigurableApplicationContext @@ -105,17 +108,42 @@ abstract class CreateNewSelfClientInitializer: SelfClientInitializer(){ val createdClientUuid = clients.findByClientId(clientId).first().id + + val newProtocolMapper = ProtocolMapperRepresentation().apply { + name = "BPN" + protocol = "openid-connect" + protocolMapper = "oidc-usermodel-attribute-mapper" + config = mapOf( + "introspection.token.claim" to "true", + "userinfo.token.claim" to "true", + "user.attribute" to "bpn", + "id.token.claim" to "true", + "access.token.claim" to "true", + "claim.name" to "bpn", + "jsonType.label" to "String" + ) + } + clients + .get(createdClientUuid) + .protocolMappers + .createMapper(newProtocolMapper) + val newServiceAccount = clients .get(createdClientUuid) .serviceAccountUser + newServiceAccount.attributes = mutableMapOf(Pair("bpn", listOf(TENANT_BPNL))) + + realm.users() + .get(newServiceAccount.id) + .update(newServiceAccount) + realm.users() .get(newServiceAccount.id) .roles() .clientLevel(roleManagementClient.toRepresentation().id) .add(listOf(role)) - super.initialize(applicationContext) } diff --git a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateInputFactory.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/GateInputFactory.kt similarity index 96% rename from bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateInputFactory.kt rename to bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/GateInputFactory.kt index de6a6b787..25319b53f 100644 --- a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateInputFactory.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/GateInputFactory.kt @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ -package org.eclipse.tractusx.bpdm.test.system.utils +package org.eclipse.tractusx.bpdm.test.testdata.gate import com.neovisionaries.i18n.CountryCode import org.eclipse.tractusx.bpdm.common.dto.AddressType @@ -30,7 +30,6 @@ import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequ import org.eclipse.tractusx.bpdm.gate.api.model.response.AddressRepresentationInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.SiteRepresentationInputDto -import org.eclipse.tractusx.bpdm.test.system.config.TestRunData import java.time.Duration import java.time.Instant import java.time.ZoneOffset @@ -38,7 +37,7 @@ import kotlin.random.Random class GateInputFactory( private val testMetadata: TestMetadata, - private val testRunData: TestRunData + private val testRunData: TestRunData? ) { val genericFullValidWithSiteWithoutAnyBpn = createAllFieldsFilled("genericFullValidWithSiteWithoutAnyBpn") { it.withoutAnyBpn().withAddressType(null) } @@ -48,7 +47,7 @@ class GateInputFactory( } fun createFullValid(seed: String, externalId: String = seed): BusinessPartnerInputRequest { - return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData.toExternalId(externalId)) + return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData?.toExternalId(externalId) ?: externalId) } inner class SeededTestDataCreator( @@ -62,7 +61,7 @@ class GateInputFactory( fun createAllFieldsFilled(): BusinessPartnerInputRequest{ return BusinessPartnerInputRequest( - externalId = "${seed}_${testRunData.testTime}", + externalId = testRunData?.let { "${seed}_${testRunData.testTime}" } ?: seed , nameParts = listRange.map { "Name Part $seed $it" }, identifiers = emptyList(), roles = BusinessPartnerRole.entries, diff --git a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestRunData.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/TestRunData.kt similarity index 95% rename from bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestRunData.kt rename to bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/TestRunData.kt index 31f8a27da..9adc9fdda 100644 --- a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestRunData.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/TestRunData.kt @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ -package org.eclipse.tractusx.bpdm.test.system.config +package org.eclipse.tractusx.bpdm.test.testdata.gate import java.time.Instant diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/config/CustomJwtAuthenticationConverter.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/config/CustomJwtAuthenticationConverter.kt index bcfba0719..d0d7ea716 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/config/CustomJwtAuthenticationConverter.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/config/CustomJwtAuthenticationConverter.kt @@ -55,12 +55,15 @@ import java.util.stream.Collectors class CustomJwtAuthenticationConverter(private val resourceId: String, private val requiredBpn: String = "") : Converter { private val defaultGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter() override fun convert(source: Jwt): AbstractAuthenticationToken { - val authorities: Collection = - defaultGrantedAuthoritiesConverter.convert(source)!!.plus(extractResourceRoles(source, resourceId, requiredBpn)).toSet() - val bpn = source.claims["bpn"] as String? ?: "" val tokenAttributes = mutableMapOf("bpn" to bpn) + val authorities: Collection = + defaultGrantedAuthoritiesConverter.convert(source)!! + .plus(extractResourceRoles(source, resourceId, requiredBpn)) + .let { if(bpn.isNotBlank()) it.plus(SimpleGrantedAuthority(bpn)) else it } + .toSet() + return JwtAuthenticationToken(source, authorities, tokenAttributes.toString()) } diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/exception/BpdmValidationErrorException.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/exception/BpdmValidationErrorException.kt index ff1e84a41..a1ec929e1 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/exception/BpdmValidationErrorException.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/exception/BpdmValidationErrorException.kt @@ -25,6 +25,6 @@ import org.springframework.web.bind.annotation.ResponseStatus @ResponseStatus(HttpStatus.BAD_REQUEST) -class BpdmValidationErrorException( +open class BpdmValidationErrorException( val validationErrors: List ): RuntimeException(validationErrors.joinToString()) \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt index 85b9e969d..5a8924140 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt @@ -26,7 +26,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import org.eclipse.tractusx.bpdm.gate.api.GateBusinessPartnerApi.Companion.BUSINESS_PARTNER_PATH import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto -import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse +import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse import org.springframework.core.io.ByteArrayResource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity @@ -55,7 +55,7 @@ interface GatePartnerUploadApi { ApiResponse(responseCode = "400", description = "On malformed Business partner upload request", content = [Content( mediaType = "application/json", - schema = Schema(implementation = PartnerUploadErrorResponse::class) + schema = Schema(implementation = GateErrorResponse::class) )]), ] ) diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GateRelationApi.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GateRelationApi.kt new file mode 100644 index 000000000..f7f70f624 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GateRelationApi.kt @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import jakarta.validation.Valid +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.gate.api.GateRelationApi.Companion.RELATIONS_PATH +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest +import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse +import org.springdoc.core.annotations.ParameterObject +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.* +import java.time.Instant + +@RequestMapping(RELATIONS_PATH, produces = [MediaType.APPLICATION_JSON_VALUE]) +interface GateRelationApi { + + companion object{ + const val RELATIONS_PATH = "${ApiCommons.BASE_PATH}/input/business-partner-relations" + } + + @Operation( + summary = "Find business partner input relations", + description = "Find paginated list of business partner relations from the input stage. " + + "There are various filter criteria available. " + + "All filters are 'AND' filters." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "A paginated list of business partner relations for the input stage") + ] + ) + @GetMapping + fun get( + @Schema(description = "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.") + @RequestParam tenantBpnL: String? = null, + @Schema(description = "Only show relations with the given external identifiers") + @RequestParam externalIds: List? = null, + @Schema(description = "Only show relations of the given type") + @RequestParam relationType: RelationType? = null, + @Schema(description = "Only show relations which have the given business partners as sources") + @RequestParam businessPartnerSourceExternalIds: List? = null, + @Schema(description = "Only show relations which have the given business partners as targets") + @RequestParam businessPartnerTargetExternalIds: List? = null, + @Schema(description = "Only show relations which have been modified after the given time stamp") + @RequestParam updatedAtFrom: Instant? = null, + @ParameterObject @Valid paginationRequest: PaginationRequest = PaginationRequest() + ): PageDto + + @Operation( + summary = "Create a new business partner input relation", + description = "Create a new relation between two business partner entries on the input stage. " + + "The external identifier is optional and a new one will be automatically created if not given. " + + "A given external identifier has to be unique." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "201", description = "The created business partner input relation"), + ApiResponse(responseCode = "400", description = "If the business partner could not be created based on wrong or insufficient data provided such as non-existent business partners or violated relation constraints. ", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = GateErrorResponse::class) + )]), + ApiResponse(responseCode = "409", description = "If a relation with the given external identifier already exists", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = GateErrorResponse::class) + )]), + ] + ) + @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE]) + fun post( + @RequestBody requestBody: RelationPostRequest + ): RelationDto + + @Operation( + summary = "Update a business partner input relation", + description = "Update an existing business partner relation on the input stage. " + + "By using a request parameter it is also possible to create a relation if the relation with the given external identifier does not exist. " + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "The given business partner relation has been updated."), + ApiResponse(responseCode = "400", description = "On wrong or insufficient user provided data like references to non-existent business partners or relations that violate the relation constraints. ", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = GateErrorResponse::class) + )]) + ] + ) + @PutMapping(consumes = [MediaType.APPLICATION_JSON_VALUE]) + fun put( + @Schema(description = "If true a business partner relation will be created even if a relation could not be found under the given external identifier.") + @RequestParam createIfNotExist: Boolean = false, + @RequestBody requestBody: RelationPutRequest + ): RelationDto + + @Operation( + summary = "Delete an existing business partner relation", + description = "Delete a relation between two business partners on the input stage." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "The specified relation has been deleted."), + ApiResponse(responseCode = "400", description = "On specifying a relation that does not exist. ", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = GateErrorResponse::class) + )]) + ] + ) + @DeleteMapping + fun delete( + @Schema(description = "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.") + @RequestParam tenantBpnL: String? = null, + @Schema(description = "The external identifier of the business partner relation to delete") + @RequestParam externalId: String + ) + + +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt index 90aee224a..3c975cde0 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt @@ -19,8 +19,6 @@ package org.eclipse.tractusx.bpdm.gate.api.client -import org.eclipse.tractusx.bpdm.gate.api.StatsApi - interface GateClient { val businessParters: BusinessPartnerApiClient @@ -32,4 +30,6 @@ interface GateClient { val stats: StatsApiClient val partnerUpload: PartnerUploadApiClient + + val relation: RelationApiClient } diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt index 2ebf4aad4..25d87b736 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt @@ -51,6 +51,8 @@ class GateClientImpl( override val partnerUpload by lazy { createClient() } + override val relation: RelationApiClient by lazy { createClient() } + private inline fun createClient() = httpServiceProxyFactory.createClient(T::class.java) } diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/RelationApiClient.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/RelationApiClient.kt new file mode 100644 index 000000000..022a3e6fa --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/RelationApiClient.kt @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.client + +import jakarta.validation.Valid +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.gate.api.GateRelationApi +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest +import org.springdoc.core.annotations.ParameterObject +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.service.annotation.* +import java.time.Instant + +@HttpExchange(GateRelationApi.RELATIONS_PATH) +interface RelationApiClient: GateRelationApi { + + @GetExchange + override fun get( + @RequestParam tenantBpnL: String?, + @RequestParam externalIds: List?, + @RequestParam relationType: RelationType?, + @RequestParam businessPartnerSourceExternalIds: List?, + @RequestParam businessPartnerTargetExternalIds: List?, + @RequestParam updatedAtFrom: Instant?, + @ParameterObject @Valid paginationRequest: PaginationRequest + ): PageDto + + @PostExchange + override fun post(@RequestBody requestBody: RelationPostRequest): RelationDto + + @PutExchange + override fun put(@RequestParam createIfNotExist: Boolean, @RequestBody requestBody: RelationPutRequest): RelationDto + + @DeleteExchange + override fun delete( @RequestParam tenantBpnL: String?, @RequestParam externalId: String) +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationDto.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationDto.kt new file mode 100644 index 000000000..58b5cc6ac --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationDto.kt @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model + +interface IRelationDto { + val tenantBpnL: String? + val externalId: String? + val relationType: RelationType? + val businessPartnerSourceExternalId: String? + val businessPartnerTargetExternalId: String? +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationshipLocation.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationshipLocation.kt new file mode 100644 index 000000000..325a5cc06 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IRelationshipLocation.kt @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model + +import org.eclipse.tractusx.bpdm.common.model.StageType + +interface IRelationshipLocation { + val externalId: String? + val tenantBpnL: String? + val stageType: StageType? +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IUpsertRequestParameter.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IUpsertRequestParameter.kt new file mode 100644 index 000000000..48591b4f7 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IUpsertRequestParameter.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model + +interface IUpsertRequestParameter{ + val createIfNotExists: Boolean? +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationDto.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationDto.kt new file mode 100644 index 000000000..b930b018d --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationDto.kt @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model + +import io.swagger.v3.oas.annotations.media.Schema +import java.time.Instant + +@Schema(description = "A relation from one business partner (the source) to another business partner (the target). ") +data class RelationDto( + @Schema(description = "The identifier with which to reference this relation") + override val externalId: String, + @Schema(description = "The type of relation between the business partners") + override val relationType: RelationType, + @Schema(description = "The business partner from which the relation emerges (the source)") + override val businessPartnerSourceExternalId: String, + @Schema(description = "The business partner to which this relation goes (the target)") + override val businessPartnerTargetExternalId: String, + @Schema(description = "The BPNL of the tenant this relation belongs to") + override val tenantBpnL: String?, + @Schema(description = "The time when this relation was last modified") + val updatedAt: Instant, + @Schema(description = "The time when this relation was created") + val createdAt: Instant, +): IRelationDto diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationType.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationType.kt new file mode 100644 index 000000000..972d8e454 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/RelationType.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model + +enum class RelationType { + IsManagedBy +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPostRequest.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPostRequest.kt new file mode 100644 index 000000000..c9a2a2049 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPostRequest.kt @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model.request + +import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.gate.api.model.IRelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType + +@Schema(description = "A request to create a new relation between two business partners") +data class RelationPostRequest( + @Schema(description = "The BPNL of the tenant to post in. If not given, the system attempts to infer the tenant from the authentication.") + override val tenantBpnL: String? = null, + @Schema(description = "The identifier under which the relation will be referenced. " + + "If not given a unique identifier will be automatically assigned by the system. " + + "A given external identifier needs be unique.") + override val externalId: String? = null, + @Schema(description = "The type of relation that should be created") + override val relationType: RelationType, + @Schema(description = "The external identifier of the business partner from which the relation should emerge (the source)") + override val businessPartnerSourceExternalId: String, + @Schema(description = "The external identifier of the business partner to which the relation should point (the target)") + override val businessPartnerTargetExternalId: String +): IRelationDto diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPutRequest.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPutRequest.kt new file mode 100644 index 000000000..7c7a39062 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/RelationPutRequest.kt @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model.request + +import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.gate.api.model.IRelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType + +@Schema(description = "A request to update the content of the given business partner relation") +data class RelationPutRequest( + @Schema(description = "The BPNL of the tenant in which to update. If not given, the system attempts to infer the tenant from the authentication.") + override val tenantBpnL: String? = null, + @Schema(description = "The external identifier of the business partner relation to update") + override val externalId: String, + @Schema(description = "The type the relation should be") + override val relationType: RelationType, + @Schema(description = "The external identifier of the business partner from which the relation should emerge (the source)") + override val businessPartnerSourceExternalId: String, + @Schema(description = "The external identifier of the business partner to which the relation should point (the target)") + override val businessPartnerTargetExternalId: String +): IRelationDto diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/GateErrorResponse.kt similarity index 97% rename from bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt rename to bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/GateErrorResponse.kt index 2e0dbec79..6b26fc594 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/GateErrorResponse.kt @@ -24,7 +24,7 @@ import org.springframework.http.HttpStatus import java.time.Instant @Schema(description = "Error response for invalid partner upload") -class PartnerUploadErrorResponse( +class GateErrorResponse( @Schema(description = "Timestamp of the error occurrence") val timestamp: Instant, @Schema(description = "HTTP status of the error response") diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt index db2bd0dfc..28de54a92 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt @@ -33,7 +33,9 @@ data class PermissionConfigProperties( val readSharingState: String = "read_sharing_state", val writeSharingState: String = "write_sharing_state", val readStats: String = "read_stats", - val uploadInputPartner: String = "upload_input_partner" + val uploadInputPartner: String = "upload_input_partner", + val readInputRelation: String = "read_input_relation", + val writeInputRelation: String = "write_input_relation" ) { companion object { const val PREFIX = "bpdm.security.permissions" @@ -51,5 +53,7 @@ data class PermissionConfigProperties( const val WRITE_SHARING_STATE = "@$BEAN_QUALIFIER.getWriteSharingState()" const val READ_STATS = "@$BEAN_QUALIFIER.getReadStats()" const val UPLOAD_INPUT_PARTNER = "@$BEAN_QUALIFIER.getUploadInputPartner()" + const val READ_INPUT_RELATION = "@$BEAN_QUALIFIER.getReadInputRelation()" + const val WRITE_INPUT_RELATION = "@$BEAN_QUALIFIER.getWriteInputRelation()" } } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationController.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationController.kt new file mode 100644 index 000000000..ccbd9bb5e --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationController.kt @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.controller + +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.common.model.StageType +import org.eclipse.tractusx.bpdm.gate.api.GateRelationApi +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest +import org.eclipse.tractusx.bpdm.gate.config.PermissionConfigProperties +import org.eclipse.tractusx.bpdm.gate.service.RelationService +import org.eclipse.tractusx.bpdm.gate.util.PrincipalUtil +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.RestController +import java.time.Instant + +@RestController +class RelationController( + private val relationshipService: RelationService, + private val principalUtil: PrincipalUtil +): GateRelationApi { + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.READ_INPUT_RELATION}) && hasAuthority(@principalUtil.tryResolve(#tenantBpnL))") + override fun get( + tenantBpnL: String?, + externalIds: List?, + relationType: RelationType?, + businessPartnerSourceExternalIds: List?, + businessPartnerTargetExternalIds: List?, + updatedAtFrom: Instant?, + paginationRequest: PaginationRequest + ): PageDto { + return relationshipService.findRelations( + tenantBpnL = principalUtil.resolveTenantBpnL(tenantBpnL), + stageType = StageType.Input, + externalIds = externalIds ?: emptyList(), + relationType = relationType, + sourceBusinessPartnerExternalIds = businessPartnerSourceExternalIds ?: emptyList(), + targetBusinessPartnerExternalIds = businessPartnerTargetExternalIds ?: emptyList(), + updatedAtFrom = updatedAtFrom, + paginationRequest = paginationRequest + ) + } + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.WRITE_INPUT_RELATION}) && hasAuthority(@principalUtil.tryResolve(#requestBody.getTenantBpnL()))") + override fun post( + requestBody: RelationPostRequest + ): RelationDto { + return relationshipService.createRelation( + tenantBpnL = principalUtil.resolveTenantBpnL(requestBody.tenantBpnL), + stageType = StageType.Input, + externalId = requestBody.externalId, + relationType = requestBody.relationType, + sourceBusinessPartnerExternalId = requestBody.businessPartnerSourceExternalId, + targetBusinessPartnerExternalId = requestBody.businessPartnerTargetExternalId + ) + } + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.WRITE_INPUT_RELATION}) && hasAuthority(@principalUtil.tryResolve(#requestBody.getTenantBpnL()))") + override fun put( + createIfNotExist: Boolean, + requestBody: RelationPutRequest + ): RelationDto { + return if(createIfNotExist){ + relationshipService.upsertRelation( + tenantBpnL = principalUtil.resolveTenantBpnL(requestBody.tenantBpnL), + stageType = StageType.Input, + externalId = requestBody.externalId, + relationType = requestBody.relationType, + sourceBusinessPartnerExternalId = requestBody.businessPartnerSourceExternalId, + targetBusinessPartnerExternalId = requestBody.businessPartnerTargetExternalId + ) + }else{ + relationshipService.updateRelation( + tenantBpnL = principalUtil.resolveTenantBpnL(requestBody.tenantBpnL), + stageType = StageType.Input, + externalId = requestBody.externalId, + relationType = requestBody.relationType, + sourceBusinessPartnerExternalId = requestBody.businessPartnerSourceExternalId, + targetBusinessPartnerExternalId = requestBody.businessPartnerTargetExternalId + ) + } + } + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.WRITE_INPUT_RELATION}) && hasAuthority(@principalUtil.tryResolve(#tenantBpnL))") + override fun delete(tenantBpnL: String?, externalId: String) { + relationshipService.deleteRelation( + tenantBpnL = principalUtil.resolveTenantBpnL(tenantBpnL), + stageType = StageType.Input, + externalId = externalId + ) + } +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/RelationDb.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/RelationDb.kt new file mode 100644 index 000000000..6f6b53a2b --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/RelationDb.kt @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.entity + +import jakarta.persistence.* +import org.eclipse.tractusx.bpdm.common.model.BaseEntity +import org.eclipse.tractusx.bpdm.common.model.StageType +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType + + +@Entity +@Table(name = "business_partner_relations", + uniqueConstraints = [ + UniqueConstraint(name = "uc_business_partner_relations_external_id_stage_tenant", columnNames = ["external_id, stage, tenant_bpnl"]), + UniqueConstraint(name = "uc_business_partner_relations_source_target", columnNames = ["source_sharing_state_id", "target_sharing_state_id"]) + ] +) +class RelationDb( + @Column(name = "external_id", unique = true, nullable = false) + var externalId: String, + @Enumerated(EnumType.STRING) + @Column(name = "relation_type", nullable = false) + var relationType: RelationType, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "source_sharing_state_id", nullable = false) + var source: SharingStateDb, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "target_sharing_state_id", nullable = false) + var target: SharingStateDb, + @Column(name = "tenant_bpnl", nullable = false) + var tenantBpnL: String, + @Column(name = "stage", nullable = false) + @Enumerated(EnumType.STRING) + var stage: StageType, +) : BaseEntity() \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidRelationConstraintsException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidRelationConstraintsException.kt new file mode 100644 index 000000000..0a65fbc62 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidRelationConstraintsException.kt @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +import org.eclipse.tractusx.bpdm.gate.service.IRelationService +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus + +@ResponseStatus(HttpStatus.BAD_REQUEST) +class BpdmInvalidRelationConstraintsException( + val errors: List +) : RuntimeException("The following errors have been discovered when validating business partner relationships: ${errors.joinToString(System.lineSeparator())}"){ + + companion object{ + fun fromConstraintErrors(errors: List) = + BpdmInvalidRelationConstraintsException(errors.map { "Constraint for relation '${it.externalId}' is violated by value '${it.erroneousValue}': ${it.message}"}) + } +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidTenantBpnlException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidTenantBpnlException.kt new file mode 100644 index 000000000..a88e986a3 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidTenantBpnlException.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +class BpdmInvalidTenantBpnlException( + invalidTenantBpnL: String +): RuntimeException("The provided tenant BPNL `$invalidTenantBpnL` is invalid as its not a correct BPNL.") \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingRelationException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingRelationException.kt new file mode 100644 index 000000000..9a9bb5c86 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingRelationException.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +class BpdmMissingRelationException( + val externalId: String +): RuntimeException("Relation '$externalId' can not be found.") \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingSharingStateException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingSharingStateException.kt index e2367e5cb..1360f45c4 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingSharingStateException.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmMissingSharingStateException.kt @@ -19,7 +19,7 @@ package org.eclipse.tractusx.bpdm.gate.exception -class BpdmMissingSharingStateException( +open class BpdmMissingSharingStateException( externalId: String, tenantBpnl: String? ) : RuntimeException("Sharing state with external-id '$externalId' in tenant '$tenantBpnl' is missing.") \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationAlreadyExistsException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationAlreadyExistsException.kt new file mode 100644 index 000000000..aefccf5ef --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationAlreadyExistsException.kt @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus + +@ResponseStatus(HttpStatus.CONFLICT) +class BpdmRelationAlreadyExistsException( + val externalId: String +): RuntimeException("Relation '$externalId' already exists.") \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationSourceNotFoundException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationSourceNotFoundException.kt new file mode 100644 index 000000000..8002ef382 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationSourceNotFoundException.kt @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +class BpdmRelationSourceNotFoundException( + externalId: String, + tenantBpnL: String +) : BpdmMissingSharingStateException(externalId, tenantBpnL) \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationTargetNotFoundException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationTargetNotFoundException.kt new file mode 100644 index 000000000..684c9b3dd --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmRelationTargetNotFoundException.kt @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +class BpdmRelationTargetNotFoundException( + externalId: String, + tenantBpnL: String +) : BpdmMissingSharingStateException(externalId, tenantBpnL) \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmTenantResolutionException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmTenantResolutionException.kt new file mode 100644 index 000000000..fe0600476 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmTenantResolutionException.kt @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus + +@ResponseStatus(HttpStatus.BAD_REQUEST) +class BpdmTenantResolutionException :RuntimeException("Could not determine tenant from request. Try providing it manually.") \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt index 36c3dadfc..aa157740b 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt @@ -20,7 +20,7 @@ package org.eclipse.tractusx.bpdm.gate.exception import org.eclipse.tractusx.bpdm.common.exception.BpdmExceptionHandler -import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse +import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.ControllerAdvice @@ -32,13 +32,51 @@ import java.time.Instant class GateExceptionHandler : BpdmExceptionHandler() { @ExceptionHandler(BpdmInvalidPartnerUploadException::class) - fun handleInvalidPartnerUploadException(ex:BpdmInvalidPartnerUploadException, request: WebRequest): ResponseEntity { - val errorResponse = PartnerUploadErrorResponse( + fun handleInvalidPartnerUploadException(ex:BpdmInvalidPartnerUploadException, request: WebRequest): ResponseEntity { + val errorResponse = createResponse(ex.errors, request) + return ResponseEntity.status(errorResponse.status).body(errorResponse) + } + + @ExceptionHandler(BpdmInvalidRelationConstraintsException::class) + fun handleInvalidRelationConstraintsException(ex: BpdmInvalidRelationConstraintsException, request: WebRequest): ResponseEntity { + val errorResponse = createResponse(ex.errors, request) + return ResponseEntity.status(errorResponse.status).body(errorResponse) + } + + @ExceptionHandler(BpdmRelationAlreadyExistsException::class) + fun handleMissingRelationshipException(ex: BpdmRelationAlreadyExistsException, request: WebRequest): ResponseEntity { + val errorResponse = createResponse(ex.message ?: "", request, HttpStatus.CONFLICT) + return ResponseEntity.status(errorResponse.status).body(errorResponse) + } + + @ExceptionHandler( + BpdmRelationTargetNotFoundException::class, + BpdmRelationTargetNotFoundException::class, + BpdmRelationSourceNotFoundException::class, + BpdmMissingSharingStateException::class, + BpdmTenantResolutionException::class, + BpdmMissingRelationException::class, + BpdmInvalidTenantBpnlException::class + ) + fun handleGenericBadRequestException(ex: RuntimeException, request: WebRequest): ResponseEntity{ + val errorResponse = createResponse(ex, request) + return ResponseEntity.status(errorResponse.status).body(errorResponse) + } + + private fun createResponse(exception: RuntimeException, request: WebRequest, status: HttpStatus = HttpStatus.BAD_REQUEST): GateErrorResponse{ + return createResponse(listOf(exception.message ?: ""), request, status) + } + + private fun createResponse(error: String, request: WebRequest, status: HttpStatus = HttpStatus.BAD_REQUEST): GateErrorResponse{ + return createResponse(listOf(error), request, status) + } + + private fun createResponse(errors: List, request: WebRequest, status: HttpStatus = HttpStatus.BAD_REQUEST): GateErrorResponse{ + return GateErrorResponse( timestamp = Instant.now(), - status = HttpStatus.BAD_REQUEST, - error = ex.errors, + status = status, + error = errors, path = request.getDescription(false) ) - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse) } } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/RelationRepository.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/RelationRepository.kt new file mode 100644 index 000000000..4dc0e9477 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/RelationRepository.kt @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.repository + +import org.eclipse.tractusx.bpdm.common.model.StageType +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.entity.RelationDb +import org.eclipse.tractusx.bpdm.gate.entity.SharingStateDb +import org.springframework.data.jpa.domain.Specification +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.JpaSpecificationExecutor +import java.time.Instant + +interface RelationRepository: JpaRepository, JpaSpecificationExecutor { + object Specs { + fun byExternalIds(externalIds: Collection?) = + Specification { root, _, _ -> + externalIds?.takeIf { it.isNotEmpty() }?.let { + root.get(RelationDb::externalId.name).`in`(externalIds) + } + } + + fun byRelationshipType(relationType: RelationType?) = + Specification { root, _, builder -> + relationType?.let { + builder.equal(root.get(RelationDb::relationType.name), relationType) + } + } + + fun bySourceExternalIds(sourceBusinessPartnerExternalIds: List?) = + Specification { root, _, _ -> + sourceBusinessPartnerExternalIds?.takeIf { it.isNotEmpty() }?.let { + root + .get(RelationDb::source.name) + .get(SharingStateDb::externalId.name) + .`in`(sourceBusinessPartnerExternalIds) + } + } + + fun byTargetExternalIds(targetBusinessPartnerExternalIds: List?) = + Specification { root, _, _ -> + targetBusinessPartnerExternalIds?.takeIf { it.isNotEmpty() }?.let { + root + .get(RelationDb::target.name) + .get(SharingStateDb::externalId.name) + .`in`(targetBusinessPartnerExternalIds) + } + } + + fun byUpdatedAfter(updatedAfter: Instant?) = + Specification { root, _, builder -> + updatedAfter?.let { + builder.greaterThan(root.get(RelationDb::updatedAt.name), updatedAfter) + } + } + + fun byStage(stageType: StageType?) = + Specification { root, _, builder -> + stageType?.let { + builder.equal(root.get(RelationDb::stage.name), stageType) + } + } + + fun byTenantBpnL(tenantBpnL: String?) = + Specification { root, _, builder -> + tenantBpnL?.let { + builder.equal(root.get(RelationDb::tenantBpnL.name), tenantBpnL) + } + } + } + + fun findByTenantBpnLAndStageAndExternalId(tenantBpnL: String, stageType: StageType, externalId: String): RelationDb? + fun findByRelationTypeAndSource(relationType: RelationType, source: SharingStateDb): Set + fun findByRelationTypeAndTarget(relationType: RelationType, target: SharingStateDb): Set + fun findBySourceInAndStage(sources: Set, stageType: StageType): Set + fun findByTargetInAndStage(sources: Set, stageType: StageType): Set +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SharingStateRepository.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SharingStateRepository.kt index fb5bbce22..73e1bf901 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SharingStateRepository.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/repository/SharingStateRepository.kt @@ -55,10 +55,10 @@ interface SharingStateRepository : PagingAndSortingRepository, tenantBpnl: String?): Collection + fun findByExternalIdAndTenantBpnl(externalId: String, tenantBpnl: String?): Collection - fun findBySharingStateType(sharingStateType: SharingStateType, pageable: Pageable): Page - fun findBySharingStateTypeAndTaskIdNotNull(sharingStateType: SharingStateType, pageable: Pageable): Page + fun findBySharingStateType(sharingStateType: SharingStateType, pageable: Pageable): Page @Query("SELECT s.sharingStateType as type, COUNT(s.sharingStateType) as count FROM SharingStateDb AS s GROUP BY s.sharingStateType") fun countSharingStateTypes(): List diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt index 3a13c2aba..75d566d54 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt @@ -21,6 +21,7 @@ package org.eclipse.tractusx.bpdm.gate.service import mu.KotlinLogging import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.mapping.types.BpnLString import org.eclipse.tractusx.bpdm.common.model.StageType import org.eclipse.tractusx.bpdm.common.service.toPageDto import org.eclipse.tractusx.bpdm.gate.api.model.ChangelogType @@ -32,6 +33,7 @@ import org.eclipse.tractusx.bpdm.gate.entity.ChangelogEntryDb import org.eclipse.tractusx.bpdm.gate.entity.SharingStateDb import org.eclipse.tractusx.bpdm.gate.entity.generic.BusinessPartnerDb import org.eclipse.tractusx.bpdm.gate.exception.BpdmInvalidPartnerException +import org.eclipse.tractusx.bpdm.gate.exception.BpdmInvalidRelationConstraintsException import org.eclipse.tractusx.bpdm.gate.model.upsert.output.OutputUpsertData import org.eclipse.tractusx.bpdm.gate.repository.ChangelogRepository import org.eclipse.tractusx.bpdm.gate.repository.generic.BusinessPartnerRepository @@ -51,7 +53,8 @@ class BusinessPartnerService( private val changelogRepository: ChangelogRepository, private val copyUtil: BusinessPartnerCopyUtil, private val compareUtil: BusinessPartnerComparisonUtil, - private val outputUpsertMappings: OutputUpsertMappings + private val outputUpsertMappings: OutputUpsertMappings, + private val relationshipService: RelationService ) { private val logger = KotlinLogging.logger { } @@ -89,6 +92,11 @@ class BusinessPartnerService( ?.businessPartner } + if(tenantBpnl != null) + relationshipService.checkConstraints(BpnLString(tenantBpnl), StageType.Input, updatedEntities.map { it.sharingState.externalId }) + .takeIf { it.isNotEmpty() } + ?.let { throw BpdmInvalidRelationConstraintsException.fromConstraintErrors(it) } + return updatedEntities.map(businessPartnerMappings::toBusinessPartnerInputDto) } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationConstraintProvider.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationConstraintProvider.kt new file mode 100644 index 000000000..e9506b13c --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationConstraintProvider.kt @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.service + +import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerType +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType + +interface IRelationConstraintProvider { + + fun getConstraints(relationType: RelationType): RelationTypeConstraints + + data class RelationTypeConstraints( + val selfRelateAllowed: Boolean, + val sourceMustBeOwnCompanyData: Boolean, + val targetMustBeOwnCompanyData: Boolean, + val sourceAllowedTypes: Set, + val targetAllowedTypes: Set, + val sourceCanBeTarget: Boolean, + val multipleSourcesAllowed: Boolean + ) +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationService.kt new file mode 100644 index 000000000..9412a9b7f --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/IRelationService.kt @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.service + +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.common.mapping.types.BpnLString +import org.eclipse.tractusx.bpdm.common.model.StageType +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import java.time.Instant + +interface IRelationService { + + fun findRelations( + tenantBpnL: BpnLString, + stageType: StageType, + externalIds: List = emptyList(), + relationType: RelationType? = null, + sourceBusinessPartnerExternalIds: List = emptyList(), + targetBusinessPartnerExternalIds: List = emptyList(), + updatedAtFrom: Instant? = null, + paginationRequest: PaginationRequest + ): PageDto + + fun createRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String?, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto + + fun upsertRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto + + fun updateRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto + + fun deleteRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String + ) + + fun checkConstraints( + tenantBpnL: BpnLString, + stageType: StageType, + businessPartnerExternalIds: List + ): List + + data class ConstraintError( + val externalId: String, + val errorType: ConstraintErrorType, + val erroneousValue: String + ){ + val message = errorType.msg + } + + enum class ConstraintErrorType(val msg: String){ + SourceEqualsTarget("Source and target are the same"), + SourceNotOwnCompanyData("Source is not own company data"), + TargetNotOwnCompanyData("Target is not own company data"), + SourceNotAllowedType("Source is not of allowed business partner type"), + TargetNotAllowedType("Target is not of allowed business partner type"), + SourceUsedAsTarget("Source is already used as target of relationship type"), + TargetUsedAsSource("Target is already used as source of relationship type"), + TargetMultipleSources("Target has multiple sources") + } +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationConstraintProvider.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationConstraintProvider.kt new file mode 100644 index 000000000..f36917af3 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationConstraintProvider.kt @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.service + +import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerType +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.springframework.stereotype.Service + +@Service +class RelationConstraintProvider: IRelationConstraintProvider { + + private val isManagedByConstraints = IRelationConstraintProvider.RelationTypeConstraints( + selfRelateAllowed = false, + sourceMustBeOwnCompanyData = true, + targetMustBeOwnCompanyData = true, + sourceAllowedTypes = setOf(BusinessPartnerType.LEGAL_ENTITY), + targetAllowedTypes = setOf(BusinessPartnerType.LEGAL_ENTITY), + sourceCanBeTarget = false, + multipleSourcesAllowed = false + ) + + override fun getConstraints(relationType: RelationType): IRelationConstraintProvider.RelationTypeConstraints { + return when(relationType){ + RelationType.IsManagedBy -> isManagedByConstraints + } + } + + +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationService.kt new file mode 100644 index 000000000..5e5c3feab --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/RelationService.kt @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.service + +import jakarta.transaction.Transactional +import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerType +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.common.mapping.types.BpnLString +import org.eclipse.tractusx.bpdm.common.model.StageType +import org.eclipse.tractusx.bpdm.common.service.toPageDto +import org.eclipse.tractusx.bpdm.common.service.toPageRequest +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest +import org.eclipse.tractusx.bpdm.gate.entity.RelationDb +import org.eclipse.tractusx.bpdm.gate.exception.* +import org.eclipse.tractusx.bpdm.gate.repository.RelationRepository +import org.eclipse.tractusx.bpdm.gate.repository.SharingStateRepository +import org.eclipse.tractusx.bpdm.gate.repository.generic.BusinessPartnerRepository +import org.eclipse.tractusx.bpdm.gate.service.IRelationService.ConstraintErrorType.* +import org.springframework.data.jpa.domain.Specification +import org.springframework.stereotype.Service +import java.time.Instant +import java.util.* + +@Service +class RelationService( + private val relationRepository: RelationRepository, + private val sharingStateRepository: SharingStateRepository, + private val businessPartnerRepository: BusinessPartnerRepository, + private val constraintProvider: IRelationConstraintProvider +): IRelationService { + + override fun findRelations( + tenantBpnL: BpnLString, + stageType: StageType, + externalIds: List, + relationType: RelationType?, + sourceBusinessPartnerExternalIds: List, + targetBusinessPartnerExternalIds: List, + updatedAtFrom: Instant?, + paginationRequest: PaginationRequest + ): PageDto{ + + val querySpecs = Specification.allOf( + RelationRepository.Specs.byTenantBpnL(tenantBpnL.value), + RelationRepository.Specs.byStage(stageType), + RelationRepository.Specs.byExternalIds(externalIds), + RelationRepository.Specs.byRelationshipType(relationType), + RelationRepository.Specs.bySourceExternalIds(sourceBusinessPartnerExternalIds), + RelationRepository.Specs.byTargetExternalIds(targetBusinessPartnerExternalIds), + RelationRepository.Specs.byUpdatedAfter(updatedAtFrom) + ) + + return relationRepository.findAll(querySpecs, paginationRequest.toPageRequest()) + .toPageDto { toDto(it) } + } + + @Transactional + override fun createRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String?, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto { + if(externalId != null) relationRepository.findByTenantBpnLAndStageAndExternalId(tenantBpnL.value, stageType, externalId)?.run { throw BpdmRelationAlreadyExistsException(externalId) } + + val source = sharingStateRepository.findByExternalIdAndTenantBpnl(sourceBusinessPartnerExternalId, tenantBpnL.value).singleOrNull() + ?: throw BpdmRelationSourceNotFoundException(sourceBusinessPartnerExternalId, tenantBpnL.value) + val target = sharingStateRepository.findByExternalIdAndTenantBpnl(targetBusinessPartnerExternalId, tenantBpnL.value).singleOrNull() + ?: throw BpdmRelationTargetNotFoundException(targetBusinessPartnerExternalId, tenantBpnL.value) + + val newRelationship = RelationDb( + externalId = externalId ?: UUID.randomUUID().toString(), + relationType = relationType, + stage = StageType.Input, + source = source, + target = target, + tenantBpnL = tenantBpnL.value, + ) + + assertValid(newRelationship) + + relationRepository.save(newRelationship) + + return toDto(newRelationship) + } + + @Transactional + override fun upsertRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto { + val existingRelationship = relationRepository.findByTenantBpnLAndStageAndExternalId(tenantBpnL.value, stageType, externalId) + return if(existingRelationship == null){ + createRelation( + tenantBpnL = tenantBpnL, + stageType = stageType, + externalId = externalId, + relationType = relationType, + sourceBusinessPartnerExternalId = sourceBusinessPartnerExternalId, + targetBusinessPartnerExternalId = targetBusinessPartnerExternalId + ) + }else{ + updateRelationship( + relationship = existingRelationship, + relationType = relationType, + sourceBusinessPartnerExternalId = sourceBusinessPartnerExternalId, + targetBusinessPartnerExternalId = targetBusinessPartnerExternalId + ) + } + } + + @Transactional + override fun updateRelation( + tenantBpnL: BpnLString, + stageType: StageType, + externalId: String, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto { + val relationshipToUpdate = findRelationshipOrThrow(tenantBpnL = tenantBpnL.value, stageType = stageType, externalId = externalId) + return updateRelationship( + relationship = relationshipToUpdate, + relationType = relationType, + sourceBusinessPartnerExternalId = sourceBusinessPartnerExternalId, + targetBusinessPartnerExternalId = targetBusinessPartnerExternalId + ) + } + + private fun updateRelationship( + relationship: RelationDb, + relationType: RelationType, + sourceBusinessPartnerExternalId: String, + targetBusinessPartnerExternalId: String + ): RelationDto { + val source = sharingStateRepository.findByExternalIdAndTenantBpnl(sourceBusinessPartnerExternalId, relationship.tenantBpnL).singleOrNull() ?: throw BpdmRelationSourceNotFoundException(sourceBusinessPartnerExternalId, relationship.tenantBpnL) + val target = sharingStateRepository.findByExternalIdAndTenantBpnl(targetBusinessPartnerExternalId, relationship.tenantBpnL).singleOrNull() ?: throw BpdmRelationTargetNotFoundException(targetBusinessPartnerExternalId, relationship.tenantBpnL) + RelationPutRequest( + externalId = relationship.externalId, + relationType = relationType.also { relationship.relationType = it }, + businessPartnerSourceExternalId = sourceBusinessPartnerExternalId.also { relationship.source = source }, + businessPartnerTargetExternalId = targetBusinessPartnerExternalId.also { relationship.target = target }, + tenantBpnL = relationship.tenantBpnL + ) + + assertValid(relationship) + + relationRepository.save(relationship) + + return toDto(relationship) + } + + @Transactional + override fun deleteRelation(tenantBpnL: BpnLString, stageType: StageType, externalId: String) { + val relationshipToDelete = findRelationshipOrThrow(tenantBpnL = tenantBpnL.value, stageType = stageType, externalId = externalId) + relationRepository.delete(relationshipToDelete) + } + + override fun checkConstraints( + tenantBpnL: BpnLString, + stageType: StageType, + businessPartnerExternalIds: List + ): List { + val sharingStates = sharingStateRepository.findByExternalIdInAndTenantBpnl(businessPartnerExternalIds, tenantBpnL.value).toSet() + val sources = relationRepository.findBySourceInAndStage(sharingStates, stageType) + val targets = relationRepository.findByTargetInAndStage(sharingStates, stageType) + + val allRelationships = sources.union(targets) + return allRelationships.flatMap { check(it) } + } + + private fun toDto(entity: RelationDb): RelationDto{ + return RelationDto( + externalId = entity.externalId, + relationType = entity.relationType, + businessPartnerSourceExternalId = entity.source.externalId, + businessPartnerTargetExternalId = entity.target.externalId, + updatedAt = entity.updatedAt, + createdAt = entity.createdAt, + tenantBpnL = entity.tenantBpnL + ) + } + + private fun assertValid(relationship: RelationDb){ + check(relationship).let { constraintErrors -> + if (constraintErrors.isNotEmpty()) + throw BpdmInvalidRelationConstraintsException.fromConstraintErrors(constraintErrors) + } + } + + private fun check(relationship: RelationDb): List { + val validationErrors = mutableListOf () + val externalId = relationship.externalId + + val constraints = constraintProvider.getConstraints(relationship.relationType) + + if(!constraints.selfRelateAllowed){ + if(relationship.source == relationship.target) validationErrors.add(SourceEqualsTarget.toError(externalId, relationship.source.externalId)) + } + + val sourceInput = businessPartnerRepository.findBySharingStateInAndStage(listOf(relationship.source), StageType.Input).single() + val targetInput = businessPartnerRepository.findBySharingStateInAndStage(listOf(relationship.target), StageType.Input).single() + + + if(!constraints.sourceCanBeTarget) + { + val sourceUsedAsTarget = relationRepository.findByRelationTypeAndTarget(relationship.relationType, relationship.source) + .minusElement(relationship).isNotEmpty() + if(sourceUsedAsTarget) validationErrors.add(SourceUsedAsTarget.toError(externalId, relationship.source.externalId)) + + val targetUsedAsSource = relationRepository.findByRelationTypeAndSource(relationship.relationType, relationship.target) + .minusElement(relationship).isNotEmpty() + if(targetUsedAsSource) validationErrors.add(TargetUsedAsSource.toError(externalId, relationship.target.externalId)) + } + + if(!constraints.multipleSourcesAllowed){ + val targetHasMultipleSources = relationRepository.findByRelationTypeAndTarget(relationship.relationType, relationship.target) + .minusElement(relationship).isNotEmpty() + if(targetHasMultipleSources) validationErrors.add(TargetMultipleSources.toError(externalId, relationship.target.externalId)) + } + + if(constraints.sourceMustBeOwnCompanyData){ + if(!sourceInput.isOwnCompanyData) validationErrors.add(SourceNotOwnCompanyData.toError(externalId, relationship.source.externalId)) + } + + if(constraints.targetMustBeOwnCompanyData){ + if(!targetInput.isOwnCompanyData) validationErrors.add(TargetNotOwnCompanyData.toError(externalId, relationship.target.externalId)) + } + + val sourceBusinessPartnerTypes = sourceInput.postalAddress.addressType?.businessPartnerTypes ?: listOf(BusinessPartnerType.GENERIC) + if(sourceBusinessPartnerTypes.intersect(constraints.sourceAllowedTypes).isEmpty()) + validationErrors.add(SourceNotAllowedType.toError(externalId, relationship.source.externalId)) + + val targetBusinessPartnerTypes = targetInput.postalAddress.addressType?.businessPartnerTypes ?: listOf(BusinessPartnerType.GENERIC) + if(targetBusinessPartnerTypes.intersect(constraints.targetAllowedTypes).isEmpty()) + validationErrors.add(TargetNotAllowedType.toError(externalId, relationship.target.externalId)) + + return validationErrors + } + + private fun findRelationshipOrThrow( + tenantBpnL: String, + stageType: StageType, + externalId: String + ): RelationDb{ + return relationRepository.findByTenantBpnLAndStageAndExternalId( + tenantBpnL = tenantBpnL, + stageType = stageType, + externalId = externalId + ) ?: throw BpdmMissingRelationException(externalId) + } + + fun IRelationService.ConstraintErrorType.toError(externalId: String, erroneousValue: String) = + IRelationService.ConstraintError(externalId, this, erroneousValue) +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PrincipalUtil.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PrincipalUtil.kt new file mode 100644 index 000000000..a38f655c7 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PrincipalUtil.kt @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.util + +import org.eclipse.tractusx.bpdm.common.mapping.types.BpnLString +import org.eclipse.tractusx.bpdm.gate.config.BpnConfigProperties +import org.eclipse.tractusx.bpdm.gate.exception.BpdmInvalidTenantBpnlException +import org.eclipse.tractusx.bpdm.gate.exception.BpdmTenantResolutionException +import org.springframework.stereotype.Component + +@Component +class PrincipalUtil( + private val bpnConfigProperties: BpnConfigProperties +) { + + fun tryResolve(tenantBpnL: String?): String?{ + return tenantBpnL ?: getTenantBnlFromContext() + } + + fun resolveTenantBpnL(tenantBpnL: String?): BpnLString{ + return BpnLString.map(resolveToString(tenantBpnL)).successfulResultOrNull + ?: throw BpdmInvalidTenantBpnlException(tenantBpnL!!) + } + + fun getTenantBnlFromContext(): String? { + return getCurrentUserBpn() ?: bpnConfigProperties.ownerBpnL.takeIf { it.isNotBlank() } + } + + private fun resolveToString(tenantBpnL: String?): String{ + return tenantBpnL ?: getTenantBnlFromContext() ?: throw BpdmTenantResolutionException() + } + +} + diff --git a/bpdm-gate/src/main/resources/application.yml b/bpdm-gate/src/main/resources/application.yml index 8010bcff2..69d067265 100644 --- a/bpdm-gate/src/main/resources/application.yml +++ b/bpdm-gate/src/main/resources/application.yml @@ -133,6 +133,10 @@ bpdm: read_stats: read_stats # Name of the permission to upload business partner entries for business partner input data uploadInputPartner: write_input_partner + # Name of the permission to read business partner relations + readInputRelation: read_input_partner + # Name of the permission to update or create business partner relations + writeInputRelation: write_input_partner datasource: # Host name of the used datasource host: localhost diff --git a/bpdm-gate/src/main/resources/db/migration/V6_3_0_0__add_relationship.sql b/bpdm-gate/src/main/resources/db/migration/V6_3_0_0__add_relationship.sql new file mode 100644 index 000000000..28d3622cd --- /dev/null +++ b/bpdm-gate/src/main/resources/db/migration/V6_3_0_0__add_relationship.sql @@ -0,0 +1,16 @@ +CREATE TABLE business_partner_relations +( + id BIGINT NOT NULL, + uuid UUID NOT NULL, + external_id VARCHAR(255) NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + relation_type VARCHAR(255) NOT NULL, + stage VARCHAR(255) NOT NULL, + tenant_bpnl VARCHAR(255) NOT NULL, + source_sharing_state_id BIGINT NOT NULL REFERENCES sharing_states (id), + target_sharing_state_id BIGINT NOT NULL REFERENCES sharing_states (id), + CONSTRAINT pk_business_partner_relationships PRIMARY KEY (id), + UNIQUE(source_sharing_state_id, target_sharing_state_id), + UNIQUE(external_id, stage, tenant_bpnl) +); \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt index be90c5c28..6c02bd595 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt @@ -65,6 +65,12 @@ class AuthAdminIT @Autowired constructor( uploadPartner = UploadPartnerAuthExpections( postInput = AuthExpectationType.Authorized, getInputTemplate = AuthExpectationType.Authorized + ), + relation = RelationAuthExpectations( + get = AuthExpectationType.Authorized, + post = AuthExpectationType.Authorized, + put = AuthExpectationType.Authorized, + delete = AuthExpectationType.Authorized, ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt index c6154d8f0..db5e309cf 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt @@ -65,6 +65,12 @@ class AuthInputConsumerIT @Autowired constructor( uploadPartner = UploadPartnerAuthExpections( postInput = AuthExpectationType.Forbidden, getInputTemplate = AuthExpectationType.Forbidden + ), + relation = RelationAuthExpectations( + get = AuthExpectationType.Authorized, + post = AuthExpectationType.Forbidden, + put = AuthExpectationType.Forbidden, + delete = AuthExpectationType.Forbidden, ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt index e90f77fb4..f0f180416 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt @@ -65,6 +65,12 @@ class AuthInputManagerIT @Autowired constructor( uploadPartner = UploadPartnerAuthExpections( postInput = AuthExpectationType.Authorized, getInputTemplate = AuthExpectationType.Authorized + ), + relation = RelationAuthExpectations( + get = AuthExpectationType.Authorized, + post = AuthExpectationType.Authorized, + put = AuthExpectationType.Authorized, + delete = AuthExpectationType.Authorized, ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt index fe9eb2f45..32f99da5e 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt @@ -65,6 +65,12 @@ class AuthOutputConsumerIT @Autowired constructor( uploadPartner = UploadPartnerAuthExpections( postInput = AuthExpectationType.Forbidden, getInputTemplate = AuthExpectationType.Forbidden + ), + relation = RelationAuthExpectations( + get = AuthExpectationType.Forbidden, + post = AuthExpectationType.Forbidden, + put = AuthExpectationType.Forbidden, + delete = AuthExpectationType.Forbidden, ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt index daea091c6..9dd764e1c 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt @@ -22,9 +22,8 @@ package org.eclipse.tractusx.bpdm.gate.auth import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest import org.eclipse.tractusx.bpdm.common.model.StageType import org.eclipse.tractusx.bpdm.gate.api.client.GateClient -import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequest -import org.eclipse.tractusx.bpdm.gate.api.model.request.ChangelogSearchRequest -import org.eclipse.tractusx.bpdm.gate.api.model.request.PostSharingStateReadyRequest +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.* import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType import org.junit.jupiter.api.Test @@ -106,6 +105,26 @@ abstract class AuthTestBase( authAssertions.assert(authExpectations.uploadPartner.getInputTemplate) { gateClient.partnerUpload.getPartnerCsvTemplate() } } + @Test + fun `GET Relations`() { + authAssertions.assert(authExpectations.relation.get) { gateClient.relation.get() } + } + + @Test + fun `POST Relations`() { + authAssertions.assert(authExpectations.relation.post) { gateClient.relation.post(RelationPostRequest(null, null, RelationType.IsManagedBy, "", "")) } + } + + @Test + fun `PUT Relations`() { + authAssertions.assert(authExpectations.relation.put) { gateClient.relation.put(true, RelationPutRequest(null, "", RelationType.IsManagedBy, "", "")) } + } + + @Test + fun `DELETE Relations`() { + authAssertions.assert(authExpectations.relation.delete) { gateClient.relation.delete(null, "") } + } + } data class GateAuthExpectations( @@ -113,7 +132,8 @@ data class GateAuthExpectations( val changelog: ChangelogAuthExpectations, val sharingState: SharingStateAuthExpectations, val stats: StatsAuthExpectations, - val uploadPartner: UploadPartnerAuthExpections + val uploadPartner: UploadPartnerAuthExpections, + val relation: RelationAuthExpectations ) data class BusinessPartnerAuthExpectations( @@ -143,3 +163,10 @@ data class UploadPartnerAuthExpections( val postInput: AuthExpectationType, val getInputTemplate: AuthExpectationType ) + +data class RelationAuthExpectations( + val get: AuthExpectationType, + val post: AuthExpectationType, + val put: AuthExpectationType, + val delete: AuthExpectationType +) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt index 9ff91faa2..016151579 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt @@ -63,6 +63,12 @@ class NoAuthIT @Autowired constructor( uploadPartner = UploadPartnerAuthExpections( postInput = AuthExpectationType.Unauthorized, getInputTemplate = AuthExpectationType.Unauthorized + ), + relation = RelationAuthExpectations( + get = AuthExpectationType.Unauthorized, + post = AuthExpectationType.Unauthorized, + put = AuthExpectationType.Unauthorized, + delete = AuthExpectationType.Unauthorized, ) ) ) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/config/TestDataConfiguration.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/config/TestDataConfiguration.kt new file mode 100644 index 000000000..ee20862b3 --- /dev/null +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/config/TestDataConfiguration.kt @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.config + +import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient +import org.eclipse.tractusx.bpdm.test.testdata.gate.GateInputFactory +import org.eclipse.tractusx.bpdm.test.testdata.gate.TestMetadata +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class TestDataConfiguration { + + @Bean + fun testMetadata(poolClient: PoolApiClient): TestMetadata { + val testMetadata = TestMetadata( + identifierTypes = listOf("EU_VAT_ID_DE", "DUNS_ID"), + legalForms = listOf("SCE1", "SGST"), + adminAreas = listOf("DE-BW", "DE-BY") + ) + + return testMetadata + } + + @Bean + fun gateTestDataFactory(testMetadata: TestMetadata): GateInputFactory { + return GateInputFactory(testMetadata, null) + } + +} \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationControllerIT.kt new file mode 100644 index 000000000..b29d8a65a --- /dev/null +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/RelationControllerIT.kt @@ -0,0 +1,2251 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.controller + +import org.assertj.core.api.Assertions +import org.eclipse.tractusx.bpdm.common.dto.AddressType +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.gate.api.client.GateClient +import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto +import org.eclipse.tractusx.bpdm.gate.api.model.RelationType +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest +import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest +import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer +import org.eclipse.tractusx.bpdm.test.testdata.gate.GateInputFactory +import org.eclipse.tractusx.bpdm.test.testdata.gate.withAddressType +import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers +import org.eclipse.tractusx.bpdm.test.util.Timeframe +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInfo +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.web.reactive.function.client.WebClientResponseException.* +import java.time.Instant + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(initializers = [ + PostgreSQLContextInitializer::class, + KeyCloakInitializer::class, + SelfClientAsPartnerUploaderInitializer::class +]) +class RelationControllerIT @Autowired constructor( + private val gateClient: GateClient, + private val testHelpers: DbTestHelpers, + private val inputFactory: GateInputFactory +) { + + val tenantBpnl = "BPNL00000003CRHK" + var testName: String = "" + + @BeforeEach + fun beforeEach(testInfo: TestInfo) { + testHelpers.truncateDbTables() + testName = testInfo.displayName + } + + @Test + fun postRelation(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + val beforePost = Instant.now() + val response = gateClient.relation.post( + RelationPostRequest( + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = "ANY", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost)) + } + + @Test + fun postRelationWithLegalAndSiteMainAddresses(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.LegalAndSiteMainAddress), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.LegalAndSiteMainAddress) + )) + + val beforePost = Instant.now() + val response = gateClient.relation.post( + RelationPostRequest( + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = "ANY", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost)) + } + + @Test + fun postRelationWithTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + val beforePost = Instant.now() + val response = gateClient.relation.post( + RelationPostRequest( + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = "ANY", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = true) + } + + @Test + fun postRelationWithExternalId(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + val relationId = testName + val beforePost = Instant.now() + val response = gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = false) + } + + @Test + fun postRelationWithMultipleTargets(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput( listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3) + )) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + val beforePost = Instant.now() + val response = gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = false) + } + + @Test + fun postRelationWithWrongTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(Forbidden::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + tenantBpnL = "WRONG Tenant", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithSourceNotOwnCompanyData(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).copy(isOwnCompanyData = false), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithTargetNotOwnCompanyData(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).copy(isOwnCompanyData = false) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithSourceSite(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.SiteMainAddress), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithTargetSite(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.SiteMainAddress) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithSourceAdditionalAddress(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.AdditionalAddress), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationWithTargetAdditionalAddress(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.AdditionalAddress) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationDuplicateId(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(Conflict::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + } + } + + @Test + fun postRelationWithSourceEqualsTarget(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun postRelationWithSourceNotExist(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = "NOT EXIST", + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun postRelationWithTargetNotExist(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = "NOT EXIST" + ) + ) + } + } + + @Test + fun postRelationMultipleSources(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun postRelationTargetIsAlreadySource(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun postRelationSourceIsAlreadyTarget(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId2, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + } + } + + @Test + fun putUpdateRelation(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterCreation = Instant.now() + + val beforeUpdate = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + val afterUpdate = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation( + actual = response, + expectation = expectation, + updateTimeframe = Timeframe(beforeUpdate, afterUpdate), + createTimeframe = Timeframe(beforeCreation, afterCreation), + ignoreExternalId = false) + } + + @Test + fun putUpdateRelationWithLegalAndSiteMainAddress(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.LegalAndSiteMainAddress), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.LegalAndSiteMainAddress), + createLegalEntityRequest(legalEntityId3).withAddressType(AddressType.LegalAndSiteMainAddress), + createLegalEntityRequest(legalEntityId4).withAddressType(AddressType.LegalAndSiteMainAddress) + )) + + val beforeCreation = Instant.now() + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterCreation = Instant.now() + + val beforeUpdate = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + val afterUpdate = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation( + actual = response, + expectation = expectation, + updateTimeframe = Timeframe(beforeUpdate, afterUpdate), + createTimeframe = Timeframe(beforeCreation, afterCreation), + ignoreExternalId = false) + } + + @Test + fun putUpdateRelationWithTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterCreation = Instant.now() + + val beforeUpdate = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + tenantBpnL = tenantBpnl, + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + val afterUpdate = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation( + actual = response, + expectation = expectation, + updateTimeframe = Timeframe(beforeUpdate, afterUpdate), + createTimeframe = Timeframe(beforeCreation, afterCreation), + ignoreExternalId = false) + } + + @Test + fun putUpdateRelationWithMultipleTargets(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + val afterCreation = Instant.now() + + val beforeUpdate = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + val afterUpdate = Instant.now() + + val expectation = RelationDto( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation( + actual = response, + expectation = expectation, + updateTimeframe = Timeframe(beforeUpdate, afterUpdate), + createTimeframe = Timeframe(beforeCreation, afterCreation), + ignoreExternalId = false) + } + + @Test + fun putUpdateRelationIdNotExist(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun putUpdateRelationWithWrongTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(Forbidden::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + tenantBpnL = "WRONG TENANT", + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + } + } + + @Test + fun putUpdateRelationWithSourceEqualsTarget(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + } + } + + @Test + fun putUpdateRelationWithSourceNotExist(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = "NOT EXISTS", + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + } + } + + @Test + fun putUpdateRelationWithTargetNotExist(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = "NOT EXISTS" + ) + ) + } + } + + @Test + fun putUpdateRelationMultipleSources(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpdateRelationTargetIsAlreadySource(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun putUpdateRelationSourceIsAlreadyTarget(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = false, + RelationPutRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId2, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + } + } + + @Test + fun putUpsertRelation(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + val beforePost = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = false) + } + + @Test + fun putUpsertRelationWithTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + val beforePost = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + tenantBpnL = tenantBpnl, + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = false) + } + + @Test + fun putUpsertRelationWithMultipleTargets(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val relationId1 = "$testName Relation 1" + val relationId2 = "$testName Relation 2" + + gateClient.businessParters.upsertBusinessPartnersInput( listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3) + )) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + val beforePost = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = relationId2, + tenantBpnL = tenantBpnl, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + val afterPost = Instant.now() + + val expectation = RelationDto( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation(response, expectation, Timeframe(beforePost, afterPost), ignoreExternalId = false) + } + + @Test + fun putUpsertRelationWithSameId(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + val beforeCreate = Instant.now() + gateClient.relation.post( + RelationPostRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + val afterCreate = Instant.now() + + val beforePut = Instant.now() + val response = gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + val afterPut = Instant.now() + + val expectation = RelationDto( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId4, + tenantBpnL = tenantBpnl, + updatedAt = Instant.now(), + createdAt = Instant.now() + ) + + assertRelation( + actual = response, + expectation = expectation, + updateTimeframe = Timeframe(beforePut, afterPut), + createTimeframe = Timeframe(beforeCreate, afterCreate), + ignoreExternalId = false + ) + } + + @Test + fun putUpsertRelationWithSourceNotOwnCompanyData(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).copy(isOwnCompanyData = false), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithTargetNotOwnCompanyData(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).copy(isOwnCompanyData = false) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithSourceSite(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.SiteMainAddress), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithTargetSite(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.SiteMainAddress) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithSourceAdditionalAddress(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1).withAddressType(AddressType.AdditionalAddress), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithTargetAdditionalAddress(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2).withAddressType(AddressType.AdditionalAddress) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithWrongTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + + val legalEntities = listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + ) + gateClient.businessParters.upsertBusinessPartnersInput(legalEntities) + + Assertions.assertThatExceptionOfType(Forbidden::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + tenantBpnL = "WRONG Tenant", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationWithSourceEqualsTarget(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun putUpsertRelationWithSourceNotExist(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = "NOT EXIST", + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun putUpsertRelationWithTargetNotExist(){ + val legalEntityId1 = "$testName LE 1" + gateClient.businessParters.upsertBusinessPartnersInput( + listOf( + createLegalEntityRequest(legalEntityId1) + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = testName, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = "NOT EXIST" + ) + ) + } + } + + @Test + fun putUpsertRelationMultipleSources(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + } + } + + @Test + fun putUpsertRelationTargetIsAlreadySource(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId3, + businessPartnerTargetExternalId = legalEntityId1 + ) + ) + } + } + + @Test + fun putUpsertRelationSourceIsAlreadyTarget(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + )) + gateClient.relation.post( + RelationPostRequest( + externalId = "$testName Relation 1", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.put( + createIfNotExist = true, + RelationPutRequest( + externalId = "$testName Relation 2", + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId2, + businessPartnerTargetExternalId = legalEntityId3 + ) + ) + } + } + + @Test + fun deleteRelation(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + gateClient.relation.delete(externalId = relationId) + + val response = gateClient.relation.get(externalIds = listOf(relationId)) + Assertions.assertThat(response.content).isEmpty() + } + + @Test + fun deleteRelationWithTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + gateClient.relation.delete(tenantBpnL = tenantBpnl, externalId = relationId) + + val response = gateClient.relation.get(externalIds = listOf(relationId)) + Assertions.assertThat(response.content).isEmpty() + } + + @Test + fun deleteRelationWithWrongTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(Forbidden::class.java).isThrownBy { + gateClient.relation.delete(tenantBpnL = "WRONG TENANT", externalId = relationId) + } + } + + @Test + fun deleteRelationWithIdNotExists(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val relationId = "$testName Relation" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + Assertions.assertThatExceptionOfType(BadRequest::class.java).isThrownBy { + gateClient.relation.delete(externalId = "NOT EXISTS") + } + } + + @Test + fun getRelations(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + + val response = gateClient.relation.get() + + val expectation = PageDto(3, 1, 0, 3, postResponses) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaExternalId(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get(externalIds = listOf(relationId1, relationId2)) + + val expectation = PageDto(2, 1, 0, 2, postResponses.filter { it.externalId in listOf(relationId1, relationId2) }) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaRelationType(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get(relationType = RelationType.IsManagedBy) + + val expectation = PageDto(3, 1, 0, 3, postResponses) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaSource(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val legalEntityId5 = "$testName LE 5" + val legalEntityId6 = "$testName LE 6" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + val relationId4 = "$testName Relation4" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4), + createLegalEntityRequest(legalEntityId5), + createLegalEntityRequest(legalEntityId6), + )) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId4, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId5, + businessPartnerTargetExternalId = legalEntityId6 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get(businessPartnerSourceExternalIds = listOf(legalEntityId1, legalEntityId2)) + + val expectation = PageDto(3, 1, 0, 3, postResponses.filter { it.businessPartnerSourceExternalId == legalEntityId1 }) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaTarget(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get(businessPartnerTargetExternalIds = listOf(legalEntityId2, legalEntityId3)) + + val expectation = PageDto(2, 1, 0, 2, postResponses.filter { it.businessPartnerTargetExternalId in listOf(legalEntityId2, legalEntityId3) }) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaUpdatedAtFrom(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get(updatedAtFrom = beforeCreation) + + val expectation = PageDto(2, 1, 0, 2, postResponses) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsViaAllFilters(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + + val beforeCreation = Instant.now() + val postResponses = listOf( + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ), + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + ) + val afterCreation = Instant.now() + + val response = gateClient.relation.get( + tenantBpnL = tenantBpnl, + externalIds = listOf(relationId3), + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalIds = listOf(legalEntityId1), + businessPartnerTargetExternalIds = listOf(legalEntityId4), + updatedAtFrom = beforeCreation + ) + + val expectation = PageDto(1, 1, 0, 1, postResponses.filter { it.externalId == relationId3 }) + + assertRelationPage(response, expectation, Timeframe(beforeCreation, afterCreation)) + } + + @Test + fun getRelationsWrongTenant(){ + val legalEntityId1 = "$testName LE 1" + val legalEntityId2 = "$testName LE 2" + val legalEntityId3 = "$testName LE 3" + val legalEntityId4 = "$testName LE 4" + val relationId1 = "$testName Relation1" + val relationId2 = "$testName Relation2" + val relationId3 = "$testName Relation3" + + gateClient.businessParters.upsertBusinessPartnersInput(listOf( + createLegalEntityRequest(legalEntityId1), + createLegalEntityRequest(legalEntityId2), + createLegalEntityRequest(legalEntityId3), + createLegalEntityRequest(legalEntityId4) + )) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId1, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId2 + ) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId2, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId3) + ) + gateClient.relation.post( + RelationPostRequest( + externalId = relationId3, + relationType = RelationType.IsManagedBy, + businessPartnerSourceExternalId = legalEntityId1, + businessPartnerTargetExternalId = legalEntityId4 + ) + ) + + Assertions.assertThatExceptionOfType(Forbidden::class.java).isThrownBy{ + gateClient.relation.get(tenantBpnL = "WRONG TENANT") + } + } + + + private fun assertRelationPage( + actual: PageDto, + expectation: PageDto, + updateTimeframe: Timeframe, + createTimeframe: Timeframe = updateTimeframe + ){ + Assertions + .assertThat(actual) + .usingRecursiveComparison() + .ignoringFields(PageDto::content.name) + .isEqualTo(expectation) + + Assertions.assertThat(actual.content.size).isEqualTo(expectation.content.size) + actual.content.sortedBy { it.externalId }.zip(expectation.content.sortedBy { it.externalId }).forEach { (actualEntry, expectationEntry) -> + assertRelation( + actual = actualEntry, + expectation = expectationEntry, + updateTimeframe = updateTimeframe, + createTimeframe = createTimeframe, ignoreExternalId = false) + } + + + } + + + private fun assertRelation( + actual: RelationDto, + expectation: RelationDto, + updateTimeframe: Timeframe, + createTimeframe: Timeframe = updateTimeframe, + ignoreExternalId: Boolean = true + ){ + Assertions + .assertThat(actual) + .usingRecursiveComparison() + .ignoringFields( + RelationDto::externalId.name, + RelationDto::updatedAt.name, + RelationDto::createdAt.name) + .isEqualTo(expectation) + + Assertions.assertThat(actual.externalId).isNotBlank() + if(!ignoreExternalId) Assertions.assertThat(actual.externalId).isEqualTo(expectation.externalId) + Assertions.assertThat(actual.createdAt).isBetween(createTimeframe.startTime, createTimeframe.endTime) + Assertions.assertThat(actual.updatedAt).isBetween(updateTimeframe.startTime, updateTimeframe.endTime) + } + + private fun createLegalEntityRequest(externalId: String) = + inputFactory.createAllFieldsFilled(externalId).request + .withAddressType(AddressType.LegalAddress) + .copy(isOwnCompanyData = true) +} \ No newline at end of file diff --git a/bpdm-system-tester/pom.xml b/bpdm-system-tester/pom.xml index 5a1f44742..08d497bdb 100644 --- a/bpdm-system-tester/pom.xml +++ b/bpdm-system-tester/pom.xml @@ -68,7 +68,7 @@ ${project.groupId} bpdm-common-test - test + compile org.springframework.boot diff --git a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestDataConfiguration.kt b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestDataConfiguration.kt index 889a5a32a..c6762d045 100644 --- a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestDataConfiguration.kt +++ b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/config/TestDataConfiguration.kt @@ -21,10 +21,11 @@ package org.eclipse.tractusx.bpdm.test.system.config import org.eclipse.tractusx.bpdm.gate.api.client.GateClient import org.eclipse.tractusx.bpdm.pool.api.client.PoolApiClient -import org.eclipse.tractusx.bpdm.test.system.utils.GateInputFactory import org.eclipse.tractusx.bpdm.test.system.utils.GateOutputFactory import org.eclipse.tractusx.bpdm.test.system.utils.StepUtils -import org.eclipse.tractusx.bpdm.test.system.utils.TestMetadata +import org.eclipse.tractusx.bpdm.test.testdata.gate.GateInputFactory +import org.eclipse.tractusx.bpdm.test.testdata.gate.TestMetadata +import org.eclipse.tractusx.bpdm.test.testdata.gate.TestRunData import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import java.time.Instant diff --git a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/stepdefinations/ShareGenericNoBpnStepDefs.kt b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/stepdefinations/ShareGenericNoBpnStepDefs.kt index 418399da7..dca6d4025 100644 --- a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/stepdefinations/ShareGenericNoBpnStepDefs.kt +++ b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/stepdefinations/ShareGenericNoBpnStepDefs.kt @@ -26,8 +26,13 @@ import org.assertj.core.api.Assertions.assertThat import org.eclipse.tractusx.bpdm.common.dto.AddressType import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest import org.eclipse.tractusx.bpdm.gate.api.client.GateClient -import org.eclipse.tractusx.bpdm.test.system.config.TestRunData -import org.eclipse.tractusx.bpdm.test.system.utils.* +import org.eclipse.tractusx.bpdm.test.system.utils.GateOutputFactory +import org.eclipse.tractusx.bpdm.test.system.utils.StepUtils +import org.eclipse.tractusx.bpdm.test.system.utils.withAddressType +import org.eclipse.tractusx.bpdm.test.testdata.gate.GateInputFactory +import org.eclipse.tractusx.bpdm.test.testdata.gate.TestRunData +import org.eclipse.tractusx.bpdm.test.testdata.gate.withAddressType +import org.eclipse.tractusx.bpdm.test.testdata.gate.withoutAnyBpn class ShareGenericNoBpnStepDefs( private val gateInputDataFactory: GateInputFactory, diff --git a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateOutputFactory.kt b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateOutputFactory.kt index 1908fb42b..569794235 100644 --- a/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateOutputFactory.kt +++ b/bpdm-system-tester/src/main/kotlin/org/eclipse/tractusx/bpdm/test/system/utils/GateOutputFactory.kt @@ -26,6 +26,7 @@ import org.eclipse.tractusx.bpdm.gate.api.model.response.AddressComponentOutputD import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerOutputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationOutputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.SiteRepresentationOutputDto +import org.eclipse.tractusx.bpdm.test.testdata.gate.GateInputFactory import java.time.Instant import java.time.LocalDateTime import java.time.temporal.ChronoUnit diff --git a/docs/api/gate.json b/docs/api/gate.json index a30da08e3..ebc9097ce 100644 --- a/docs/api/gate.json +++ b/docs/api/gate.json @@ -61,6 +61,274 @@ } } }, + "/v6/input/business-partner-relations": { + "get": { + "tags": [ + "relation-controller" + ], + "summary": "Find business partner input relations", + "description": "Find paginated list of business partner relations from the input stage. There are various filter criteria available. All filters are 'AND' filters.", + "operationId": "get", + "parameters": [ + { + "name": "tenantBpnL", + "in": "query", + "description": "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.", + "required": false, + "schema": { + "type": "string", + "description": "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication." + } + }, + { + "name": "externalIds", + "in": "query", + "description": "Only show relations with the given external identifiers", + "required": false, + "schema": { + "type": "array", + "description": "Only show relations with the given external identifiers", + "items": { + "type": "string", + "description": "Only show relations with the given external identifiers" + } + } + }, + { + "name": "relationType", + "in": "query", + "description": "Only show relations of the given type", + "required": false, + "schema": { + "type": "string", + "description": "Only show relations of the given type", + "enum": [ + "IsManagedBy" + ] + } + }, + { + "name": "businessPartnerSourceExternalIds", + "in": "query", + "description": "Only show relations which have the given business partners as sources", + "required": false, + "schema": { + "type": "array", + "description": "Only show relations which have the given business partners as sources", + "items": { + "type": "string", + "description": "Only show relations which have the given business partners as sources" + } + } + }, + { + "name": "businessPartnerTargetExternalIds", + "in": "query", + "description": "Only show relations which have the given business partners as targets", + "required": false, + "schema": { + "type": "array", + "description": "Only show relations which have the given business partners as targets", + "items": { + "type": "string", + "description": "Only show relations which have the given business partners as targets" + } + } + }, + { + "name": "updatedAtFrom", + "in": "query", + "description": "Only show relations which have been modified after the given time stamp", + "required": false, + "schema": { + "type": "string", + "description": "Only show relations which have been modified after the given time stamp", + "format": "date-time" + } + }, + { + "name": "page", + "in": "query", + "description": "Number of page to get results from", + "required": false, + "schema": { + "minimum": 0, + "type": "string" + } + }, + { + "name": "size", + "in": "query", + "description": "Size of each page", + "required": false, + "schema": { + "maximum": 100, + "minimum": 0, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "A paginated list of business partner relations for the input stage", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PageDtoRelationDto" + } + } + } + } + } + }, + "put": { + "tags": [ + "relation-controller" + ], + "summary": "Update a business partner input relation", + "description": "Update an existing business partner relation on the input stage. By using a request parameter it is also possible to create a relation if the relation with the given external identifier does not exist. ", + "operationId": "put", + "parameters": [ + { + "name": "createIfNotExist", + "in": "query", + "description": "If true a business partner relation will be created even if a relation could not be found under the given external identifier.", + "required": false, + "schema": { + "type": "boolean", + "description": "If true a business partner relation will be created even if a relation could not be found under the given external identifier." + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationPutRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The given business partner relation has been updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationDto" + } + } + } + }, + "400": { + "description": "On wrong or insufficient user provided data like references to non-existent business partners or relations that violate the relation constraints. ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GateErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "relation-controller" + ], + "summary": "Create a new business partner input relation", + "description": "Create a new relation between two business partner entries on the input stage. The external identifier is optional and a new one will be automatically created if not given. A given external identifier has to be unique.", + "operationId": "post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationPostRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "The created business partner input relation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationDto" + } + } + } + }, + "400": { + "description": "If the business partner could not be created based on wrong or insufficient data provided such as non-existent business partners or violated relation constraints. ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GateErrorResponse" + } + } + } + }, + "409": { + "description": "If a relation with the given external identifier already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GateErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": [ + "relation-controller" + ], + "summary": "Delete an existing business partner relation", + "description": "Delete a relation between two business partners on the input stage.", + "operationId": "delete", + "parameters": [ + { + "name": "tenantBpnL", + "in": "query", + "description": "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.", + "required": false, + "schema": { + "type": "string", + "description": "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication." + } + }, + { + "name": "externalId", + "in": "query", + "description": "The external identifier of the business partner relation to delete", + "required": true, + "schema": { + "type": "string", + "description": "The external identifier of the business partner relation to delete" + } + } + ], + "responses": { + "200": { + "description": "The specified relation has been deleted." + }, + "400": { + "description": "On specifying a relation that does not exist. ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GateErrorResponse" + } + } + } + } + } + } + }, "/v6/sharing-state/ready": { "post": { "tags": [ @@ -105,8 +373,7 @@ "required": false, "schema": { "minimum": 0, - "type": "string", - "default": "0" + "type": "string" } }, { @@ -117,8 +384,7 @@ "schema": { "maximum": 100, "minimum": 0, - "type": "string", - "default": "10" + "type": "string" } } ], @@ -165,8 +431,7 @@ "required": false, "schema": { "minimum": 0, - "type": "string", - "default": "0" + "type": "string" } }, { @@ -177,8 +442,7 @@ "schema": { "maximum": 100, "minimum": 0, - "type": "string", - "default": "10" + "type": "string" } } ], @@ -264,7 +528,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PartnerUploadErrorResponse" + "$ref": "#/components/schemas/GateErrorResponse" } } } @@ -288,8 +552,7 @@ "required": false, "schema": { "minimum": 0, - "type": "string", - "default": "0" + "type": "string" } }, { @@ -300,8 +563,7 @@ "schema": { "maximum": 100, "minimum": 0, - "type": "string", - "default": "10" + "type": "string" } } ], @@ -348,8 +610,7 @@ "required": false, "schema": { "minimum": 0, - "type": "string", - "default": "0" + "type": "string" } }, { @@ -360,8 +621,7 @@ "schema": { "maximum": 100, "minimum": 0, - "type": "string", - "default": "10" + "type": "string" } } ], @@ -503,8 +763,7 @@ "required": false, "schema": { "minimum": 0, - "type": "string", - "default": "0" + "type": "string" } }, { @@ -515,8 +774,7 @@ "schema": { "maximum": 100, "minimum": 0, - "type": "string", - "default": "10" + "type": "string" } }, { @@ -1358,6 +1616,110 @@ }, "description": "Holds information about failures" }, + "GateErrorResponse": { + "required": [ + "error", + "path", + "status", + "timestamp" + ], + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp of the error occurrence", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "HTTP status of the error response", + "enum": [ + "100 CONTINUE", + "101 SWITCHING_PROTOCOLS", + "102 PROCESSING", + "103 EARLY_HINTS", + "103 CHECKPOINT", + "200 OK", + "201 CREATED", + "202 ACCEPTED", + "203 NON_AUTHORITATIVE_INFORMATION", + "204 NO_CONTENT", + "205 RESET_CONTENT", + "206 PARTIAL_CONTENT", + "207 MULTI_STATUS", + "208 ALREADY_REPORTED", + "226 IM_USED", + "300 MULTIPLE_CHOICES", + "301 MOVED_PERMANENTLY", + "302 FOUND", + "302 MOVED_TEMPORARILY", + "303 SEE_OTHER", + "304 NOT_MODIFIED", + "305 USE_PROXY", + "307 TEMPORARY_REDIRECT", + "308 PERMANENT_REDIRECT", + "400 BAD_REQUEST", + "401 UNAUTHORIZED", + "402 PAYMENT_REQUIRED", + "403 FORBIDDEN", + "404 NOT_FOUND", + "405 METHOD_NOT_ALLOWED", + "406 NOT_ACCEPTABLE", + "407 PROXY_AUTHENTICATION_REQUIRED", + "408 REQUEST_TIMEOUT", + "409 CONFLICT", + "410 GONE", + "411 LENGTH_REQUIRED", + "412 PRECONDITION_FAILED", + "413 PAYLOAD_TOO_LARGE", + "413 REQUEST_ENTITY_TOO_LARGE", + "414 URI_TOO_LONG", + "414 REQUEST_URI_TOO_LONG", + "415 UNSUPPORTED_MEDIA_TYPE", + "416 REQUESTED_RANGE_NOT_SATISFIABLE", + "417 EXPECTATION_FAILED", + "418 I_AM_A_TEAPOT", + "419 INSUFFICIENT_SPACE_ON_RESOURCE", + "420 METHOD_FAILURE", + "421 DESTINATION_LOCKED", + "422 UNPROCESSABLE_ENTITY", + "423 LOCKED", + "424 FAILED_DEPENDENCY", + "425 TOO_EARLY", + "426 UPGRADE_REQUIRED", + "428 PRECONDITION_REQUIRED", + "429 TOO_MANY_REQUESTS", + "431 REQUEST_HEADER_FIELDS_TOO_LARGE", + "451 UNAVAILABLE_FOR_LEGAL_REASONS", + "500 INTERNAL_SERVER_ERROR", + "501 NOT_IMPLEMENTED", + "502 BAD_GATEWAY", + "503 SERVICE_UNAVAILABLE", + "504 GATEWAY_TIMEOUT", + "505 HTTP_VERSION_NOT_SUPPORTED", + "506 VARIANT_ALSO_NEGOTIATES", + "507 INSUFFICIENT_STORAGE", + "508 LOOP_DETECTED", + "509 BANDWIDTH_LIMIT_EXCEEDED", + "510 NOT_EXTENDED", + "511 NETWORK_AUTHENTICATION_REQUIRED" + ] + }, + "error": { + "type": "array", + "description": "List of error messages", + "items": { + "type": "string", + "description": "List of error messages" + } + }, + "path": { + "type": "string", + "description": "Request path where the error occurred" + } + }, + "description": "Error response for invalid partner upload" + }, "GeoCoordinateDto": { "required": [ "latitude", @@ -1585,7 +1947,7 @@ }, "description": "Paginated collection of results" }, - "PageDtoSharingStateDto": { + "PageDtoRelationDto": { "required": [ "content", "contentSize", @@ -1619,115 +1981,51 @@ "type": "array", "description": "Collection of results in the page", "items": { - "$ref": "#/components/schemas/SharingStateDto" + "$ref": "#/components/schemas/RelationDto" } } }, "description": "Paginated collection of results" }, - "PartnerUploadErrorResponse": { + "PageDtoSharingStateDto": { "required": [ - "error", - "path", - "status", - "timestamp" + "content", + "contentSize", + "page", + "totalElements", + "totalPages" ], "type": "object", "properties": { - "timestamp": { - "type": "string", - "description": "Timestamp of the error occurrence", - "format": "date-time" + "totalElements": { + "type": "integer", + "description": "Total number of all results in all pages", + "format": "int64" }, - "status": { - "type": "string", - "description": "HTTP status of the error response", - "enum": [ - "100 CONTINUE", - "101 SWITCHING_PROTOCOLS", - "102 PROCESSING", - "103 EARLY_HINTS", - "103 CHECKPOINT", - "200 OK", - "201 CREATED", - "202 ACCEPTED", - "203 NON_AUTHORITATIVE_INFORMATION", - "204 NO_CONTENT", - "205 RESET_CONTENT", - "206 PARTIAL_CONTENT", - "207 MULTI_STATUS", - "208 ALREADY_REPORTED", - "226 IM_USED", - "300 MULTIPLE_CHOICES", - "301 MOVED_PERMANENTLY", - "302 FOUND", - "302 MOVED_TEMPORARILY", - "303 SEE_OTHER", - "304 NOT_MODIFIED", - "305 USE_PROXY", - "307 TEMPORARY_REDIRECT", - "308 PERMANENT_REDIRECT", - "400 BAD_REQUEST", - "401 UNAUTHORIZED", - "402 PAYMENT_REQUIRED", - "403 FORBIDDEN", - "404 NOT_FOUND", - "405 METHOD_NOT_ALLOWED", - "406 NOT_ACCEPTABLE", - "407 PROXY_AUTHENTICATION_REQUIRED", - "408 REQUEST_TIMEOUT", - "409 CONFLICT", - "410 GONE", - "411 LENGTH_REQUIRED", - "412 PRECONDITION_FAILED", - "413 PAYLOAD_TOO_LARGE", - "413 REQUEST_ENTITY_TOO_LARGE", - "414 URI_TOO_LONG", - "414 REQUEST_URI_TOO_LONG", - "415 UNSUPPORTED_MEDIA_TYPE", - "416 REQUESTED_RANGE_NOT_SATISFIABLE", - "417 EXPECTATION_FAILED", - "418 I_AM_A_TEAPOT", - "419 INSUFFICIENT_SPACE_ON_RESOURCE", - "420 METHOD_FAILURE", - "421 DESTINATION_LOCKED", - "422 UNPROCESSABLE_ENTITY", - "423 LOCKED", - "424 FAILED_DEPENDENCY", - "425 TOO_EARLY", - "426 UPGRADE_REQUIRED", - "428 PRECONDITION_REQUIRED", - "429 TOO_MANY_REQUESTS", - "431 REQUEST_HEADER_FIELDS_TOO_LARGE", - "451 UNAVAILABLE_FOR_LEGAL_REASONS", - "500 INTERNAL_SERVER_ERROR", - "501 NOT_IMPLEMENTED", - "502 BAD_GATEWAY", - "503 SERVICE_UNAVAILABLE", - "504 GATEWAY_TIMEOUT", - "505 HTTP_VERSION_NOT_SUPPORTED", - "506 VARIANT_ALSO_NEGOTIATES", - "507 INSUFFICIENT_STORAGE", - "508 LOOP_DETECTED", - "509 BANDWIDTH_LIMIT_EXCEEDED", - "510 NOT_EXTENDED", - "511 NETWORK_AUTHENTICATION_REQUIRED" - ] + "totalPages": { + "type": "integer", + "description": "Total number pages", + "format": "int32" }, - "error": { + "page": { + "type": "integer", + "description": "Current page number", + "format": "int32" + }, + "contentSize": { + "type": "integer", + "description": "Number of results in the page", + "format": "int32" + }, + "content": { "type": "array", - "description": "List of error messages", + "description": "Collection of results in the page", "items": { - "type": "string", - "description": "List of error messages" + "$ref": "#/components/schemas/SharingStateDto" } - }, - "path": { - "type": "string", - "description": "Request path where the error occurred" } }, - "description": "Error response for invalid partner upload" + "description": "Paginated collection of results" }, "PhysicalPostalAddressDto": { "type": "object", @@ -2082,6 +2380,122 @@ }, "description": "Request for setting business partners into ready to be shared to golden record state" }, + "RelationDto": { + "required": [ + "businessPartnerSourceExternalId", + "businessPartnerTargetExternalId", + "createdAt", + "externalId", + "relationType", + "updatedAt" + ], + "type": "object", + "properties": { + "externalId": { + "type": "string", + "description": "The identifier with which to reference this relation" + }, + "relationType": { + "type": "string", + "description": "The type of relation between the business partners", + "enum": [ + "IsManagedBy" + ] + }, + "businessPartnerSourceExternalId": { + "type": "string", + "description": "The business partner from which the relation emerges (the source)" + }, + "businessPartnerTargetExternalId": { + "type": "string", + "description": "The business partner to which this relation goes (the target)" + }, + "tenantBpnL": { + "type": "string", + "description": "The BPNL of the tenant this relation belongs to" + }, + "updatedAt": { + "type": "string", + "description": "The time when this relation was last modified", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "description": "The time when this relation was created", + "format": "date-time" + } + }, + "description": "A relation from one business partner (the source) to another business partner (the target). " + }, + "RelationPostRequest": { + "required": [ + "businessPartnerSourceExternalId", + "businessPartnerTargetExternalId", + "relationType" + ], + "type": "object", + "properties": { + "tenantBpnL": { + "type": "string", + "description": "The BPNL of the tenant to post in. If not given, the system attempts to infer the tenant from the authentication." + }, + "externalId": { + "type": "string", + "description": "The identifier under which the relation will be referenced. If not given a unique identifier will be automatically assigned by the system. A given external identifier needs be unique." + }, + "relationType": { + "type": "string", + "description": "The type of relation that should be created", + "enum": [ + "IsManagedBy" + ] + }, + "businessPartnerSourceExternalId": { + "type": "string", + "description": "The external identifier of the business partner from which the relation should emerge (the source)" + }, + "businessPartnerTargetExternalId": { + "type": "string", + "description": "The external identifier of the business partner to which the relation should point (the target)" + } + }, + "description": "A request to create a new relation between two business partners" + }, + "RelationPutRequest": { + "required": [ + "businessPartnerSourceExternalId", + "businessPartnerTargetExternalId", + "externalId", + "relationType" + ], + "type": "object", + "properties": { + "tenantBpnL": { + "type": "string", + "description": "The BPNL of the tenant in which to update. If not given, the system attempts to infer the tenant from the authentication." + }, + "externalId": { + "type": "string", + "description": "The external identifier of the business partner relation to update" + }, + "relationType": { + "type": "string", + "description": "The type the relation should be", + "enum": [ + "IsManagedBy" + ] + }, + "businessPartnerSourceExternalId": { + "type": "string", + "description": "The external identifier of the business partner from which the relation should emerge (the source)" + }, + "businessPartnerTargetExternalId": { + "type": "string", + "description": "The external identifier of the business partner to which the relation should point (the target)" + } + }, + "description": "A request to update the content of the given business partner relation" + }, "SharingStateDto": { "required": [ "externalId", @@ -2106,12 +2520,18 @@ }, "sharingErrorCode": { "type": "string", - "description": "One of the sharing error codes in case the current sharing state type is \"error\".", + "description": "One of the sharing error codes in case the current sharing state type is \"error\". \n* `NaturalPersonError`: The provided record contains natural person information.\n* `BpnErrorNotFound`: The provided record can not be matched to a legal entity or an address.\n* `BpnErrorTooManyOptions`: The provided record can not link to a clear legal entity.\n* `MandatoryFieldValidationFailed`: The provided record does not fulfill mandatory validation rules.\n* `BlacklistCountryPresent`: The provided record is part of a country that is not allowed to be processed by the GR process (example: Brazil).\n* `UnknownSpecialCharacters`: The provided record contains unallowed special characters.", "enum": [ "SharingProcessError", "SharingTimeout", "BpnNotInPool", - "MissingTaskID" + "MissingTaskID", + "NaturalPersonError", + "BpnErrorNotFound", + "BpnErrorTooManyOptions", + "MandatoryFieldValidationFailed", + "BlacklistCountryPresent", + "UnknownSpecialCharacters" ] }, "sharingErrorMessage": { @@ -2128,7 +2548,7 @@ "description": "The orchestrator task identifier that was created" } }, - "description": "A sharing state entry shows the progress in the sharing process and is updated each time the progress for a business partner changes. The business partner is identified by a combination of external ID and business partner type." + "description": "A sharing state entry shows the progress in the sharing process and is updated each time the progress for a business partner changes. The business partner is identified by a combination of external ID and business partner type.\n" }, "SiteRepresentationInputDto": { "required": [ diff --git a/docs/api/gate.yaml b/docs/api/gate.yaml index f69dbd344..c5ee4383f 100644 --- a/docs/api/gate.yaml +++ b/docs/api/gate.yaml @@ -36,6 +36,184 @@ paths: $ref: '#/components/schemas/BusinessPartnerInputDto' '400': description: On malformed legal entity request + /v6/input/business-partner-relations: + get: + tags: + - relation-controller + summary: Find business partner input relations + description: Find paginated list of business partner relations from the input stage. There are various filter criteria available. All filters are 'AND' filters. + operationId: get + parameters: + - name: tenantBpnL + in: query + description: The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication. + required: false + schema: + type: string + description: The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication. + - name: externalIds + in: query + description: Only show relations with the given external identifiers + required: false + schema: + type: array + description: Only show relations with the given external identifiers + items: + type: string + description: Only show relations with the given external identifiers + - name: relationType + in: query + description: Only show relations of the given type + required: false + schema: + type: string + description: Only show relations of the given type + enum: + - IsManagedBy + - name: businessPartnerSourceExternalIds + in: query + description: Only show relations which have the given business partners as sources + required: false + schema: + type: array + description: Only show relations which have the given business partners as sources + items: + type: string + description: Only show relations which have the given business partners as sources + - name: businessPartnerTargetExternalIds + in: query + description: Only show relations which have the given business partners as targets + required: false + schema: + type: array + description: Only show relations which have the given business partners as targets + items: + type: string + description: Only show relations which have the given business partners as targets + - name: updatedAtFrom + in: query + description: Only show relations which have been modified after the given time stamp + required: false + schema: + type: string + description: Only show relations which have been modified after the given time stamp + format: date-time + - name: page + in: query + description: Number of page to get results from + required: false + schema: + minimum: 0 + type: string + - name: size + in: query + description: Size of each page + required: false + schema: + maximum: 100 + minimum: 0 + type: string + responses: + '200': + description: A paginated list of business partner relations for the input stage + content: + application/json: + schema: + $ref: '#/components/schemas/PageDtoRelationDto' + put: + tags: + - relation-controller + summary: Update a business partner input relation + description: 'Update an existing business partner relation on the input stage. By using a request parameter it is also possible to create a relation if the relation with the given external identifier does not exist. ' + operationId: put + parameters: + - name: createIfNotExist + in: query + description: If true a business partner relation will be created even if a relation could not be found under the given external identifier. + required: false + schema: + type: boolean + description: If true a business partner relation will be created even if a relation could not be found under the given external identifier. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RelationPutRequest' + required: true + responses: + '200': + description: The given business partner relation has been updated. + content: + application/json: + schema: + $ref: '#/components/schemas/RelationDto' + '400': + description: 'On wrong or insufficient user provided data like references to non-existent business partners or relations that violate the relation constraints. ' + content: + application/json: + schema: + $ref: '#/components/schemas/GateErrorResponse' + post: + tags: + - relation-controller + summary: Create a new business partner input relation + description: Create a new relation between two business partner entries on the input stage. The external identifier is optional and a new one will be automatically created if not given. A given external identifier has to be unique. + operationId: post + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RelationPostRequest' + required: true + responses: + '201': + description: The created business partner input relation + content: + application/json: + schema: + $ref: '#/components/schemas/RelationDto' + '400': + description: 'If the business partner could not be created based on wrong or insufficient data provided such as non-existent business partners or violated relation constraints. ' + content: + application/json: + schema: + $ref: '#/components/schemas/GateErrorResponse' + '409': + description: If a relation with the given external identifier already exists + content: + application/json: + schema: + $ref: '#/components/schemas/GateErrorResponse' + delete: + tags: + - relation-controller + summary: Delete an existing business partner relation + description: Delete a relation between two business partners on the input stage. + operationId: delete + parameters: + - name: tenantBpnL + in: query + description: The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication. + required: false + schema: + type: string + description: The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication. + - name: externalId + in: query + description: The external identifier of the business partner relation to delete + required: true + schema: + type: string + description: The external identifier of the business partner relation to delete + responses: + '200': + description: The specified relation has been deleted. + '400': + description: 'On specifying a relation that does not exist. ' + content: + application/json: + schema: + $ref: '#/components/schemas/GateErrorResponse' /v6/sharing-state/ready: post: tags: @@ -69,7 +247,6 @@ paths: schema: minimum: 0 type: string - default: '0' - name: size in: query description: Size of each page @@ -78,7 +255,6 @@ paths: maximum: 100 minimum: 0 type: string - default: '10' requestBody: content: application/json: @@ -109,7 +285,6 @@ paths: schema: minimum: 0 type: string - default: '0' - name: size in: query description: Size of each page @@ -118,7 +293,6 @@ paths: maximum: 100 minimum: 0 type: string - default: '10' requestBody: content: application/json: @@ -172,7 +346,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PartnerUploadErrorResponse' + $ref: '#/components/schemas/GateErrorResponse' /v6/input/changelog/search: post: tags: @@ -188,7 +362,6 @@ paths: schema: minimum: 0 type: string - default: '0' - name: size in: query description: Size of each page @@ -197,7 +370,6 @@ paths: maximum: 100 minimum: 0 type: string - default: '10' requestBody: content: application/json: @@ -228,7 +400,6 @@ paths: schema: minimum: 0 type: string - default: '0' - name: size in: query description: Size of each page @@ -237,7 +408,6 @@ paths: maximum: 100 minimum: 0 type: string - default: '10' requestBody: content: application/json: @@ -325,7 +495,6 @@ paths: schema: minimum: 0 type: string - default: '0' - name: size in: query description: Size of each page @@ -334,7 +503,6 @@ paths: maximum: 100 minimum: 0 type: string - default: '10' - name: externalIds in: query description: External IDs @@ -1022,6 +1190,101 @@ components: type: string description: Key (externalId) of the entity that failed description: Holds information about failures + GateErrorResponse: + required: + - error + - path + - status + - timestamp + type: object + properties: + timestamp: + type: string + description: Timestamp of the error occurrence + format: date-time + status: + type: string + description: HTTP status of the error response + enum: + - 100 CONTINUE + - 101 SWITCHING_PROTOCOLS + - 102 PROCESSING + - 103 EARLY_HINTS + - 103 CHECKPOINT + - 200 OK + - 201 CREATED + - 202 ACCEPTED + - 203 NON_AUTHORITATIVE_INFORMATION + - 204 NO_CONTENT + - 205 RESET_CONTENT + - 206 PARTIAL_CONTENT + - 207 MULTI_STATUS + - 208 ALREADY_REPORTED + - 226 IM_USED + - 300 MULTIPLE_CHOICES + - 301 MOVED_PERMANENTLY + - 302 FOUND + - 302 MOVED_TEMPORARILY + - 303 SEE_OTHER + - 304 NOT_MODIFIED + - 305 USE_PROXY + - 307 TEMPORARY_REDIRECT + - 308 PERMANENT_REDIRECT + - 400 BAD_REQUEST + - 401 UNAUTHORIZED + - 402 PAYMENT_REQUIRED + - 403 FORBIDDEN + - 404 NOT_FOUND + - 405 METHOD_NOT_ALLOWED + - 406 NOT_ACCEPTABLE + - 407 PROXY_AUTHENTICATION_REQUIRED + - 408 REQUEST_TIMEOUT + - 409 CONFLICT + - 410 GONE + - 411 LENGTH_REQUIRED + - 412 PRECONDITION_FAILED + - 413 PAYLOAD_TOO_LARGE + - 413 REQUEST_ENTITY_TOO_LARGE + - 414 URI_TOO_LONG + - 414 REQUEST_URI_TOO_LONG + - 415 UNSUPPORTED_MEDIA_TYPE + - 416 REQUESTED_RANGE_NOT_SATISFIABLE + - 417 EXPECTATION_FAILED + - 418 I_AM_A_TEAPOT + - 419 INSUFFICIENT_SPACE_ON_RESOURCE + - 420 METHOD_FAILURE + - 421 DESTINATION_LOCKED + - 422 UNPROCESSABLE_ENTITY + - 423 LOCKED + - 424 FAILED_DEPENDENCY + - 425 TOO_EARLY + - 426 UPGRADE_REQUIRED + - 428 PRECONDITION_REQUIRED + - 429 TOO_MANY_REQUESTS + - 431 REQUEST_HEADER_FIELDS_TOO_LARGE + - 451 UNAVAILABLE_FOR_LEGAL_REASONS + - 500 INTERNAL_SERVER_ERROR + - 501 NOT_IMPLEMENTED + - 502 BAD_GATEWAY + - 503 SERVICE_UNAVAILABLE + - 504 GATEWAY_TIMEOUT + - 505 HTTP_VERSION_NOT_SUPPORTED + - 506 VARIANT_ALSO_NEGOTIATES + - 507 INSUFFICIENT_STORAGE + - 508 LOOP_DETECTED + - 509 BANDWIDTH_LIMIT_EXCEEDED + - 510 NOT_EXTENDED + - 511 NETWORK_AUTHENTICATION_REQUIRED + error: + type: array + description: List of error messages + items: + type: string + description: List of error messages + path: + type: string + description: Request path where the error occurred + description: Error response for invalid partner upload GeoCoordinateDto: required: - latitude @@ -1194,7 +1457,7 @@ components: items: $ref: '#/components/schemas/BusinessPartnerOutputDto' description: Paginated collection of results - PageDtoSharingStateDto: + PageDtoRelationDto: required: - content - contentSize @@ -1223,103 +1486,39 @@ components: type: array description: Collection of results in the page items: - $ref: '#/components/schemas/SharingStateDto' + $ref: '#/components/schemas/RelationDto' description: Paginated collection of results - PartnerUploadErrorResponse: + PageDtoSharingStateDto: required: - - error - - path - - status - - timestamp + - content + - contentSize + - page + - totalElements + - totalPages type: object properties: - timestamp: - type: string - description: Timestamp of the error occurrence - format: date-time - status: - type: string - description: HTTP status of the error response - enum: - - 100 CONTINUE - - 101 SWITCHING_PROTOCOLS - - 102 PROCESSING - - 103 EARLY_HINTS - - 103 CHECKPOINT - - 200 OK - - 201 CREATED - - 202 ACCEPTED - - 203 NON_AUTHORITATIVE_INFORMATION - - 204 NO_CONTENT - - 205 RESET_CONTENT - - 206 PARTIAL_CONTENT - - 207 MULTI_STATUS - - 208 ALREADY_REPORTED - - 226 IM_USED - - 300 MULTIPLE_CHOICES - - 301 MOVED_PERMANENTLY - - 302 FOUND - - 302 MOVED_TEMPORARILY - - 303 SEE_OTHER - - 304 NOT_MODIFIED - - 305 USE_PROXY - - 307 TEMPORARY_REDIRECT - - 308 PERMANENT_REDIRECT - - 400 BAD_REQUEST - - 401 UNAUTHORIZED - - 402 PAYMENT_REQUIRED - - 403 FORBIDDEN - - 404 NOT_FOUND - - 405 METHOD_NOT_ALLOWED - - 406 NOT_ACCEPTABLE - - 407 PROXY_AUTHENTICATION_REQUIRED - - 408 REQUEST_TIMEOUT - - 409 CONFLICT - - 410 GONE - - 411 LENGTH_REQUIRED - - 412 PRECONDITION_FAILED - - 413 PAYLOAD_TOO_LARGE - - 413 REQUEST_ENTITY_TOO_LARGE - - 414 URI_TOO_LONG - - 414 REQUEST_URI_TOO_LONG - - 415 UNSUPPORTED_MEDIA_TYPE - - 416 REQUESTED_RANGE_NOT_SATISFIABLE - - 417 EXPECTATION_FAILED - - 418 I_AM_A_TEAPOT - - 419 INSUFFICIENT_SPACE_ON_RESOURCE - - 420 METHOD_FAILURE - - 421 DESTINATION_LOCKED - - 422 UNPROCESSABLE_ENTITY - - 423 LOCKED - - 424 FAILED_DEPENDENCY - - 425 TOO_EARLY - - 426 UPGRADE_REQUIRED - - 428 PRECONDITION_REQUIRED - - 429 TOO_MANY_REQUESTS - - 431 REQUEST_HEADER_FIELDS_TOO_LARGE - - 451 UNAVAILABLE_FOR_LEGAL_REASONS - - 500 INTERNAL_SERVER_ERROR - - 501 NOT_IMPLEMENTED - - 502 BAD_GATEWAY - - 503 SERVICE_UNAVAILABLE - - 504 GATEWAY_TIMEOUT - - 505 HTTP_VERSION_NOT_SUPPORTED - - 506 VARIANT_ALSO_NEGOTIATES - - 507 INSUFFICIENT_STORAGE - - 508 LOOP_DETECTED - - 509 BANDWIDTH_LIMIT_EXCEEDED - - 510 NOT_EXTENDED - - 511 NETWORK_AUTHENTICATION_REQUIRED - error: + totalElements: + type: integer + description: Total number of all results in all pages + format: int64 + totalPages: + type: integer + description: Total number pages + format: int32 + page: + type: integer + description: Current page number + format: int32 + contentSize: + type: integer + description: Number of results in the page + format: int32 + content: type: array - description: List of error messages + description: Collection of results in the page items: - type: string - description: List of error messages - path: - type: string - description: Request path where the error occurred - description: Error response for invalid partner upload + $ref: '#/components/schemas/SharingStateDto' + description: Paginated collection of results PhysicalPostalAddressDto: type: object properties: @@ -1650,6 +1849,93 @@ components: items: type: string description: Request for setting business partners into ready to be shared to golden record state + RelationDto: + required: + - businessPartnerSourceExternalId + - businessPartnerTargetExternalId + - createdAt + - externalId + - relationType + - updatedAt + type: object + properties: + externalId: + type: string + description: The identifier with which to reference this relation + relationType: + type: string + description: The type of relation between the business partners + enum: + - IsManagedBy + businessPartnerSourceExternalId: + type: string + description: The business partner from which the relation emerges (the source) + businessPartnerTargetExternalId: + type: string + description: The business partner to which this relation goes (the target) + tenantBpnL: + type: string + description: The BPNL of the tenant this relation belongs to + updatedAt: + type: string + description: The time when this relation was last modified + format: date-time + createdAt: + type: string + description: The time when this relation was created + format: date-time + description: 'A relation from one business partner (the source) to another business partner (the target). ' + RelationPostRequest: + required: + - businessPartnerSourceExternalId + - businessPartnerTargetExternalId + - relationType + type: object + properties: + tenantBpnL: + type: string + description: The BPNL of the tenant to post in. If not given, the system attempts to infer the tenant from the authentication. + externalId: + type: string + description: The identifier under which the relation will be referenced. If not given a unique identifier will be automatically assigned by the system. A given external identifier needs be unique. + relationType: + type: string + description: The type of relation that should be created + enum: + - IsManagedBy + businessPartnerSourceExternalId: + type: string + description: The external identifier of the business partner from which the relation should emerge (the source) + businessPartnerTargetExternalId: + type: string + description: The external identifier of the business partner to which the relation should point (the target) + description: A request to create a new relation between two business partners + RelationPutRequest: + required: + - businessPartnerSourceExternalId + - businessPartnerTargetExternalId + - externalId + - relationType + type: object + properties: + tenantBpnL: + type: string + description: The BPNL of the tenant in which to update. If not given, the system attempts to infer the tenant from the authentication. + externalId: + type: string + description: The external identifier of the business partner relation to update + relationType: + type: string + description: The type the relation should be + enum: + - IsManagedBy + businessPartnerSourceExternalId: + type: string + description: The external identifier of the business partner from which the relation should emerge (the source) + businessPartnerTargetExternalId: + type: string + description: The external identifier of the business partner to which the relation should point (the target) + description: A request to update the content of the given business partner relation SharingStateDto: required: - externalId @@ -1670,7 +1956,14 @@ components: - Ready sharingErrorCode: type: string - description: One of the sharing error codes in case the current sharing state type is "error". + description: |- + One of the sharing error codes in case the current sharing state type is "error". + * `NaturalPersonError`: The provided record contains natural person information. + * `BpnErrorNotFound`: The provided record can not be matched to a legal entity or an address. + * `BpnErrorTooManyOptions`: The provided record can not link to a clear legal entity. + * `MandatoryFieldValidationFailed`: The provided record does not fulfill mandatory validation rules. + * `BlacklistCountryPresent`: The provided record is part of a country that is not allowed to be processed by the GR process (example: Brazil). + * `UnknownSpecialCharacters`: The provided record contains unallowed special characters. enum: - SharingProcessError - SharingTimeout @@ -1692,7 +1985,8 @@ components: taskId: type: string description: The orchestrator task identifier that was created - description: A sharing state entry shows the progress in the sharing process and is updated each time the progress for a business partner changes. The business partner is identified by a combination of external ID and business partner type. + description: | + A sharing state entry shows the progress in the sharing process and is updated each time the progress for a business partner changes. The business partner is identified by a combination of external ID and business partner type. SiteRepresentationInputDto: required: - states