diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerEquivalenceService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerEquivalenceService.kt new file mode 100644 index 000000000..8c45b8515 --- /dev/null +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/BusinessPartnerEquivalenceService.kt @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 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.service + +import org.eclipse.tractusx.bpdm.pool.entity.* +import org.springframework.stereotype.Service + +@Service +class BusinessPartnerEquivalenceService { + fun isEquivalent(original: LegalEntity, updated: LegalEntity): Boolean = + original.bpn != updated.bpn || + isEquivalent(original.legalForm, updated.legalForm) || + isEquivalentLegalEntityIdentifier(original.identifiers, updated.identifiers) || + isEquivalentLegalEntityState(original.states, updated.states) || + isEquivalentLogisticAddress(original.addresses, updated.addresses) || + isEquivalent(original.sites, updated.sites) || + isEquivalentLegalEntityClassification(original.classifications, updated.classifications) || + isEquivalent(original.legalName, updated.legalName) || + isEquivalent(original.legalAddress, updated.legalAddress) + + + private fun isEquivalentLegalEntityIdentifier( + originalSet: MutableSet, + updatedSet: MutableSet + ): Boolean { + return isEquivalent(originalSet, updatedSet) { entity1, entity2 -> + isEquivalent(entity1, entity2) + } + } + + private fun isEquivalent(original: LegalEntityIdentifier, updated: LegalEntityIdentifier): Boolean = + original.value != updated.value || + isEquivalent(original.type, updated.type) || + original.issuingBody != updated.issuingBody + + private fun isEquivalent(original: IdentifierType, updated: IdentifierType): Boolean = + original.technicalKey != updated.technicalKey || + original.name != updated.name || + original.businessPartnerType.name != updated.businessPartnerType.name + + + private fun isEquivalentLegalEntityState( + originalSet: MutableSet, + updatedSet: MutableSet + ): Boolean { + return isEquivalent(originalSet, updatedSet) { entity1, entity2 -> + isEquivalent(entity1, entity2) + } + } + + private fun isEquivalent(original: LegalEntityState, updated: LegalEntityState): Boolean = + original.description != updated.description || + original.validFrom != updated.validFrom || + original.validTo != updated.validTo || + original.type != updated.type + + + private fun isEquivalentLegalEntityClassification( + originalSet: MutableSet, + updatedSet: MutableSet + ): Boolean { + return isEquivalent(originalSet, updatedSet) { entity1, entity2 -> + isEquivalent(entity1, entity2) + } + } + + + private fun isEquivalent(original: LegalEntityClassification, updated: LegalEntityClassification): Boolean { + return ( + original.value != updated.value || + original.code != updated.code || + original.type != updated.type + ) + } + + private fun isEquivalent(original: Name, updated: Name): Boolean = + original.value != updated.value || + original.shortName != updated.shortName + + private fun isEquivalent(original: LegalForm?, updated: LegalForm?): Boolean = + original?.name != updated?.name || + original?.technicalKey != updated?.technicalKey || + original?.abbreviation != updated?.abbreviation + + + private fun isEquivalentLogisticAddress( + originalSet: MutableSet, + updatedSet: MutableSet + ): Boolean { + return isEquivalent(originalSet, updatedSet) { entity1, entity2 -> + isEquivalent(entity1, entity2) + } + } + + + fun isEquivalent(original: LogisticAddress, updated: LogisticAddress): Boolean = + original.bpn != updated.bpn || + original.name != updated.name || + isEquivalent(original.physicalPostalAddress, updated.physicalPostalAddress) || + isEquivalent(original.alternativePostalAddress, updated.alternativePostalAddress) + + + fun isEquivalentLogisticAddressSite(original: LogisticAddress, updated: LogisticAddress): Boolean = + original.name != updated.name || + isEquivalent(original.physicalPostalAddress, updated.physicalPostalAddress) || + isEquivalent(original.alternativePostalAddress, updated.alternativePostalAddress) + + + private fun isEquivalent( + originalSet: MutableSet, + updatedSet: MutableSet + ): Boolean { + return isEquivalent(originalSet, updatedSet) { entity1, entity2 -> + isEquivalent(entity1, entity2) + } + } + + fun isEquivalent(original: Site, updated: Site): Boolean = + original.bpn != updated.bpn || + original.name != updated.name || + isEquivalentLogisticAddressSite(original.mainAddress, updated.mainAddress) + + private fun isEquivalent( + original: AlternativePostalAddress?, + updated: AlternativePostalAddress? + ): Boolean = + isEquivalent(original?.geographicCoordinates, updated?.geographicCoordinates) || + original?.country != updated?.country || + isEquivalent(original?.administrativeAreaLevel1, updated?.administrativeAreaLevel1) || + original?.postCode != updated?.postCode || + original?.city != updated?.city || + original?.deliveryServiceType != updated?.deliveryServiceType || + original?.deliveryServiceNumber != updated?.deliveryServiceNumber || + original?.deliveryServiceQualifier != updated?.deliveryServiceQualifier + + + private fun isEquivalent(original: GeographicCoordinate?, updated: GeographicCoordinate?): Boolean = + original?.latitude != updated?.latitude || + original?.longitude != updated?.longitude || + original?.altitude != updated?.altitude + + private fun isEquivalent(original: Region?, updated: Region?): Boolean = + original?.countryCode != updated?.countryCode || + original?.regionCode != updated?.regionCode || + original?.regionName != updated?.regionName + + + private fun isEquivalent( + original: PhysicalPostalAddress, + updated: PhysicalPostalAddress + ): Boolean = + original.country.name != updated.country.name || + original.administrativeAreaLevel1 != updated.administrativeAreaLevel1 || + original.administrativeAreaLevel2 != updated.administrativeAreaLevel2 || + original.administrativeAreaLevel3 != updated.administrativeAreaLevel3 || + original.administrativeAreaLevel4 != updated.administrativeAreaLevel4 || + original.postCode != updated.postCode || + original.city != updated.city || + original.districtLevel1 != updated.districtLevel1 || + original.districtLevel2 != updated.districtLevel2 || + original.companyPostCode != updated.companyPostCode || + original.industrialZone != updated.industrialZone || + original.building != updated.building || + original.floor != updated.floor || + original.door != updated.door || + isEquivalent(original.geographicCoordinates, updated.geographicCoordinates) || + isEquivalent(original.street, updated.street) + + private fun isEquivalent(original: Street?, updated: Street?): Boolean = + original?.name != updated?.name || + original?.houseNumber != updated?.houseNumber || + original?.milestone != updated?.milestone || + original?.direction != updated?.direction + + private fun isEquivalent( + originalSet: MutableSet, + updatedSet: MutableSet, + comparator: (T, T) -> Boolean + ): Boolean { + if (originalSet.size != updatedSet.size) { + return true + } + + for (originalEntity in originalSet) { + val updatedEntity = updatedSet.find { comparator(it, originalEntity) } + + if (updatedEntity == null || comparator(originalEntity, updatedEntity)) { + return true + } + } + + return false + } +} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepBuildService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepBuildService.kt index 1893471bb..8dd03923e 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepBuildService.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/TaskStepBuildService.kt @@ -42,6 +42,7 @@ class TaskStepBuildService( private val legalEntityRepository: LegalEntityRepository, private val logisticAddressRepository: LogisticAddressRepository, private val siteRepository: SiteRepository, + private val businessPartnerEquivalenceService: BusinessPartnerEquivalenceService, ) { enum class CleaningError(val message: String) { @@ -100,7 +101,7 @@ class TaskStepBuildService( ) }, legalEntity = businessPartnerDto.legalEntity!!.copy( - bpnLReference = BpnReferenceDto(referenceValue = legalEntity.bpn, referenceType = BpnReferenceType.Bpn) , + bpnLReference = BpnReferenceDto(referenceValue = legalEntity.bpn, referenceType = BpnReferenceType.Bpn), legalAddress = businessPartnerDto.legalEntity!!.legalAddress!!.copy( bpnAReference = BpnReferenceDto(referenceValue = legalEntity.legalAddress.bpn, referenceType = BpnReferenceType.Bpn) ) @@ -117,11 +118,10 @@ class TaskStepBuildService( siteEntity: Site?, taskEntryBpnMapping: TaskEntryBpnMapping ): LogisticAddress { - val bpnAReference = addressDto?.bpnAReference ?: throw BpdmValidationException(CleaningError.BPNA_IS_NULL.message) val bpn = taskEntryBpnMapping.getBpn(bpnAReference) - + var hasChanges = false val upsertAddress = if (bpn == null) { val bpnA = bpnIssuingService.issueAddressBpns(1).single() taskEntryBpnMapping.addMapping(bpnAReference, bpnA) @@ -129,7 +129,11 @@ class TaskStepBuildService( } else { val addressMetadataMap = metadataService.getMetadata(listOf(addressDto)).toMapping() val updateAddress = logisticAddressRepository.findByBpn(bpn) + if (updateAddress != null) { + val newAddress = createLogisticAddressInternal(addressDto, updateAddress.bpn) + updateLogisticAddress(newAddress, addressDto, addressMetadataMap) + hasChanges = businessPartnerEquivalenceService.isEquivalent(updateAddress, newAddress) updateLogisticAddress(updateAddress, addressDto, addressMetadataMap) } else { throw BpdmValidationException(CleaningError.INVALID_LOGISTIC_ADDRESS_BPN.message) @@ -141,15 +145,22 @@ class TaskStepBuildService( } else { upsertAddress.legalEntity = legalEntity } - logisticAddressRepository.save(upsertAddress) + + if (hasChanges || bpn == null) { + logisticAddressRepository.save(upsertAddress) + } + val changelogType = if (bpn == null) ChangelogType.CREATE else ChangelogType.UPDATE - changelogService.createChangelogEntries( - listOf( - ChangelogEntryCreateRequest(upsertAddress.bpn, changelogType, BusinessPartnerType.ADDRESS) + if (hasChanges || bpn == null) { + changelogService.createChangelogEntries( + listOf( + ChangelogEntryCreateRequest(upsertAddress.bpn, changelogType, BusinessPartnerType.ADDRESS) + ) ) - ) + } + return upsertAddress } @@ -253,7 +264,7 @@ class TaskStepBuildService( val legalEntityMetadataMap = metadataService.getMetadata(listOf(legalEntityDto)).toMapping() val bpn = taskEntryBpnMapping.getBpn(bpnLReference) - + var hasChanges = false val upsertLe = if (bpn == null) { val bpnL = bpnIssuingService.issueLegalEntityBpns(1).single() taskEntryBpnMapping.addMapping(bpnLReference, bpnL) @@ -267,6 +278,14 @@ class TaskStepBuildService( val updateLe = legalEntityRepository.findByBpn(bpn) if (updateLe != null) { if (legalEntityDto.hasChanged == true) { + + val createdLe = + BusinessPartnerBuildService.createLegalEntity(legalEntityDto, updateLe.bpn, legalEntityDto.legalName, legalEntityMetadataMap) + val address = createLogisticAddress(legalAddress, taskEntryBpnMapping) + createdLe.legalAddress = address + address.legalEntity = createdLe + hasChanges = businessPartnerEquivalenceService.isEquivalent(updateLe, createdLe) + BusinessPartnerBuildService.updateLegalEntity(updateLe, legalEntityDto, legalEntityDto.legalName, legalEntityMetadataMap) val addressMetadataMap = metadataService.getMetadata(listOf(legalAddress)).toMapping() updateLogisticAddress(updateLe.legalAddress, legalAddress, addressMetadataMap) @@ -276,15 +295,17 @@ class TaskStepBuildService( } updateLe } - legalEntityRepository.save(upsertLe) val changelogType = if (bpn == null) ChangelogType.CREATE else ChangelogType.UPDATE - changelogService.createChangelogEntries( - listOf( - ChangelogEntryCreateRequest(upsertLe.bpn, changelogType, BusinessPartnerType.LEGAL_ENTITY) - ) - ) + if (hasChanges || bpn == null) { + legalEntityRepository.save(upsertLe) + changelogService.createChangelogEntries( + listOf( + ChangelogEntryCreateRequest(upsertLe.bpn, changelogType, BusinessPartnerType.LEGAL_ENTITY) + ) + ) + } return upsertLe } @@ -300,7 +321,7 @@ class TaskStepBuildService( val bpn = taskEntryBpnMapping.getBpn(bpnSReference) val changelogType = if (bpn == null) ChangelogType.CREATE else ChangelogType.UPDATE - + var hasChanges = false val upsertSite = if (bpn == null) { val bpnS = bpnIssuingService.issueSiteBpns(1).single() val createSite = BusinessPartnerBuildService.createSite(siteDto, bpnS, legalEntity) @@ -321,6 +342,16 @@ class TaskStepBuildService( val updateSite = siteRepository.findByBpn(bpn) if (updateSite != null) { if (siteDto.hasChanged == true) { + val createSite = BusinessPartnerBuildService.createSite(siteDto, updateSite.bpn, legalEntity) + val siteMainAddress = + if (genericBusinessPartner.generic.postalAddress.addressType == AddressType.LegalAndSiteMainAddress) + legalEntity.legalAddress + else + createLogisticAddress(mainAddress, taskEntryBpnMapping) + createSite.mainAddress = siteMainAddress + siteMainAddress.site = createSite + hasChanges = businessPartnerEquivalenceService.isEquivalent(updateSite, createSite) + BusinessPartnerBuildService.updateSite(updateSite, siteDto) val addressMetadataMap = metadataService.getMetadata(listOf(mainAddress)).toMapping() updateLogisticAddress(updateSite.mainAddress, mainAddress, addressMetadataMap) @@ -330,12 +361,15 @@ class TaskStepBuildService( } updateSite } - siteRepository.save(upsertSite) - changelogService.createChangelogEntries( - listOf( - ChangelogEntryCreateRequest(upsertSite.bpn, changelogType, BusinessPartnerType.SITE) + + if (hasChanges || bpn == null) { + siteRepository.save(upsertSite) + changelogService.createChangelogEntries( + listOf( + ChangelogEntryCreateRequest(upsertSite.bpn, changelogType, BusinessPartnerType.SITE) + ) ) - ) + } return upsertSite } diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/ChangelogControllerIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/ChangelogControllerIT.kt index c1839a034..692efd6bf 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/ChangelogControllerIT.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/controller/ChangelogControllerIT.kt @@ -76,12 +76,12 @@ class ChangelogControllerIT @Autowired constructor( val bpnA1 = createdStructures[0].legalEntity.legalAddress.bpna val bpnA2 = createdStructures[1].legalEntity.legalAddress.bpna - + val toUpdate = listOf( + BusinessPartnerNonVerboseValues.legalEntityUpdate1.copy(bpnl = bpnL1), + BusinessPartnerNonVerboseValues.legalEntityUpdate2.copy(bpnl = bpnL2) + ) poolClient.legalEntities.updateBusinessPartners( - listOf( - BusinessPartnerNonVerboseValues.legalEntityUpdate1.copy(bpnl = bpnL1), - BusinessPartnerNonVerboseValues.legalEntityUpdate2.copy(bpnl = bpnL2) - ) + toUpdate ) val timeAfterUpdate = Instant.now() @@ -136,9 +136,14 @@ class ChangelogControllerIT @Autowired constructor( val bpnMainAddress1 = createdStructures[0].siteStructures[0].site.mainAddress.bpna val bpnMainAddress2 = createdStructures[0].siteStructures[1].site.mainAddress.bpna + val name1 = BusinessPartnerNonVerboseValues.siteUpdate3.site.name + poolClient.sites.updateSite( listOf( - BusinessPartnerNonVerboseValues.siteUpdate1.copy(bpns = bpnS1), + BusinessPartnerNonVerboseValues.siteUpdate1.copy( + bpns = bpnS1, site = + BusinessPartnerNonVerboseValues.siteUpdate1.site.copy(name = name1) + ), BusinessPartnerNonVerboseValues.siteUpdate2.copy(bpns = bpnS2) ) ) @@ -184,7 +189,12 @@ class ChangelogControllerIT @Autowired constructor( LegalEntityStructureRequest( legalEntity = BusinessPartnerNonVerboseValues.legalEntityCreate1, addresses = listOf(BusinessPartnerNonVerboseValues.addressPartnerCreate1), - siteStructures = listOf(SiteStructureRequest(site = BusinessPartnerNonVerboseValues.siteCreate1, addresses = listOf(BusinessPartnerNonVerboseValues.addressPartnerCreate2))) + siteStructures = listOf( + SiteStructureRequest( + site = BusinessPartnerNonVerboseValues.siteCreate1, + addresses = listOf(BusinessPartnerNonVerboseValues.addressPartnerCreate2) + ) + ) ) ) ) @@ -195,9 +205,13 @@ class ChangelogControllerIT @Autowired constructor( val bpnA1 = createdStructures[0].addresses[0].address.bpna val bpnA2 = createdStructures[0].siteStructures[0].addresses[0].address.bpna + val nameUpdate = "nameUpdate" poolClient.addresses.updateAddresses( listOf( - BusinessPartnerNonVerboseValues.addressPartnerUpdate1.copy(bpna = bpnA1), + BusinessPartnerNonVerboseValues.addressPartnerUpdate1.copy( + bpna = bpnA1, + address = BusinessPartnerNonVerboseValues.addressPartnerUpdate1.address.copy(name = nameUpdate) + ), BusinessPartnerNonVerboseValues.addressPartnerUpdate2.copy(bpna = bpnA2) ) ) @@ -461,5 +475,4 @@ class ChangelogControllerIT @Autowired constructor( .also(checkTimestampsInBetween(timeBeforeInsert, timeAfterInsert)) } - } \ No newline at end of file