From 0c7b6b712c27a7b6c349a6726cb69ca473d716eb Mon Sep 17 00:00:00 2001 From: kunyao-cofinity-x <138508171+kunyao-cofinity-x@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:25:05 +0200 Subject: [PATCH 1/2] feat: Implement search logic for business partner --- CHANGELOG.md | 1 + .../pool/BusinessPartnerNonVerboseValues.kt | 31 + .../pool/BusinessPartnerVerboseValues.kt | 44 ++ .../response/BusinessPartnerOutputDto.kt | 3 +- .../tractusx/bpdm/pool/api/ApiCommons.kt | 2 + .../bpdm/pool/api/PoolBusinessPartnerApi.kt | 64 ++ .../api/client/BusinessPartnersApiClient.kt | 45 ++ .../bpdm/pool/api/client/PoolApiClient.kt | 2 + .../bpdm/pool/api/client/PoolClientImpl.kt | 2 + .../model/BusinessPartnerSearchFilterType.kt | 26 + .../LegalEntityPropertiesSearchRequest.kt | 24 +- .../BusinessPartnerSearchResultDto.kt | 126 ++++ .../controller/BusinessPartnerController.kt | 67 ++ .../BusinessPartnerExceptionHandler.kt | 62 ++ .../BusinessPartnerSearchException.kt | 28 + .../pool/repository/LegalEntityRepository.kt | 1 + .../repository/LogisticAddressRepository.kt | 78 +++ .../bpdm/pool/repository/SiteRepository.kt | 1 + .../service/BusinessPartnerSearchService.kt | 234 ++++++- .../bpdm/pool/service/SearchService.kt | 12 + .../V7_3_0_0__add_normalized_function.sql | 25 + .../controller/BusinessPartnerControllerIT.kt | 578 ++++++++++++++++ docs/api/pool.json | 645 +++++++++++++++++- docs/api/pool.yaml | 551 +++++++++++++++ 24 files changed, 2641 insertions(+), 11 deletions(-) create mode 100644 bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/PoolBusinessPartnerApi.kt create mode 100644 bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/BusinessPartnersApiClient.kt create mode 100644 bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/BusinessPartnerSearchFilterType.kt create mode 100644 bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/response/BusinessPartnerSearchResultDto.kt create mode 100644 bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerController.kt create mode 100644 bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerExceptionHandler.kt create mode 100644 bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerSearchException.kt create mode 100644 bpdm-pool/src/main/resources/db/migration/V7_3_0_0__add_normalized_function.sql create mode 100644 bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerControllerIT.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 75674d876..47e0c2686 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: Add validity on business partner relation to the golden record process for legal entity relations [#1295](https://github.com/eclipse-tractusx/bpdm/issues/1295) - BPDM Gate: Fix synchronization of golden records sometimes skipping updates [#1519](https://github.com/eclipse-tractusx/bpdm/issues/1519) - BPDM: The Golden Record Process now automatically calculates the number of sharing members for the confidence criteria [#1464](https://github.com/eclipse-tractusx/sig-release/issues/1464) +- BPDM Pool: Add a business partner search endpoint [[#1382]](https://github.com/eclipse-tractusx/bpdm/issues/781) ### Changed diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerNonVerboseValues.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerNonVerboseValues.kt index 36b9c0439..09e8589e5 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerNonVerboseValues.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerNonVerboseValues.kt @@ -43,6 +43,8 @@ object BusinessPartnerNonVerboseValues { sortedSetOf(IdentifierTypeCategory.NBR) ) val addressIdentifierTypeDto2 = addressIdentifierTypeDto1 + val addressIdentifierTypeDto3 = addressIdentifierTypeDto1 + val identifier1 = LegalEntityIdentifierDto( value = BusinessPartnerVerboseValues.identifier1.value, @@ -255,6 +257,35 @@ object BusinessPartnerNonVerboseValues { index = BusinessPartnerVerboseValues.legalEntityUpsert3.index ) + val legalEntityCreate4 = LegalEntityPartnerCreateRequest( + legalEntity = LegalEntityDto( + legalName = BusinessPartnerVerboseValues.legalEntityUpsert4.legalEntity.legalName, + legalShortName = null, + legalForm = BusinessPartnerVerboseValues.legalForm3.technicalKey, + identifiers = listOf(identifier3), + states = listOf(leStatus3), + confidenceCriteria = BusinessPartnerVerboseValues.legalEntity3.legalEntity.confidenceCriteria, + isParticipantData = false + ), + legalAddress = logisticAddress4, + index = BusinessPartnerVerboseValues.legalEntityUpsert4.index + ) + + val legalEntityCreateMultipleIdentifier = LegalEntityPartnerCreateRequest( + legalEntity = LegalEntityDto( + legalName = BusinessPartnerVerboseValues.legalEntityUpsertMultipleIdentifier.legalEntity.legalName, + legalShortName = null, + legalForm = BusinessPartnerVerboseValues.legalForm1.technicalKey, + identifiers = listOf(identifier1, identifier2), + states = listOf(leStatus1), + confidenceCriteria = BusinessPartnerVerboseValues.legalEntity1.legalEntity.confidenceCriteria, + isParticipantData = false + ), + legalAddress = logisticAddress1, + index = BusinessPartnerVerboseValues.legalEntityUpsertMultipleIdentifier.index + ) + + val legalEntityUpdate1 = LegalEntityPartnerUpdateRequest( bpnl = BusinessPartnerVerboseValues.legalEntityUpsert1.legalEntity.bpnl, legalEntity = legalEntityCreate1.legalEntity, diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerVerboseValues.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerVerboseValues.kt index efe79ecfc..236911300 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerVerboseValues.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/pool/BusinessPartnerVerboseValues.kt @@ -27,6 +27,7 @@ import org.eclipse.tractusx.bpdm.common.model.BusinessStateType import org.eclipse.tractusx.bpdm.common.service.toDto import org.eclipse.tractusx.bpdm.pool.api.model.* import org.eclipse.tractusx.bpdm.pool.api.model.response.* +import org.eclipse.tractusx.bpdm.pool.api.model.StreetDto import java.time.Instant import java.time.LocalDateTime import java.time.ZoneOffset @@ -556,4 +557,47 @@ object BusinessPartnerVerboseValues { ), index = "3" ) + + val legalEntityUpsert4 = LegalEntityPartnerCreateVerboseDto( + legalEntity = LegalEntityVerboseDto( + bpnl = thirdBpnl, + legalName = "Müller Handels GmbH & Co. KG", + legalFormVerbose = legalForm3, + identifiers = listOf(LegalEntityIdentifierVerboseDto("An ID Value", identifierType3, "Official Z")), + states = listOf(leStatus3), + currentness = createdTime1.toInstant(ZoneOffset.UTC), + confidenceCriteria = confidenceCriteria3, + createdAt = Instant.now(), + updatedAt = Instant.now(), + isParticipantData = true + ), + legalAddress = addressPartner3.copy( + bpnLegalEntity = legalEntity3.legalEntity.bpnl, + addressType = AddressType.LegalAddress + ), + index = "4" + ) + + val legalEntityUpsertMultipleIdentifier = LegalEntityPartnerCreateVerboseDto( + legalEntity = LegalEntityVerboseDto( + bpnl = firstBpnL, + legalName = "Business Partner Name", + legalFormVerbose = legalForm1, + identifiers = listOf( + LegalEntityIdentifierVerboseDto("ID-XYZ", identifierType1, "Agency X"), + LegalEntityIdentifierVerboseDto("Another ID Value", identifierType2, "Body Y") + ), + states = listOf(leStatus1), + currentness = createdTime1.toInstant(ZoneOffset.UTC), + confidenceCriteria = confidenceCriteria1, + isParticipantData = false, + createdAt = Instant.now(), + updatedAt = Instant.now() + ), + legalAddress = addressPartner1.copy( + bpnLegalEntity = legalEntity1.legalEntity.bpnl, + ), + index = "1" + ) + } \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerOutputDto.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerOutputDto.kt index 73706575a..af70322b9 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerOutputDto.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerOutputDto.kt @@ -84,5 +84,6 @@ data class AddressComponentOutputDto( override val physicalPostalAddress: PhysicalPostalAddressDto = PhysicalPostalAddressDto(), override val alternativePostalAddress: AlternativePostalAddressDto? = null, val confidenceCriteria: ConfidenceCriteriaDto, - override val states: Collection = emptyList() + override val states: Collection = emptyList(), + val identifiers: Collection = emptyList(), ) : IBaseAddressRepresentation diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/ApiCommons.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/ApiCommons.kt index 24b612be7..9017aa9c4 100644 --- a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/ApiCommons.kt +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/ApiCommons.kt @@ -72,5 +72,7 @@ object ApiCommons { const val BPN_NAME = "Bpn Controller" const val BPN_DESCRIPTION = "Support functionality for BPN operations" + const val BUSINESS_PARTNERS_NAME = "Business Partners Controller" + const val BUSINESS_PARTNERS_DESCRIPTION = "Look-up business partner" } \ No newline at end of file diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/PoolBusinessPartnerApi.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/PoolBusinessPartnerApi.kt new file mode 100644 index 000000000..2a2eeef4e --- /dev/null +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/PoolBusinessPartnerApi.kt @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.pool.api + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag +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.common.util.CommonApiPathNames +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto +import org.springdoc.core.annotations.ParameterObject +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam + +@RequestMapping(PoolBusinessPartnerApi.BUSINESS_PARTNERS_PATH, produces = [MediaType.APPLICATION_JSON_VALUE]) +interface PoolBusinessPartnerApi { + + companion object{ + const val BUSINESS_PARTNERS_PATH = "${ApiCommons.BASE_PATH_V7}/business-partners" + } + + @Operation( + summary = "Return business partners look-up result", + description = "Look-up the business partner data by parameters" + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Matching business partners"), + ApiResponse(responseCode = "400", description = "Invalid request (e.g., missing both ID and legalName)"), + ] + ) + @Tag(name = ApiCommons.BUSINESS_PARTNERS_NAME, description = ApiCommons.BUSINESS_PARTNERS_DESCRIPTION) + @PostMapping(CommonApiPathNames.SUBPATH_SEARCH) + fun searchBusinessPartners( + @RequestBody searchRequest: LegalEntityPropertiesSearchRequest, + @RequestParam searchResultFilter: Set?, + @ParameterObject @Valid paginationRequest: PaginationRequest + ): PageDto +} \ No newline at end of file diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/BusinessPartnersApiClient.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/BusinessPartnersApiClient.kt new file mode 100644 index 000000000..9da54381f --- /dev/null +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/BusinessPartnersApiClient.kt @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.pool.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.pool.api.PoolBusinessPartnerApi +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto +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.HttpExchange +import org.springframework.web.service.annotation.PostExchange + +@HttpExchange(PoolBusinessPartnerApi.BUSINESS_PARTNERS_PATH) +interface BusinessPartnersApiClient: PoolBusinessPartnerApi { + + @PostExchange(value ="/search") + override fun searchBusinessPartners( + @RequestBody searchRequest: LegalEntityPropertiesSearchRequest, + @RequestParam searchResultFilter: Set?, + @ParameterObject @Valid paginationRequest: PaginationRequest + ): PageDto + +} \ No newline at end of file diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolApiClient.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolApiClient.kt index 95f52aa28..f84d64891 100644 --- a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolApiClient.kt +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolApiClient.kt @@ -36,4 +36,6 @@ interface PoolApiClient { val members: MembersApiClient val participants: DataSpaceParticipantsApiClient + + val businessPartners: BusinessPartnersApiClient } diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolClientImpl.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolClientImpl.kt index eca0c3744..933bdf38e 100644 --- a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolClientImpl.kt +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/client/PoolClientImpl.kt @@ -57,6 +57,8 @@ class PoolClientImpl( override val participants by lazy { createClient() } + override val businessPartners by lazy { createClient() } + private inline fun createClient() = httpServiceProxyFactory.createClient(T::class.java) } diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/BusinessPartnerSearchFilterType.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/BusinessPartnerSearchFilterType.kt new file mode 100644 index 000000000..16314d0a1 --- /dev/null +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/BusinessPartnerSearchFilterType.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.pool.api.model + +enum class BusinessPartnerSearchFilterType { + IncludeLegalEntities, + IncludeSites, + IncludeAdditionalAddresses +} \ No newline at end of file diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/request/LegalEntityPropertiesSearchRequest.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/request/LegalEntityPropertiesSearchRequest.kt index da80baab7..dd6994e3c 100644 --- a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/request/LegalEntityPropertiesSearchRequest.kt +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/request/LegalEntityPropertiesSearchRequest.kt @@ -19,16 +19,32 @@ package org.eclipse.tractusx.bpdm.pool.api.model.request +import com.neovisionaries.i18n.CountryCode import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.media.Schema - @Schema(description = "Contains keywords used for searching in legal entity properties") data class LegalEntityPropertiesSearchRequest( @field:Parameter(description = "Filter legal entities by name") - val legalName: String? -) { + val legalName: String?, + + @field:Parameter(description = "Filter business partners by CX-BPN.") + val bpn: String?, + + @field:Parameter(description = "Filter business partners by street name.") + val streetName: String?, + + @field:Parameter(description = "Filter business partners by zip code.") + val postalCode: String?, + + @field:Parameter(description = "Filter business partners by city.") + val city: String?, + + @field:Parameter(description = "Filter business partners by country code ISO 3166-1.") + val country: CountryCode?, + + ) { companion object { - val EmptySearchRequest = LegalEntityPropertiesSearchRequest(null) + val EmptySearchRequest = LegalEntityPropertiesSearchRequest(null, null, null, null, null, null) } } diff --git a/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/response/BusinessPartnerSearchResultDto.kt b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/response/BusinessPartnerSearchResultDto.kt new file mode 100644 index 000000000..65119b847 --- /dev/null +++ b/bpdm-pool-api/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/api/model/response/BusinessPartnerSearchResultDto.kt @@ -0,0 +1,126 @@ +/******************************************************************************* + * 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.pool.api.model.response + +import com.neovisionaries.i18n.CountryCode +import io.swagger.v3.oas.annotations.media.Schema +import org.eclipse.tractusx.bpdm.common.dto.AddressType +import org.eclipse.tractusx.bpdm.common.dto.GeoCoordinateDto +import org.eclipse.tractusx.bpdm.common.model.BusinessStateType +import org.eclipse.tractusx.bpdm.common.model.DeliveryServiceType +import org.eclipse.tractusx.bpdm.pool.api.model.StreetDto +import java.time.LocalDateTime + +@Schema(name = "BusinessPartnerSearchResultDto", description = "Response of the business partner search result.") +data class BusinessPartnerSearchResultDto( + val identifiers: Collection = emptyList(), + val states: Collection = emptyList(), + val legalEntity: BusinessPartnerLegalEntity, + val site: BusinessPartnerSite?, + val address: BusinessPartnerPostalAddress, + val isParticipantData: Boolean +) + +data class BusinessPartnerLegalEntity( + val legalEntityBpn: String, + val legalName: String? = null, + val shortName: String? = null, + val legalForm: String? = null, + val confidenceCriteria: BusinessPartnerConfidenceCriteriaDto, + val states: Collection = emptyList() +) + +data class BusinessPartnerSite( + val siteBpn: String, + val name: String? = null, + val confidenceCriteria: BusinessPartnerConfidenceCriteriaDto, + val states: Collection = emptyList() +) + +data class BusinessPartnerIdentifierDto( + + @get:Schema(description = "The type of the identifier.") + val type: String?, + @get:Schema(description = "The value of the identifier like “DE123465789.") + val value: String?, + @get:Schema(description = "The name of the official register, where the identifier is registered. For example, a Handelsregisternummer in Germany is only valid with its corresponding Registergericht and Registerart.") + val issuingBody: String? + +) + +data class BusinessPartnerPostalAddress( + val addressBpn: String, + val name: String? = null, + val addressType: AddressType?, + val physicalPostalAddress: PhysicalPostalAddressDto = PhysicalPostalAddressDto(), + val alternativePostalAddress: AlternativePostalAddressDto? = null, + val confidenceCriteria: BusinessPartnerConfidenceCriteriaDto, + val states: Collection = emptyList(), + val identifiers: Collection = emptyList(), +) + +data class BusinessPartnerStateDto( + val validFrom: LocalDateTime?, + val validTo: LocalDateTime?, + val type: BusinessStateType? +) + +data class PhysicalPostalAddressDto( + val geographicCoordinates: GeoCoordinateDto? = null, + val country: CountryCode? = null, + val administrativeAreaLevel1: String? = null, + val administrativeAreaLevel2: String? = null, + val administrativeAreaLevel3: String? = null, + val postalCode: String? = null, + val city: String? = null, + val district: String? = null, + val street: StreetDto? = null, + val companyPostalCode: String? = null, + val industrialZone: String? = null, + val building: String? = null, + val floor: String? = null, + val door: String? = null, + val taxJurisdictionCode: String? = null +) + +data class AlternativePostalAddressDto( + val geographicCoordinates: GeoCoordinateDto? = null, + val country: CountryCode? = null, + val administrativeAreaLevel1: String? = null, + val postalCode: String? = null, + val city: String? = null, + val deliveryServiceType: DeliveryServiceType? = null, + val deliveryServiceQualifier: String? = null, + val deliveryServiceNumber: String? = null +) + +data class BusinessPartnerConfidenceCriteriaDto( + val sharedByOwner: Boolean, + val checkedByExternalDataSource: Boolean, + val numberOfSharingMembers: Int, + val lastConfidenceCheckAt: LocalDateTime, + val nextConfidenceCheckAt: LocalDateTime, + val confidenceLevel: Int +) + +data class AddressIdentifierDto( + val value: String, + val type: String +) \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerController.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerController.kt new file mode 100644 index 000000000..a2662293a --- /dev/null +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerController.kt @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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.pool.controller + +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.pool.api.PoolBusinessPartnerApi +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto +import org.eclipse.tractusx.bpdm.pool.config.PermissionConfigProperties +import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerSearchService +import org.springframework.http.HttpStatus +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException + +/******************************************************************************* + * 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 + ******************************************************************************/ +@RestController +class BusinessPartnerController( + private val businessPartnerSearchService: BusinessPartnerSearchService +): PoolBusinessPartnerApi { + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.Companion.READ_PARTNER})") + override fun searchBusinessPartners( + searchRequest: LegalEntityPropertiesSearchRequest, + searchResultFilter: Set?, + paginationRequest: PaginationRequest + ): PageDto { + return businessPartnerSearchService.searchBusinessPartner(searchRequest, searchResultFilter, paginationRequest); + } + +} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerExceptionHandler.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerExceptionHandler.kt new file mode 100644 index 000000000..61bbb7a4e --- /dev/null +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerExceptionHandler.kt @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2021 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.pool.exception + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler + +/******************************************************************************* + * Copyright (c) 2021 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 + ******************************************************************************/ +@RestControllerAdvice +class BusinessPartnerExceptionHandler : ResponseEntityExceptionHandler() { + + @ExceptionHandler(BusinessPartnerSearchException::class) + fun handleBusinessPartnerSearchException( + ex: BusinessPartnerSearchException + ): ResponseEntity> { + + val body = mapOf( + "code" to HttpStatus.BAD_REQUEST.value(), + "status" to "BAD_REQUEST", + "message" to ex.message + ) + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body) + } +} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerSearchException.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerSearchException.kt new file mode 100644 index 000000000..412ea4f08 --- /dev/null +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/exception/BusinessPartnerSearchException.kt @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2021 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.pool.exception + +import org.eclipse.tractusx.bpdm.common.exception.BpdmExceptionHandler +import org.eclipse.tractusx.bpdm.pool.exception.BusinessPartnerExceptionHandler +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus + +@ResponseStatus(HttpStatus.BAD_REQUEST) +class BusinessPartnerSearchException(override val message: String) : RuntimeException(message) \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt index 604a5909e..aab57b346 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LegalEntityRepository.kt @@ -19,6 +19,7 @@ package org.eclipse.tractusx.bpdm.pool.repository +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.entity.IdentifierTypeDb import org.eclipse.tractusx.bpdm.pool.entity.LegalEntityDb import org.eclipse.tractusx.bpdm.pool.entity.NameDb diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt index 9dfd33fef..9b5bbdc23 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/LogisticAddressRepository.kt @@ -19,6 +19,7 @@ package org.eclipse.tractusx.bpdm.pool.repository +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.entity.LegalEntityDb import org.eclipse.tractusx.bpdm.pool.entity.LogisticAddressDb import org.eclipse.tractusx.bpdm.pool.entity.SiteDb @@ -70,6 +71,7 @@ interface LogisticAddressRepository : JpaRepository, Jp ) } } + } fun findByBpn(bpn: String): LogisticAddressDb? @@ -95,4 +97,80 @@ interface LogisticAddressRepository : JpaRepository, Jp @Query("SELECT DISTINCT p FROM LogisticAddressDb p LEFT JOIN FETCH p.states WHERE p IN :partners") fun joinStates(partners: Set): Set + + @Query( + value = + """ + SELECT DISTINCT la + FROM LogisticAddressDb la + JOIN la.legalEntity le + LEFT JOIN la.site s + WHERE + ( + CAST(:#{#searchRequest.legalName} AS text) IS NULL + OR concat(function('bpdm.normalize_name', CAST(la.legalEntity.legalName.value AS text)), '') + Like concat('%', concat(function('bpdm.normalize_name', CAST(:#{#searchRequest.legalName} AS text)), ''), '%') + OR function( + 'regexp_match', + concat(CAST(la.legalEntity.legalName.value AS text), ''), + concat('.*', function('regexp_replace', CAST(:#{#searchRequest.legalName} AS text), '_', '.', 'g'), '.*') + ) IS NOT NULL + ) + AND ( + CAST(:#{#searchRequest.bpn} AS text) IS NULL + OR le.bpn = CAST(:#{#searchRequest.bpn} AS text) + OR s.bpn = CAST(:#{#searchRequest.bpn} AS text) + OR la.bpn = CAST(:#{#searchRequest.bpn} AS text) + ) + AND ( + CAST(:#{#searchRequest.streetName} AS text) IS NULL + OR concat(function('bpdm.normalize_name', cast(la.physicalPostalAddress.street.name as text)),'') + LIKE concat('%', concat(function('bpdm.normalize_name', CAST(:#{#searchRequest.streetName} AS text)), ''),'%') + OR function( + 'regexp_match', + concat(CAST(la.physicalPostalAddress.street.name AS text), ''), + concat('.*', function('regexp_replace', CAST(:#{#searchRequest.streetName} AS text), '_', '.', 'g'), '.*') + ) IS NOT NULL + ) + AND ( + :#{#searchRequest.postalCode} IS NULL + OR la.physicalPostalAddress.postCode = :#{#searchRequest.postalCode} + ) + AND ( + CAST(:#{#searchRequest.city} AS text) IS NULL + OR concat(function('bpdm.normalize_name',CAST(la.physicalPostalAddress.city AS text)),'') + Like concat('%',concat(function('bpdm.normalize_name',CAST(:#{#searchRequest.city} AS text)),''), '%') + OR function( + 'regexp_match', + concat(CAST(la.physicalPostalAddress.city AS text), ''), + concat('.*', function('regexp_replace', CAST(:#{#searchRequest.city} AS text), '_', '.', 'g'), '.*') + ) IS NOT NULL + ) + AND ( + CAST(:#{#searchRequest.country} AS text) IS NULL + OR la.physicalPostalAddress.country = CAST(:#{#searchRequest.country} AS text) + ) + AND ( + ( + CAST(:#{#isLegalEntity} AS boolean) = true + AND le.legalAddress = la + ) + OR ( + CAST(:#{#isSite} AS boolean) = true + AND s.mainAddress = la + ) + OR ( + CAST(:#{#isAdditionalAddress} AS boolean) = true + AND le.legalAddress <> la + AND s is null + ) + ) + """ + ) + fun searchBusinessPartner( + searchRequest: LegalEntityPropertiesSearchRequest, + isLegalEntity: Boolean, + isSite: Boolean, + isAdditionalAddress: Boolean, + pageable: Pageable): Page } \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/SiteRepository.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/SiteRepository.kt index 9a09a88c8..35e3858e8 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/SiteRepository.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/repository/SiteRepository.kt @@ -19,6 +19,7 @@ package org.eclipse.tractusx.bpdm.pool.repository +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.entity.LegalEntityDb import org.eclipse.tractusx.bpdm.pool.entity.SiteDb import org.springframework.data.domain.Page diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt index 08f6476b7..3d2d04bd1 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerSearchService.kt @@ -20,16 +20,31 @@ package org.eclipse.tractusx.bpdm.pool.service import mu.KotlinLogging +import org.eclipse.tractusx.bpdm.common.dto.AddressType +import org.eclipse.tractusx.bpdm.common.dto.GeoCoordinateDto import org.eclipse.tractusx.bpdm.common.dto.PageDto import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType import org.eclipse.tractusx.bpdm.pool.api.model.request.AddressPartnerSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.request.BusinessPartnerSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressMatchVerboseDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.AlternativePostalAddressDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerConfidenceCriteriaDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerIdentifierDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerLegalEntity +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerPostalAddress +import org.eclipse.tractusx.bpdm.pool.api.model.response.PhysicalPostalAddressDto +import org.eclipse.tractusx.bpdm.pool.api.model.StreetDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressIdentifierDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSite import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto import org.eclipse.tractusx.bpdm.pool.api.model.response.SiteMatchVerboseDto import org.eclipse.tractusx.bpdm.pool.entity.LegalEntityDb import org.eclipse.tractusx.bpdm.pool.entity.LogisticAddressDb import org.eclipse.tractusx.bpdm.pool.entity.SiteDb +import org.eclipse.tractusx.bpdm.pool.exception.BusinessPartnerSearchException import org.eclipse.tractusx.bpdm.pool.repository.LegalEntityRepository import org.eclipse.tractusx.bpdm.pool.repository.LogisticAddressRepository import org.eclipse.tractusx.bpdm.pool.repository.SiteRepository @@ -37,6 +52,7 @@ import org.springframework.context.annotation.Primary import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Service + /** * Provides search functionality on the Catena-x database for the BPDM system */ @@ -48,7 +64,7 @@ class BusinessPartnerSearchService( private val addressService: AddressService, private val siteService: SiteService, private val logisticAddressRepository: LogisticAddressRepository, - private val siteRepository: SiteRepository, + private val siteRepository: SiteRepository ): SearchService { private val logger = KotlinLogging.logger { } @@ -69,7 +85,8 @@ class BusinessPartnerSearchService( businessPartnerFetchService.fetchLegalEntityDependencies(legalEntityPage.content.map { (_, legalEntity) -> legalEntity }.toSet()) return with(legalEntityPage) { - PageDto(totalElements, totalPages, page, contentSize, + PageDto( + totalElements, totalPages, page, contentSize, content.map { (score, legalEntity) -> legalEntity.toMatchDto(score) }) } } @@ -125,7 +142,8 @@ class BusinessPartnerSearchService( val addressPage = searchAndPrepareAddressPage(searchRequest, paginationRequest) addressService.fetchLogisticAddressDependencies(addressPage.content.map { (_, address) -> address }.toSet()) return with(addressPage) { - PageDto(totalElements, totalPages, page, contentSize, + PageDto( + totalElements, totalPages, page, contentSize, content.map { (score, address) -> address.toMatchDto(score) }) } } @@ -182,7 +200,8 @@ class BusinessPartnerSearchService( siteService.fetchSiteDependenciesPage(sitePage.content.map { site -> site }.toSet()) return with(sitePage) { - PageDto(totalElements, totalPages, page, contentSize, + PageDto( + totalElements, totalPages, page, contentSize, content.map { site -> site.toMatchDto() }) } } @@ -199,4 +218,211 @@ class BusinessPartnerSearchService( return sitePage.toDto(sitePage.content.map { it }) } + + /** + * @see searchBusinessPartner + * + */ + override fun searchBusinessPartner( + searchRequest: LegalEntityPropertiesSearchRequest, + searchResultFilter: Set?, + paginationRequest: PaginationRequest + ): PageDto { + + fun String?.startsWithWhitespace(): Boolean = this?.firstOrNull()?.isWhitespace() == true + + val isAllSearchParamsEmpty = with(searchRequest) { + listOf(bpn, legalName, city, streetName, postalCode).all { it.isNullOrBlank() } && country == null + } + + val isBpnAndLegalNameBothBlank = searchRequest.bpn.isNullOrBlank() && searchRequest.legalName.isNullOrBlank() + if (isAllSearchParamsEmpty || isBpnAndLegalNameBothBlank) { + throw BusinessPartnerSearchException("At least one of 'bpn' or 'legalName' must be provided.") + } + + val isFilterBlank = searchResultFilter.isNullOrEmpty() + if (isFilterBlank) { + throw BusinessPartnerSearchException("At least one filter value must be provided in 'searchResultFilter'.") + } + + if (searchRequest.bpn.startsWithWhitespace() || searchRequest.legalName.startsWithWhitespace()) { + throw BusinessPartnerSearchException("'bpn' and 'legalName' must not start with a whitespace character.") + } + + searchRequest.legalName?.takeIf { it.length < 3 }?.let { + throw BusinessPartnerSearchException("'legalName' must contain at least 3 characters.") + } + + val pageable = PageRequest.of(paginationRequest.page, paginationRequest.size) + val includeLegalEntities = shouldInclude(searchResultFilter, BusinessPartnerSearchFilterType.IncludeLegalEntities) + val includeSites = shouldInclude(searchResultFilter, BusinessPartnerSearchFilterType.IncludeSites) + val includeAdditionalAddresses = shouldInclude(searchResultFilter, BusinessPartnerSearchFilterType.IncludeAdditionalAddresses) + + val results = mutableListOf() + + val matchedAddress = logisticAddressRepository.searchBusinessPartner( + searchRequest, + includeLegalEntities, + includeSites, + includeAdditionalAddresses, + pageable + ) + + results.addAll(matchedAddress.map{ searchAddressResultMapping(it) }) + + return PageDto( + totalElements = matchedAddress.totalElements, + totalPages = matchedAddress.totalPages, + page = paginationRequest.page, + contentSize = results.size, + content = results + ) + } + + private fun shouldInclude( + searchResultFilter: Set?, + filterType: BusinessPartnerSearchFilterType + ): Boolean { + return searchResultFilter.isNullOrEmpty() || searchResultFilter.contains(filterType) + } + + private fun searchAddressResultMapping(result: LogisticAddressDb): BusinessPartnerSearchResultDto { + + val legalEntity = requireNotNull(result.legalEntity) { + "searchAddressResultMapping requires LogisticAddressDb.legalEntity to be non-null" + } + + val legalAddressId = result.legalEntity?.legalAddress?.id + val siteMainAddressId = result.site?.mainAddress?.id + + val addressType = when { + result.id == legalAddressId && result.id == siteMainAddressId -> AddressType.LegalAndSiteMainAddress + result.id == legalAddressId -> AddressType.LegalAddress + result.id == siteMainAddressId -> AddressType.SiteMainAddress + else -> AddressType.AdditionalAddress + } + + val identifiers: List = + legalEntity.identifiers.map { identifier -> + BusinessPartnerIdentifierDto( + type = identifier.type.technicalKey, + value = identifier.value, + issuingBody = identifier.issuingBody + ) + } + val addressIdentifiers: Collection = + result.identifiers.map { identifier -> + AddressIdentifierDto( + type = identifier.type.technicalKey, + value = identifier.value + ) + } + + val legalEntityDto = BusinessPartnerLegalEntity( + legalEntityBpn = legalEntity.bpn, + legalName = legalEntity.legalName.value, + legalForm = legalEntity.legalForm?.name, + confidenceCriteria = with(legalEntity.confidenceCriteria) { + BusinessPartnerConfidenceCriteriaDto( + sharedByOwner = sharedByOwner, + checkedByExternalDataSource = checkedByExternalDataSource, + numberOfSharingMembers = numberOfBusinessPartners, + lastConfidenceCheckAt = lastConfidenceCheckAt, + nextConfidenceCheckAt = nextConfidenceCheckAt, + confidenceLevel = confidenceLevel + ) + } + ) + + val siteDto = result.site?.let { site -> + BusinessPartnerSite( + siteBpn = site.bpn, + name = site.name, + confidenceCriteria = with(site.confidenceCriteria) { + BusinessPartnerConfidenceCriteriaDto( + sharedByOwner = sharedByOwner, + checkedByExternalDataSource = checkedByExternalDataSource, + numberOfSharingMembers = numberOfBusinessPartners, + lastConfidenceCheckAt = lastConfidenceCheckAt, + nextConfidenceCheckAt = nextConfidenceCheckAt, + confidenceLevel = confidenceLevel + ) + } + ) + } + + val physical = result.physicalPostalAddress + val alternative = result.alternativePostalAddress + val streetDto = physical.street?.let { street -> + StreetDto( + name = street.name, + houseNumber = street.houseNumber, + houseNumberSupplement = street.houseNumberSupplement, + milestone = street.milestone, + direction = street.direction, + namePrefix = street.namePrefix, + additionalNamePrefix = street.additionalNamePrefix, + nameSuffix = street.nameSuffix, + additionalNameSuffix = street.additionalNameSuffix + ) + } + + val addressDto = BusinessPartnerPostalAddress( + name = null, + addressType = addressType, + identifiers = addressIdentifiers, + addressBpn = result.bpn, + physicalPostalAddress = PhysicalPostalAddressDto( + geographicCoordinates = physical.geographicCoordinates?.let { + GeoCoordinateDto( + longitude = physical.geographicCoordinates.longitude, + latitude = physical.geographicCoordinates.latitude, + altitude = physical.geographicCoordinates.altitude + )}, + administrativeAreaLevel1 = physical.administrativeAreaLevel1?.countryCode?.name, + administrativeAreaLevel2 = physical.administrativeAreaLevel2, + administrativeAreaLevel3 = physical.administrativeAreaLevel3, + street = streetDto, + postalCode = physical.postCode, + city = physical.city, + country = physical.country, + district = physical.districtLevel1, + companyPostalCode = physical.companyPostCode, + industrialZone = physical.industrialZone, + building = physical.building, + floor = physical.floor, + door = physical.door, + taxJurisdictionCode = physical.taxJurisdictionCode + ), + alternativePostalAddress = AlternativePostalAddressDto( + geographicCoordinates = null, + country = alternative?.country, + administrativeAreaLevel1 = alternative?.administrativeAreaLevel1?.countryCode?.name, + postalCode = alternative?.postCode, + city = alternative?.city, + deliveryServiceType = alternative?.deliveryServiceType, + deliveryServiceQualifier = alternative?.deliveryServiceQualifier, + deliveryServiceNumber = alternative?.deliveryServiceNumber + + ), + confidenceCriteria = with(result.confidenceCriteria) { + BusinessPartnerConfidenceCriteriaDto( + sharedByOwner = sharedByOwner, + checkedByExternalDataSource = checkedByExternalDataSource, + numberOfSharingMembers = numberOfBusinessPartners, + lastConfidenceCheckAt = lastConfidenceCheckAt, + nextConfidenceCheckAt = nextConfidenceCheckAt, + confidenceLevel = confidenceLevel + ) + } + ) + + return BusinessPartnerSearchResultDto( + identifiers = identifiers, + legalEntity = legalEntityDto, + site = siteDto, + address = addressDto, + isParticipantData = legalEntity.isCatenaXMemberData + ) + } } \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt index b45ab2cc0..854eb9678 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SearchService.kt @@ -21,9 +21,12 @@ package org.eclipse.tractusx.bpdm.pool.service import org.eclipse.tractusx.bpdm.common.dto.PageDto import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType import org.eclipse.tractusx.bpdm.pool.api.model.request.AddressPartnerSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.request.BusinessPartnerSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressMatchVerboseDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchVerboseDto import org.eclipse.tractusx.bpdm.pool.api.model.response.SiteMatchVerboseDto @@ -55,4 +58,13 @@ interface SearchService { paginationRequest: PaginationRequest ): PageDto + /** + * Find business partner by matching their field values to [searchRequest] field query texts + */ + fun searchBusinessPartner( + searchRequest: LegalEntityPropertiesSearchRequest, + searchResultFilter: Set?, + paginationRequest: PaginationRequest + ): PageDto + } \ No newline at end of file diff --git a/bpdm-pool/src/main/resources/db/migration/V7_3_0_0__add_normalized_function.sql b/bpdm-pool/src/main/resources/db/migration/V7_3_0_0__add_normalized_function.sql new file mode 100644 index 000000000..79cd36eb8 --- /dev/null +++ b/bpdm-pool/src/main/resources/db/migration/V7_3_0_0__add_normalized_function.sql @@ -0,0 +1,25 @@ +CREATE OR REPLACE FUNCTION bpdm.normalize_name(input text) +RETURNS text AS +$$ +BEGIN + IF input IS NULL THEN + RETURN NULL; + END IF; + + RETURN regexp_replace( + lower( + replace( + replace( + replace( + replace(trim(input), 'ä', 'ae'), + 'ö', 'oe' + ), + 'ü', 'ue' + ), + 'ß', 'ss' + ) + ), + '\s+', ' ', 'g' + ); +END; +$$ LANGUAGE plpgsql IMMUTABLE; diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerControllerIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerControllerIT.kt new file mode 100644 index 000000000..93dcddfaa --- /dev/null +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/BusinessPartnerControllerIT.kt @@ -0,0 +1,578 @@ +/******************************************************************************* + * 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.pool.controller + +import com.neovisionaries.i18n.CountryCode +import org.eclipse.tractusx.bpdm.common.dto.AddressType +import org.eclipse.tractusx.bpdm.common.dto.PageDto +import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest +import org.eclipse.tractusx.bpdm.pool.Application +import org.eclipse.tractusx.bpdm.pool.api.client.PoolClientImpl +import org.eclipse.tractusx.bpdm.pool.api.model.BusinessPartnerSearchFilterType +import org.eclipse.tractusx.bpdm.pool.api.model.LegalEntityVerboseDto +import org.eclipse.tractusx.bpdm.pool.api.model.LogisticAddressVerboseDto +import org.eclipse.tractusx.bpdm.pool.api.model.StreetDto +import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest +import org.eclipse.tractusx.bpdm.pool.api.model.response.AlternativePostalAddressDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerConfidenceCriteriaDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerIdentifierDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerLegalEntity +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerPostalAddress +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSearchResultDto +import org.eclipse.tractusx.bpdm.pool.api.model.response.BusinessPartnerSite +import org.eclipse.tractusx.bpdm.pool.api.model.response.PhysicalPostalAddressDto +import org.eclipse.tractusx.bpdm.pool.util.TestHelpers +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer +import org.eclipse.tractusx.bpdm.test.testdata.pool.BusinessPartnerNonVerboseValues +import org.eclipse.tractusx.bpdm.test.testdata.pool.BusinessPartnerVerboseValues +import org.eclipse.tractusx.bpdm.test.testdata.pool.LegalEntityStructureRequest +import org.eclipse.tractusx.bpdm.test.testdata.pool.SiteStructureRequest +import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers +import org.eclipse.tractusx.bpdm.test.testdata.pool.PoolDataHelper +import org.eclipse.tractusx.bpdm.test.testdata.pool.TestDataEnvironment +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.web.reactive.function.client.WebClientResponseException + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class, TestHelpers::class] +) + +@ActiveProfiles("test-no-auth") +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) +class BusinessPartnerControllerIT@Autowired constructor( + val testHelpers: TestHelpers, + val poolClient: PoolClientImpl, + val dbTestHelpers: DbTestHelpers, + private val dataHelper: PoolDataHelper, +) { + + private lateinit var testDataEnvironment: TestDataEnvironment + + private val partnerStructure1 = LegalEntityStructureRequest( + legalEntity = BusinessPartnerNonVerboseValues.legalEntityCreate4, + siteStructures = listOf( + SiteStructureRequest(BusinessPartnerNonVerboseValues.siteCreate1) + ) + ) + private val partnerStructure2 = LegalEntityStructureRequest( + legalEntity = BusinessPartnerNonVerboseValues.legalEntityCreate4 + ) + + private lateinit var givenPartner1: LegalEntityVerboseDto + private lateinit var legalAddress1: LogisticAddressVerboseDto + + @BeforeEach + fun beforeEach() { + dbTestHelpers.truncateDbTables() + testDataEnvironment = dataHelper.createTestDataEnvironment() + val givenStructure = testHelpers.createBusinessPartnerStructure(listOf(partnerStructure1)) + givenPartner1 = with(givenStructure[0].legalEntity) { legalEntity } + legalAddress1 = givenStructure[0].legalEntity.legalAddress + + val parentBpn = givenStructure.firstOrNull()!!.legalEntity.legalEntity.bpnl + val addressToCreate = with(BusinessPartnerNonVerboseValues.addressPartnerCreate1) { + copy(bpnParent = parentBpn) + } + val createAdditionalAddress = poolClient.addresses.createAddresses(listOf(addressToCreate)) + print(createAdditionalAddress) + } + + /** + * Given null request + * Return 400 and error message + */ + @Test + fun `Request parameters are all null should return 400 bad request`() { + + val request = LegalEntityPropertiesSearchRequest(null, null, null, null, null, null) + + val ex = assertThrows { + poolClient.businessPartners.searchBusinessPartners( + request, + setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities), + PaginationRequest(0, 100) + ) + } + Assertions.assertEquals(HttpStatus.BAD_REQUEST, ex.statusCode) + } + + /** + * Given empty string request + * Return 400 and error message + */ + @Test + fun `Request parameters are all empty string should return 400 bad request`() { + + val request = LegalEntityPropertiesSearchRequest("", "", "", "", "", null) + val ex = assertThrows { + poolClient.businessPartners.searchBusinessPartners( + request, + setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities), + PaginationRequest(0, 100) + ) + } + Assertions.assertEquals(HttpStatus.BAD_REQUEST, ex.statusCode) + } + + /** + * Search by BPNL + * + */ + @Test + fun `Search by BPNL`() { + + val expected = PageDto(1, 1, 0, 1, listOf(expectedLegalEntity())) + val searchLegalName = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(searchLegalName, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + } + + /** + * Search by incorrect BPNL format + * + */ + @Test + fun `Search by incorrect BPNL format`() { + + val expected = PageDto(0, 0, 0, 0,emptyList()) + val searchLegalName = LegalEntityPropertiesSearchRequest(null, "BPNL000065", null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(searchLegalName, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + } + + /** + * Search by BPNL and incomplete legal name + * + */ + @Test + fun `Search by BPNL and incomplete legal name`() { + + val expected = PageDto(1, 1, 0, 1, listOf(expectedLegalEntity())) + val searchLegalName = LegalEntityPropertiesSearchRequest("Müller", "BPNL000000000065", null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(searchLegalName, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + } + + /** + * Search by fuzzy legal name + * Müller / Mülle / Mü__e / Mü_e / Mü_e / Muller + */ + @Test + fun `Search by fuzzy legal name`() { + + val expected = PageDto(1, 1, 0, 1, listOf(expectedLegalEntity())) + val expectedEmpty = PageDto(0, 0, 0, 0,emptyList()) + + var request = LegalEntityPropertiesSearchRequest("Müller", null, null, null, null, null) + var result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + request = LegalEntityPropertiesSearchRequest("Mülle", null, null, null, null, null) + result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + request = LegalEntityPropertiesSearchRequest("Mü__e", null, null, null, null, null) + result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + request = LegalEntityPropertiesSearchRequest("Mü_e", null, null, null, null, null) + result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expectedEmpty, result) + + /* expected empty result */ + + request = LegalEntityPropertiesSearchRequest("Mü_e", null, null, null, null, null) + result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expectedEmpty, result) + + request = LegalEntityPropertiesSearchRequest("Muller", null, null, null, null, null) + result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expectedEmpty, result) + } + + /** + * Search by fuzzy + * Street / Str / St__r + */ + @Test + fun `Search site by fuzzy street_1`() { + + val expectedMultipleResults = PageDto(1,1,0,1,listOf(expectedSite())) + val request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", "Barenyi", null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request,setOf(BusinessPartnerSearchFilterType.IncludeSites),PaginationRequest(0,100)) + Assertions.assertEquals(expectedMultipleResults, result) + } + + @Test + fun `Search Site by fuzzy street_2`() { + + val expectedMultipleResults = PageDto(1,1,0,1,listOf(expectedSite())) + val request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", "Bela", null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeSites),PaginationRequest(0,100)) + Assertions.assertEquals(expectedMultipleResults, result) + } + + @Test + fun `Search by fuzzy street_3`() { + + val expectedEmptyResult = PageDto(0,0,0,0,emptyList()) + val request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065","St__r" , null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expectedEmptyResult, result) + } + + /** + * Legal name search support German umlauts + * + */ + @Test + fun `Legal name search support German umlauts`() { + + val expected = PageDto(1, 1, 0, 1,listOf(expectedLegalEntity())) + val request = LegalEntityPropertiesSearchRequest("Muelle", null, null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request,setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + } + + /** + * City search support German umlauts and fuzzy + * + */ + @Test + fun `City search support German umlauts and fuzzy`() { + + val expected = PageDto(1, 1, 0, 1, listOf(expectedLegalEntity())) + val expectedEmpty = PageDto(0,0,0,0, emptyList()) + var request= LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, "Boe", null) + var result = poolClient.businessPartners.searchBusinessPartners(request, setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, "Boblingen", null) + result = poolClient.businessPartners.searchBusinessPartners(request, + setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expectedEmpty, result) + + request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, "Bö_lingen", null) + result = poolClient.businessPartners.searchBusinessPartners(request,setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, "B__lingen", null) + result = poolClient.businessPartners.searchBusinessPartners(request,setOf(BusinessPartnerSearchFilterType.IncludeLegalEntities),PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + } + + + /** + * Search by BPNS return the site + * + */ + @Test + fun `Search by BPNS return the site`() { + + val expected = PageDto(1, 1, 0, 1,listOf(expectedSite())) + val request = LegalEntityPropertiesSearchRequest(null, "BPNS0000000000WN", null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request, + setOf(BusinessPartnerSearchFilterType.IncludeSites), PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + + } + + /** + * Search BPNA return the address + * + */ + @Test + fun `Search BPNA return the address`() { + + dbTestHelpers.truncateDbTables() + testDataEnvironment = dataHelper.createTestDataEnvironment() + val givenStructure =testHelpers.createBusinessPartnerStructure(listOf(partnerStructure2)) + + val parentBpn = givenStructure.firstOrNull()!!.legalEntity.legalEntity.bpnl + val addressToCreate = with(BusinessPartnerNonVerboseValues.addressPartnerCreate1) { + copy(bpnParent = parentBpn) + } + + poolClient.addresses.createAddresses(listOf(addressToCreate)) + + val expected = PageDto(1, 1, 0, 1,listOf(expectAdditionalAddress())) + val request = LegalEntityPropertiesSearchRequest(null, "BPNL000000000065", null, null, null, null) + val result = poolClient.businessPartners.searchBusinessPartners(request,setOf(BusinessPartnerSearchFilterType.IncludeAdditionalAddresses), PaginationRequest(0,100)) + Assertions.assertEquals(expected, result) + } + + private fun expectedLegalEntity():BusinessPartnerSearchResultDto { + + val identifiers = mutableListOf() + val identifier = BusinessPartnerIdentifierDto( + type = BusinessPartnerNonVerboseValues.identifier3.type, + value = BusinessPartnerNonVerboseValues.identifier3.value, + issuingBody = BusinessPartnerNonVerboseValues.identifier3.issuingBody + ); + identifiers.add(identifier); + + return BusinessPartnerSearchResultDto( + identifiers = identifiers, + isParticipantData = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.isParticipantData, + legalEntity = BusinessPartnerLegalEntity( + legalEntityBpn = "BPNL000000000065", + legalName = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.legalName, + legalForm = BusinessPartnerVerboseValues.legalForm3.name, + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto( + sharedByOwner = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.confidenceLevel + ) + ), + site = null, + address = BusinessPartnerPostalAddress( + addressBpn = "BPNA00000000009W", + addressType = AddressType.LegalAddress, + name = null, + physicalPostalAddress = PhysicalPostalAddressDto( + street = StreetDto( + name = BusinessPartnerVerboseValues.address1.street?.name, + houseNumber = BusinessPartnerVerboseValues.address1.street?.houseNumber, + namePrefix = BusinessPartnerVerboseValues.address1.street?.namePrefix, + additionalNamePrefix = BusinessPartnerVerboseValues.address1.street?.additionalNamePrefix, + additionalNameSuffix = BusinessPartnerVerboseValues.address1.street?.additionalNameSuffix, + milestone = BusinessPartnerVerboseValues.address1.street?.milestone, + direction = BusinessPartnerVerboseValues.address1.street?.direction, + houseNumberSupplement = BusinessPartnerVerboseValues.address1.street?.houseNumberSupplement, + nameSuffix = BusinessPartnerVerboseValues.address1.street?.nameSuffix + ), + postalCode = BusinessPartnerVerboseValues.address1.postalCode, + city = BusinessPartnerVerboseValues.address1.city, + country = BusinessPartnerVerboseValues.address1.country, + administrativeAreaLevel1 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel1, + administrativeAreaLevel2= BusinessPartnerVerboseValues.address1.administrativeAreaLevel2, + administrativeAreaLevel3 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel3, + district = BusinessPartnerVerboseValues.address1.district, + companyPostalCode = BusinessPartnerVerboseValues.address1.companyPostalCode, + industrialZone = BusinessPartnerVerboseValues.address1.industrialZone, + building = BusinessPartnerVerboseValues.address1.building, + floor = BusinessPartnerVerboseValues.address1.floor, + door = BusinessPartnerVerboseValues.address1.door, + taxJurisdictionCode = BusinessPartnerVerboseValues.address1.taxJurisdictionCode + ), + alternativePostalAddress = AlternativePostalAddressDto( + geographicCoordinates = null, + country = null, + administrativeAreaLevel1 = null, + postalCode = null, city = null, + deliveryServiceType = null, + deliveryServiceQualifier = null, + deliveryServiceNumber = null + ), + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerNonVerboseValues.logisticAddress4.confidenceCriteria.confidenceLevel + ), + states = emptyList() + ) + ) + } + + private fun expectedSite():BusinessPartnerSearchResultDto { + + val identifiers = mutableListOf() + val identifier = BusinessPartnerIdentifierDto( + type = BusinessPartnerNonVerboseValues.identifier3.type, + value = BusinessPartnerNonVerboseValues.identifier3.value, + issuingBody = BusinessPartnerNonVerboseValues.identifier3.issuingBody + ); + identifiers.add(identifier) + + return BusinessPartnerSearchResultDto( + identifiers = identifiers, + isParticipantData = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.isParticipantData, + legalEntity = BusinessPartnerLegalEntity( + legalEntityBpn = "BPNL000000000065", + legalName = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.legalName, + legalForm = BusinessPartnerVerboseValues.legalForm3.name, + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.confidenceLevel + ) + ), + site = BusinessPartnerSite( + siteBpn = "BPNS0000000000WN", + name = "Stammwerk A", + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerVerboseValues.site1.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerVerboseValues.site1.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerVerboseValues.site1.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerVerboseValues.site1.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerVerboseValues.site1.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerVerboseValues.site1.confidenceCriteria.confidenceLevel + ) + ), + address = BusinessPartnerPostalAddress( + addressBpn = "BPNA000000000197", + addressType = AddressType.SiteMainAddress, + name = null, + physicalPostalAddress = PhysicalPostalAddressDto( + street = StreetDto( + name = BusinessPartnerVerboseValues.address1.street?.name, + houseNumber = BusinessPartnerVerboseValues.address1.street?.houseNumber, + namePrefix = BusinessPartnerVerboseValues.address1.street?.namePrefix, + additionalNamePrefix =BusinessPartnerVerboseValues.address1.street?.additionalNamePrefix, + additionalNameSuffix = BusinessPartnerVerboseValues.address1.street?.additionalNameSuffix, + milestone = BusinessPartnerVerboseValues.address1.street?.milestone, + direction = BusinessPartnerVerboseValues.address1.street?.direction, + houseNumberSupplement = BusinessPartnerVerboseValues.address1.street?.houseNumberSupplement, + nameSuffix = BusinessPartnerVerboseValues.address1.street?.nameSuffix + ), + postalCode = BusinessPartnerVerboseValues.address1.postalCode, + city = BusinessPartnerVerboseValues.address1.city, + country = BusinessPartnerVerboseValues.address1.country, + administrativeAreaLevel1 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel1, + administrativeAreaLevel2= BusinessPartnerVerboseValues.address1.administrativeAreaLevel2, + administrativeAreaLevel3 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel3, + district = BusinessPartnerVerboseValues.address1.district, + companyPostalCode = BusinessPartnerVerboseValues.address1.companyPostalCode, + industrialZone = BusinessPartnerVerboseValues.address1.industrialZone, + building = BusinessPartnerVerboseValues.address1.building, + floor = BusinessPartnerVerboseValues.address1.floor, + door = BusinessPartnerVerboseValues.address1.door, + taxJurisdictionCode = BusinessPartnerVerboseValues.address1.taxJurisdictionCode + ), + alternativePostalAddress = AlternativePostalAddressDto( + geographicCoordinates = null, + country = null, + administrativeAreaLevel1 = null, + postalCode = null, city = null, + deliveryServiceType = null, + deliveryServiceQualifier = null, + deliveryServiceNumber = null + ), + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerVerboseValues.addressPartner1.confidenceCriteria.confidenceLevel + ), + states = emptyList() + ) + ) + } + + private fun expectAdditionalAddress(): BusinessPartnerSearchResultDto { + val identifiers = mutableListOf() + val identifier = BusinessPartnerIdentifierDto( + type = BusinessPartnerNonVerboseValues.identifier3.type, + value = BusinessPartnerNonVerboseValues.identifier3.value, + issuingBody = BusinessPartnerNonVerboseValues.identifier3.issuingBody + ); + identifiers.add(identifier) + + return BusinessPartnerSearchResultDto( + identifiers = identifiers, + isParticipantData = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.isParticipantData, + legalEntity = BusinessPartnerLegalEntity( + legalEntityBpn = "BPNL000000000065", + legalName = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.legalName, + legalForm = BusinessPartnerVerboseValues.legalForm3.name, + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalEntity.confidenceCriteria.confidenceLevel + ) + ), + site = null, + address = BusinessPartnerPostalAddress( + addressBpn = "BPNA000000000197", + addressType = AddressType.AdditionalAddress, + name = null, + physicalPostalAddress = PhysicalPostalAddressDto( + street = StreetDto( + name = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.name, + houseNumber = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.houseNumber, + namePrefix = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.namePrefix, + additionalNamePrefix = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.additionalNamePrefix, + additionalNameSuffix = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.additionalNameSuffix, + milestone = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.milestone, + direction = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.direction, + houseNumberSupplement = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.houseNumberSupplement, + nameSuffix = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.street?.nameSuffix + ), + postalCode = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.postalCode, + city = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.city, + country = BusinessPartnerNonVerboseValues.logisticAddress4.physicalPostalAddress.country, + administrativeAreaLevel1 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel1, + administrativeAreaLevel2= BusinessPartnerVerboseValues.address1.administrativeAreaLevel2, + administrativeAreaLevel3 = BusinessPartnerVerboseValues.address1.administrativeAreaLevel3, + district = BusinessPartnerVerboseValues.address1.district, + companyPostalCode = BusinessPartnerVerboseValues.address1.companyPostalCode, + industrialZone = BusinessPartnerVerboseValues.address1.industrialZone, + building = BusinessPartnerVerboseValues.address1.building, + floor = BusinessPartnerVerboseValues.address1.floor, + door = BusinessPartnerVerboseValues.address1.door, + taxJurisdictionCode = BusinessPartnerVerboseValues.address1.taxJurisdictionCode + ), + alternativePostalAddress = AlternativePostalAddressDto( + geographicCoordinates = null, + country = null, + administrativeAreaLevel1 = null, + postalCode = null, city = null, + deliveryServiceType = null, + deliveryServiceQualifier = null, + deliveryServiceNumber = null + ), + confidenceCriteria = BusinessPartnerConfidenceCriteriaDto ( + sharedByOwner = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.sharedByOwner, + checkedByExternalDataSource = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.checkedByExternalDataSource, + numberOfSharingMembers = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.numberOfSharingMembers, + lastConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.lastConfidenceCheckAt, + nextConfidenceCheckAt = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.nextConfidenceCheckAt, + confidenceLevel = BusinessPartnerNonVerboseValues.legalEntityCreate4.legalAddress.confidenceCriteria.confidenceLevel + ), + states = emptyList() + ) + ) + } + + +} \ No newline at end of file diff --git a/docs/api/pool.json b/docs/api/pool.json index d0fb76abb..de67a5e3b 100644 --- a/docs/api/pool.json +++ b/docs/api/pool.json @@ -32,6 +32,9 @@ }, { "name" : "Address Controller", "description" : "Read, create and update business partner of type address" + }, { + "name" : "Business Partner Controller", + "description" : "Returns business partner records by searching with different parameters" } ], "paths" : { "/v7/sites" : { @@ -226,8 +229,29 @@ } } }, - "400" : { - "description" : "On malformed search or pagination request" + "400": { + "description": "Invalid request (e.g., missing both ID and legalName)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The HTTP response code." + }, + "status": { + "type": "string", + "description": "The HTTP response status." + }, + "message": { + "type": "string", + "description": "The error message." + } + } + } + } + } } } }, @@ -781,6 +805,87 @@ } } }, + "/v7/business-partners/search": { + "post": { + "tags": [ + "Business Partners Controller" + ], + "summary": "Returns business partner records by searching with different parameters", + "description": "This endpoint tries to find matches among all existing business partners of type legal entity, filtering out partners which entirely do not match and ranking the remaining partners according to the accuracy of the match.", + "operationId": "post", + "parameters": [ + { + "name": "searchResultFilter", + "in": "query", + "required": false, + "explode": true, + "description": "User can filter out the result by type", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "IncludeLegalEntities", + "IncludeSites", + "IncludeAdditionalAddresses" + ] + } + } + }, + { + "name": "page", + "in": "query", + "description": "Number of page to get results from", + "required": false, + "schema": { + "type": "string", + "minimum": 0 + } + }, + { + "name": "size", + "in": "query", + "description": "Size of each page", + "required": false, + "schema": { + "type": "string", + "minimum": 0, + "maximum": 100 + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LegalEntityPropertiesSearchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Matching business partners", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PageDtoBusinessPartnerSearchResultDto" + } + } + } + } + }, + "204": { + "description": "valid request but do not provide any parameter value" + }, + "400": { + "description": "Invalid request (e.g., missing both ID and legalName)" + } + } + } + }, "/v6/cx-memberships" : { "get" : { "tags" : [ "cx-membership-controller" ], @@ -3288,6 +3393,60 @@ }, "required" : [ "type" ] }, + "AddressComponentOutputDto": { + "type": "object", + "description": "Address properties of business partner output data", + "properties": { + "addressBpn": { + "type": "string", + "description": "The BPNA of the address, on which the business partner provides a view." + }, + "name": { + "type": "string", + "description": "The name of the address, on which the business partner provides a view. This is not according to official registers but according to the name the sharing members agree on, such as the name of a gate or any other additional names that designate the address in common parlance." + }, + "addressType": { + "type": "string", + "description": "One of the address types: Legal Address, Site Main Address, Legal and Site Main Address, Additional Address.", + "enum": [ + "LegalAndSiteMainAddress", + "LegalAddress", + "SiteMainAddress", + "AdditionalAddress" + ] + }, + "physicalPostalAddress": { + "$ref": "#/components/schemas/PhysicalPostalAddressDto", + "description": "The physical postal address of the address, on which the business partner provides a view, such as an office, warehouse, gate, etc." + }, + "alternativePostalAddress": { + "$ref": "#/components/schemas/AlternativePostalAddressDto", + "description": "The alternative postal address of the address, on which the business partner provides a view, for example if the goods are to be picked up somewhere else." + }, + "confidenceCriteria": { + "$ref": "#/components/schemas/ConfidenceCriteriaDto" + }, + "states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessPartnerStateDto" + } + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AddressIdentifierDto" + } + } + }, + "required": [ + "addressBpn", + "bpnA", + "confidenceCriteria", + "physicalPostalAddress", + "states" + ] + }, "AlternativePostalAddressDto" : { "type" : "object", "description" : "An alternative postal address describes an alternative way of delivery for example if the goods are to be picked up somewhere else.", @@ -3412,6 +3571,81 @@ }, "required" : [ "requestedIdentifiers" ] }, + "BusinessPartnerSearchResultDto": { + "type": "object", + "description": "business partner search result", + "properties": { + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessPartnerIdentifierDto" + } + }, + "states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessPartnerStateDto" + } + }, + "legalEntity": { + "$ref": "#/components/schemas/LegalEntityRepresentationOutputDto", + "description": "The legal entity, on which the business partner provides a view." + }, + "site": { + "$ref": "#/components/schemas/SiteRepresentationOutputDto", + "description": "The site, on which the business partner provides a view." + }, + "address": { + "$ref": "#/components/schemas/AddressComponentOutputDto", + "description": "The address, on which the business partner provides a view." + } + }, + "required": [ + "identifiers", + "legalEntity", + "address" + ] + }, + "BusinessPartnerIdentifierDto": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of the identifier." + }, + "value": { + "type": "string", + "description": "The value of the identifier like \"DE123465789\"." + }, + "issuingBody": { + "type": "string", + "description": "The name of the official register, where the identifier is registered. For example, a Handelsregisternummer in Germany is only valid with its corresponding Registergericht and Registerart." + } + } + }, + "BusinessPartnerStateDto": { + "type": "object", + "properties": { + "validFrom": { + "type": "string", + "format": "date-time", + "description": "Date since when the status is/was valid." + }, + "validTo": { + "type": "string", + "format": "date-time", + "description": "Date until the status was valid, if applicable." + }, + "type": { + "type": "string", + "description": "The type of this specified status.", + "enum": [ + "ACTIVE", + "INACTIVE" + ] + } + } + }, "ChangelogEntryVerboseDto" : { "type" : "object", "description" : "An entry of the changelog, which is created each time a business partner is modified and contains data about the change. The actual new state of the business partner is not included.", @@ -4307,6 +4541,308 @@ }, "required" : [ "isActive", "name", "technicalKey" ] }, + "LegalEntityPropertiesSearchRequest": { + "type": "object", + "properties": { + "bpn": { + "type": "string" + }, + "legalName": { + "type": "string" + }, + "streetName": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "city": { + "type": "string" + }, + "country": { + "type": "string", + "description": "The 2-digit country code of the physical postal address according to ISO 3166-1.", + "enum": [ + "UNDEFINED", + "AC", + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AN", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BL", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BU", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CP", + "CR", + "CS", + "CU", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DG", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EA", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "EU", + "EZ", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "FX", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "IC", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IR", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KP", + "KR", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MF", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NT", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SD", + "SE", + "SF", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "SS", + "ST", + "SU", + "SV", + "SX", + "SY", + "SZ", + "TA", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TP", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UK", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "XI", + "XU", + "XK", + "YE", + "YT", + "YU", + "ZA", + "ZM", + "ZR", + "ZW" + ] + } + }, + "required": [ + "bpn", + "legalName" + ] + }, "LogisticAddressDto" : { "type" : "object", "description" : "In general, an address is a collection of information to describe a physical location, using a street name with a house number and/or a post office box as reference. In addition, an address consists of several postal attributes, such as country, region (state), county, township, city, district, or postal code, which help deliver mail.In Catena-X, an address is a type of business partner representing the legal address of a legal entity, and/or the main address of a site, or any additional address of a legal entity or site (such as different gates).An address is owned by a legal entity. Thus, exactly one legal entity is assigned to an address. An address can belong to a site. Thus, one or no site is assigned to an address. An address is uniquely identified by the BPNA.", @@ -4410,6 +4946,43 @@ }, "required" : [ "bpna", "confidenceCriteria", "createdAt", "identifiers", "isParticipantData", "physicalPostalAddress", "states", "updatedAt" ] }, + "LegalEntityRepresentationOutputDto": { + "type": "object", + "description": "Legal Entity properties of business partner output data", + "properties": { + "legalEntityBpn": { + "type": "string", + "description": "The BPNL of the legal entity, on which the business partner provides a view." + }, + "legalName": { + "type": "string", + "description": "The name of the legal entity, on which the business partner provides a view, according to official registers." + }, + "shortName": { + "type": "string", + "description": "The abbreviated name of the legal entity, on which the business partner provides a view." + }, + "legalForm": { + "type": "string", + "description": "The legal form of the legal entity, on which the business partner provides a view." + }, + "confidenceCriteria": { + "$ref": "#/components/schemas/ConfidenceCriteriaDto" + }, + "states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessPartnerStateDto" + } + } + }, + "required": [ + "bpnL", + "confidenceCriteria", + "legalEntityBpn", + "states" + ] + }, "PageDtoChangelogEntryVerboseDto" : { "type" : "object", "description" : "Paginated collection of results", @@ -4750,6 +5323,46 @@ }, "required" : [ "content", "contentSize", "page", "totalElements", "totalPages" ] }, + "PageDtoBusinessPartnerSearchResultDto": { + "type": "object", + "description": "Paginated collection of results", + "properties": { + "totalElements": { + "type": "integer", + "format": "int64", + "description": "Total number of all results in all pages" + }, + "totalPages": { + "type": "integer", + "format": "int32", + "description": "Total number pages" + }, + "page": { + "type": "integer", + "format": "int32", + "description": "Current page number" + }, + "contentSize": { + "type": "integer", + "format": "int32", + "description": "Number of results in the page" + }, + "content": { + "type": "array", + "description": "Collection of results in the page", + "items": { + "$ref": "#/components/schemas/BusinessPartnerSearchResultDto" + } + } + }, + "required": [ + "content", + "contentSize", + "page", + "totalElements", + "totalPages" + ] + }, "PhysicalPostalAddressDto" : { "type" : "object", "description" : "A physical postal address describes the physical location of an office, warehouse, gate, etc.", @@ -5333,6 +5946,34 @@ } } }, + "SiteRepresentationOutputDto": { + "type": "object", + "description": "Site properties of business partner output data", + "properties": { + "siteBpn": { + "type": "string", + "description": "The BPNS of the site, on which the business partner provides a view." + }, + "name": { + "type": "string", + "description": "The name of the site, on which the business partner provides a view. This is not according to official registers but according to the name the owner chooses." + }, + "confidenceCriteria": { + "$ref": "#/components/schemas/ConfidenceCriteriaDto" + }, + "states": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessPartnerStateDto" + } + } + }, + "required": [ + "confidenceCriteria", + "siteBpn", + "states" + ] + }, "TypeKeyNameVerboseDtoBusinessStateType" : { "type" : "object", "description" : "Named type uniquely identified by its technical key", diff --git a/docs/api/pool.yaml b/docs/api/pool.yaml index 89e262268..c8b581f4c 100644 --- a/docs/api/pool.yaml +++ b/docs/api/pool.yaml @@ -24,6 +24,8 @@ tags: description: Support functionality for BPN operations - name: Address Controller description: "Read, create and update business partner of type address" + - name: Business Partner Controller + description: "Returns business partner records by searching with different parameters" paths: /v7/sites: get: @@ -1070,6 +1072,72 @@ paths: "400": description: On malformed request parameters or if number of requested bpns exceeds limit + /v7/business-partners/search: + post: + tags: + - Business Partners Controller + summary: Returns business partner records by searching with different parameters + description: 'This endpoint tries to find matches among all existing business partners of type legal entity, filtering out partners which entirely do not match and ranking the remaining partners according to the accuracy of the match.' + operationId: businessPartnerSearch + parameters: + - name: searchResultFilter + in: query + required: false + explode: true + description: User can filter out the result by type + schema: + type: array + items: + type: string + enum: + - IncludeLegalEntities + - IncludeSites + - IncludeAdditionalAddresses + - name: page + in: query + description: Number of page to get results from + required: false + schema: + type: string + minimum: 0 + - name: size + in: query + description: Size of each page + required: false + schema: + type: string + maximum: 100 + minimum: 0 + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/LegalEntityPropertiesSearchRequest" + responses: + '200': + description: Matching business partners + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/PageDtoBusinessPartnerSearchResultDto" + '400': + description: Invalid request (e.g., missing both ID and legalName) + content: + application/json: + schema: + type: object + properties: + code: + type: string + description: The HTTP response code. + status: + type: string + description: The HTTP response status. + message: + type: string + description: The error message. /v7/identifier-types: get: tags: @@ -6784,6 +6852,298 @@ components: - isActive - name - technicalKey + LegalEntityPropertiesSearchRequest: + type: object + properties: + bpn: + type: string + legalName: + type: string + streetName: + type: string + postalCode: + type: string + city: + type: string + country: + type: string + description: The 2-digit country code of the physical postal address according to ISO 3166-1. + enum: + - UNDEFINED + - AC + - AD + - AE + - AF + - AG + - AI + - AL + - AM + - AN + - AO + - AQ + - AR + - AS + - AT + - AU + - AW + - AX + - AZ + - BA + - BB + - BD + - BE + - BF + - BG + - BH + - BI + - BJ + - BL + - BM + - BN + - BO + - BQ + - BR + - BS + - BT + - BU + - BV + - BW + - BY + - BZ + - CA + - CC + - CD + - CF + - CG + - CH + - CI + - CK + - CL + - CM + - CN + - CO + - CP + - CR + - CS + - CU + - CV + - CW + - CX + - CY + - CZ + - DE + - DG + - DJ + - DK + - DM + - DO + - DZ + - EA + - EC + - EE + - EG + - EH + - ER + - ES + - ET + - EU + - EZ + - FI + - FJ + - FK + - FM + - FO + - FR + - FX + - GA + - GB + - GD + - GE + - GF + - GG + - GH + - GI + - GL + - GM + - GN + - GP + - GQ + - GR + - GS + - GT + - GU + - GW + - GY + - HK + - HM + - HN + - HR + - HT + - HU + - IC + - ID + - IE + - IL + - IM + - IN + - IO + - IQ + - IR + - IS + - IT + - JE + - JM + - JO + - JP + - KE + - KG + - KH + - KI + - KM + - KN + - KP + - KR + - KW + - KY + - KZ + - LA + - LB + - LC + - LI + - LK + - LR + - LS + - LT + - LU + - LV + - LY + - MA + - MC + - MD + - ME + - MF + - MG + - MH + - MK + - ML + - MM + - MN + - MO + - MP + - MQ + - MR + - MS + - MT + - MU + - MV + - MW + - MX + - MY + - MZ + - NA + - NC + - NE + - NF + - NG + - NI + - NL + - 'NO' + - NP + - NR + - NT + - NU + - NZ + - OM + - PA + - PE + - PF + - PG + - PH + - PK + - PL + - PM + - PN + - PR + - PS + - PT + - PW + - PY + - QA + - RE + - RO + - RS + - RU + - RW + - SA + - SB + - SC + - SD + - SE + - SF + - SG + - SH + - SI + - SJ + - SK + - SL + - SM + - SN + - SO + - SR + - SS + - ST + - SU + - SV + - SX + - SY + - SZ + - TA + - TC + - TD + - TF + - TG + - TH + - TJ + - TK + - TL + - TM + - TN + - TO + - TP + - TR + - TT + - TV + - TW + - TZ + - UA + - UG + - UK + - UM + - US + - UY + - UZ + - VA + - VC + - VE + - VG + - VI + - VN + - VU + - WF + - WS + - XI + - XU + - XK + - YE + - YT + - YU + - ZA + - ZM + - ZR + - ZW + required: + - bpn + - legalName LogisticAddressDto: type: object description: "In general, an address is a collection of information to describe\ @@ -7222,6 +7582,37 @@ components: - page - totalElements - totalPages + PageDtoBusinessPartnerSearchResultDto: + type: object + description: Paginated collection of results + properties: + totalElements: + type: integer + format: int64 + description: Total number of all results in all pages + totalPages: + type: integer + format: int32 + description: Total number pages + page: + type: integer + format: int32 + description: Current page number + contentSize: + type: integer + format: int32 + description: Number of results in the page + content: + type: array + description: Collection of results in the page + items: + $ref: "#/components/schemas/BusinessPartnerSearchResultDto" + required: + - content + - contentSize + - page + - totalElements + - totalPages PhysicalPostalAddressDto: type: object description: "A physical postal address describes the physical location of an\ @@ -8705,6 +9096,166 @@ components: required: - name - technicalKey + BusinessPartnerSearchResultDto: + type: object + description: business partner search result + properties: + identifiers: + type: array + items: + $ref: "#/components/schemas/BusinessPartnerIdentifierDto" + states: + type: array + items: + $ref: "#/components/schemas/BusinessPartnerStateDto" + legalEntity: + $ref: "#/components/schemas/LegalEntityRepresentationOutputDto" + description: "The legal entity, on which the business partner provides a\ + \ view." + site: + $ref: "#/components/schemas/SiteRepresentationOutputDto" + description: "The site, on which the business partner provides a view." + address: + $ref: "#/components/schemas/AddressComponentOutputDto" + description: "The address, on which the business partner provides a view. " + required: + - identifiers + - legalEntity + - address + LegalEntityRepresentationOutputDto: + type: object + description: Legal Entity properties of business partner output data + properties: + legalEntityBpn: + type: string + description: "The BPNL of the legal entity, on which the business partner\ + \ provides a view." + legalName: + type: string + description: "The name of the legal entity, on which the business partner\ + \ provides a view, according to official registers." + shortName: + type: string + description: "The abbreviated name of the legal entity, on which the business\ + \ partner provides a view." + legalForm: + type: string + description: "The legal form of the legal entity, on which the business\ + \ partner provides a view." + confidenceCriteria: + $ref: "#/components/schemas/ConfidenceCriteriaDto" + states: + type: array + items: + $ref: "#/components/schemas/BusinessPartnerStateDto" + required: + - bpnL + - confidenceCriteria + - legalEntityBpn + - states + SiteRepresentationOutputDto: + type: object + description: Site properties of business partner output data + properties: + siteBpn: + type: string + description: "The BPNS of the site, on which the business partner provides\ + \ a view." + name: + type: string + description: "The name of the site, on which the business partner provides\ + \ a view. This is not according to official registers but according to\ + \ the name the owner chooses." + confidenceCriteria: + $ref: "#/components/schemas/ConfidenceCriteriaDto" + states: + type: array + items: + $ref: "#/components/schemas/BusinessPartnerStateDto" + required: + - confidenceCriteria + - siteBpn + - states + AddressComponentOutputDto: + type: object + description: Address properties of business partner output data + properties: + addressBpn: + type: string + description: "The BPNA of the address, on which the business partner provides\ + \ a view." + name: + type: string + description: "The name of the address, on which the business partner provides\ + \ a view. This is not according to official registers but according to\ + \ the name the sharing members agree on, such as the name of a gate or\ + \ any other additional names that designate the address in common parlance." + addressType: + type: string + description: "One of the address types: Legal Address, Site Main Address,\ + \ Legal and Site Main Address, Additional Address. " + enum: + - LegalAndSiteMainAddress + - LegalAddress + - SiteMainAddress + - AdditionalAddress + physicalPostalAddress: + $ref: "#/components/schemas/PhysicalPostalAddressDto" + description: "The physical postal address of the address, on which the business\ + \ partner provides a view, such as an office, warehouse, gate, etc." + alternativePostalAddress: + $ref: "#/components/schemas/AlternativePostalAddressDto" + description: "The alternative postal address of the address, on which the\ + \ business partner provides a view, for example if the goods are to be\ + \ picked up somewhere else." + confidenceCriteria: + $ref: "#/components/schemas/ConfidenceCriteriaDto" + states: + type: array + items: + $ref: "#/components/schemas/BusinessPartnerStateDto" + identifiers: + type: object + items: + $ref: "#/components/schemas/AddressIdentifierDto" + required: + - addressBpn + - bpnA + - confidenceCriteria + - physicalPostalAddress + - states + BusinessPartnerIdentifierDto: + type: object + properties: + type: + type: string + description: The type of the identifier. + value: + type: string + description: The value of the identifier like “DE123465789. + issuingBody: + type: string + description: "The name of the official register, where the identifier is\ + \ registered. For example, a Handelsregisternummer in Germany is only\ + \ valid with its corresponding Registergericht and Registerart." + BusinessPartnerStateDto: + type: object + properties: + validFrom: + type: string + format: date-time + description: Date since when the status is/was valid. + validTo: + type: string + format: date-time + description: "Date until the status was valid, if applicable." + type: + type: string + description: The type of this specified status. + enum: + - ACTIVE + - INACTIVE + securitySchemes: open_id_scheme: type: oauth2 From c39d37e0d703fe26675c355c99f9634ffa46624e Mon Sep 17 00:00:00 2001 From: Nico Koprowski Date: Fri, 19 Dec 2025 11:15:00 +0800 Subject: [PATCH 2/2] fix(Charts): increase Pool Chart memory limit --- charts/bpdm/charts/bpdm-pool/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/bpdm/charts/bpdm-pool/values.yaml b/charts/bpdm/charts/bpdm-pool/values.yaml index d762bd63e..9c049160f 100644 --- a/charts/bpdm/charts/bpdm-pool/values.yaml +++ b/charts/bpdm/charts/bpdm-pool/values.yaml @@ -70,10 +70,10 @@ ingress: resources: limits: cpu: 1 - memory: 1Gi + memory: 1200Mi requests: cpu: 100m - memory: 1Gi + memory: 1200Mi nodeSelector: {}