diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d64c2718..57816b978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C - BPDM System Test: End-to-end test CI/CD workflow setup for golden record process. ([#1155](https://github.com/eclipse-tractusx/bpdm/issues/1155)) - Apps : Enhanced dependency readiness checks with a scheduler to verify connections to required services every 30 seconds and during startup for Pool, Cleaning Service Dummy, and Gate service. ([#1161](https://github.com/eclipse-tractusx/bpdm/issues/1161)) - BPDM Gate: Add consistency check with the golden record Pool making sure that referenced BPNs are still existing ([#1130](https://github.com/eclipse-tractusx/bpdm/issues/1130)) +- BPDM Gate: Add externalTimestamp to prevent old request overwrite newer request ### Changed @@ -114,15 +115,15 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C ### Added -- BPDM Gate: Configuration to prevent the uploaded business partner input data to immediately enter the golden record process. -In this configuration the business partner data needs to be sent to the golden record process manually over the new state/ready API endpoint. -Default configuration remains automatically sharing. +- BPDM Gate: Configuration to prevent the uploaded business partner input data to immediately enter the golden record process. + In this configuration the business partner data needs to be sent to the golden record process manually over the new state/ready API endpoint. + Default configuration remains automatically sharing. - BPDM Gate: Limited multi-tenancy support. Business partners are now separated by owner-BPNL. -The owner is determined from the 'bpn' claim in the token. -This means users of a Gate can only see and edit their own business partner data. + The owner is determined from the 'bpn' claim in the token. + This means users of a Gate can only see and edit their own business partner data. - BPDM Pool: New API endpoints to query business partner data which belongs to Catena-X members only - APIs: Added a major version number to all API endpoint paths indicating the current version of the BPDM APIs. -In the future we will use version numbers in the URL to differentiate between all currently supported major versions of the API + In the future we will use version numbers in the URL to differentiate between all currently supported major versions of the API - BPDM Gate Client: Now supports the stats endpoints of the BPDM API ### Changed @@ -134,15 +135,15 @@ In the future we will use version numbers in the URL to differentiate between al - BPDM Gate: Fix not correctly updating business partner output data from golden record updates in the Pool. - JAVA version to 21 - BPDM API Permissions: Overhaul of the permissions needed to access the BPDM API endpoints. -Permissions are now more fine-granular and differentiate more clearly between read or write. -For more details consult the Arc42, API documentation and properties files of the respective applications. + Permissions are now more fine-granular and differentiate more clearly between read or write. + For more details consult the Arc42, API documentation and properties files of the respective applications. - BPDM App Configuration: Now all applications are secured (authenticated and authorized) by default. -You can still deactivate security in the BPDM apps for testing or development purposes though. + You can still deactivate security in the BPDM apps for testing or development purposes though. - BPDM Pool: Fix Pool trying to update golden records which the golden record process indicated to have no changes - BPDM Orchestrator: The business partner data for golden record process tasks has been completely overhauled. -Now business partner data is clearly divided into `uncategorized`, `legal entity`, `site` and `additonal address` data. -This model is less verbose and contains less duplicate data. -Additionally, both Pool and Gate can write and read from it making it unnecessary for a cleaning service to provide the data in two different models. + Now business partner data is clearly divided into `uncategorized`, `legal entity`, `site` and `additonal address` data. + This model is less verbose and contains less duplicate data. + Additionally, both Pool and Gate can write and read from it making it unnecessary for a cleaning service to provide the data in two different models. @@ -190,7 +191,7 @@ Additionally, both Pool and Gate can write and read from it making it unnecessar - BPDM Gate: New business partner type 'GENERIC' for changelog - BPDM Gate: New business partner type 'GENERIC' for sharing state - Workflows: Trivy now targets the latest alpha Docker image instead of the latest release version -- Apps: Increase projectreactor.netty version to fix Trivy vulnerability +- Apps: Increase projectreactor.netty version to fix Trivy vulnerability ## [4.0.1] - 2023-08-28 @@ -228,8 +229,8 @@ Please create a back-up of your business partner data before updating. - BPDM: Umbrella Chart with BPDM Bridge Dummy. ### Fixed - - BPDM: Deprecated endpoints for retrieving business partners in legacy format. - - Endpoint for retrieving changelog entries has now improved filtering (breaking API change) +- BPDM: Deprecated endpoints for retrieving business partners in legacy format. +- Endpoint for retrieving changelog entries has now improved filtering (breaking API change) ## [3.2.2] - 2023-05-12 @@ -273,7 +274,7 @@ Please create a back-up of your business partner data before updating. ### Fixed -- BPDM Gate: For a business partner with a child relation, this relation could be returned as parent relation erroneously. +- BPDM Gate: For a business partner with a child relation, this relation could be returned as parent relation erroneously. - BPDM Gate: When a business partner with a child relation was updated, this relation was erroneously deleted, rendering the previous child invalid. ## [3.0.3] - 2023-02-23 diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerNonVerboseValues.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerNonVerboseValues.kt index ecc4f77d1..2c210c9a2 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerNonVerboseValues.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerNonVerboseValues.kt @@ -36,7 +36,8 @@ object BusinessPartnerNonVerboseValues { val bpInputRequestMinimal = BusinessPartnerInputRequest( externalId = BusinessPartnerVerboseValues.externalId2, - address = bpPostalAddressInputDtoMinimal + address = bpPostalAddressInputDtoMinimal, + externalSequenceTimestamp = null ) val bpInputRequestFull = BusinessPartnerVerboseValues.bpInputRequestFull diff --git a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerVerboseValues.kt b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerVerboseValues.kt index c91d12667..3fce098de 100644 --- a/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerVerboseValues.kt +++ b/bpdm-common-test/src/main/kotlin/org/eclipse/tractusx/bpdm/test/testdata/gate/BusinessPartnerVerboseValues.kt @@ -72,6 +72,10 @@ object BusinessPartnerVerboseValues { const val businessStatusDescription1 = "Active" const val businessStatusDescription2 = "Insolvent" + val externalSequenceTimestamp1 = Instant.now().minusSeconds(5) + val externalSequenceTimestamp2 = Instant.now() + val externalSequenceTimestamp3 = Instant.now().plusSeconds(5) + val businessStatusValidFrom1 = LocalDateTime.of(2020, 1, 1, 0, 0) val businessStatusValidFrom2 = LocalDateTime.of(2019, 1, 1, 0, 0) @@ -331,7 +335,8 @@ object BusinessPartnerVerboseValues { addressType = AddressType.LegalAddress, physicalPostalAddress = postalAddress2, alternativePostalAddress = alternativeAddressFull - ) + ), + externalSequenceTimestamp = null ) @@ -358,7 +363,8 @@ object BusinessPartnerVerboseValues { addressType = AddressType.SiteMainAddress, physicalPostalAddress = postalAddress2, alternativePostalAddress = alternativeAddressFull - ) + ), + externalSequenceTimestamp = null ) //New Values for Logistic Addresses Tests @@ -376,7 +382,7 @@ object BusinessPartnerVerboseValues { building = "Bauteil A", floor = "Etage 1", door = "Door One", - street = StreetDto(name = "Mercedesstraße", houseNumber = "", direction = "direction1", houseNumberSupplement = "A"), + street = StreetDto(name = "Mercedesstraße", houseNumber = "", direction = "direction1", houseNumberSupplement = "A") ) val postalAddressLogisticAddress2 = PhysicalPostalAddressDto( @@ -393,7 +399,7 @@ object BusinessPartnerVerboseValues { building = "Building Two", floor = "Floor Two", door = "Door Two", - street = StreetDto(name = "TODO", houseNumber = "", direction = "direction1", houseNumberSupplement = "B"), + street = StreetDto(name = "TODO", houseNumber = "", direction = "direction1", houseNumberSupplement = "B") ) //New Values for Logistic Address Tests @@ -657,7 +663,8 @@ object BusinessPartnerVerboseValues { addressType = AddressType.LegalAndSiteMainAddress, physicalPostalAddress = physicalAddressChina, alternativePostalAddress = AlternativePostalAddressDto() - ) + ), + externalSequenceTimestamp = null ) val bpInputRequestCleaned = BusinessPartnerInputRequest( @@ -685,7 +692,8 @@ object BusinessPartnerVerboseValues { addressType = AddressType.LegalAddress, physicalPostalAddress = postalAddress2, alternativePostalAddress = alternativeAddressFull - ) + ), + externalSequenceTimestamp = null ) val bpInputRequestError = BusinessPartnerInputRequest( @@ -713,7 +721,8 @@ object BusinessPartnerVerboseValues { addressType = AddressType.LegalAddress, physicalPostalAddress = postalAddress2, alternativePostalAddress = alternativeAddressFull - ) + ), + externalSequenceTimestamp = null ) val bpOutputDtoCleaned = BusinessPartnerOutputDto( @@ -829,6 +838,60 @@ object BusinessPartnerVerboseValues { updatedAt = Instant.now() ) + val bpInputRequestWithExternalSequenceTimestamp1 = BusinessPartnerInputRequest( + externalId = externalId1, + legalEntity = LegalEntityRepresentationInputDto( + legalEntityBpn = "BPNL0000000000XY", + shortName = "short", + legalName = "Limited Liability Company Name", + legalForm = "Limited Liability Company" + ), + address = AddressRepresentationInputDto( + addressBpn = "BPNA0000000001XY", + name = "Address Name", + addressType = null, + physicalPostalAddress = physicalAddressMinimal + ), + externalSequenceTimestamp = externalSequenceTimestamp1 + + ) + + val bpInputRequestWithExternalSequenceTimestamp2 = BusinessPartnerInputRequest( + externalId = externalId1, + legalEntity = LegalEntityRepresentationInputDto( + legalEntityBpn = "BPNL0000000000XY", + shortName = "short1", + legalName = "Limited Liability Company Name", + legalForm = "Limited Liability Company" + ), + address = AddressRepresentationInputDto( + addressBpn = "BPNA0000000001XY", + name = "Address Name", + addressType = null, + physicalPostalAddress = physicalAddressMinimal + ), + externalSequenceTimestamp = externalSequenceTimestamp2 + + ) + + val bpInputRequestWithExternalSequenceTimestamp3 = BusinessPartnerInputRequest( + externalId = externalId1, + legalEntity = LegalEntityRepresentationInputDto( + legalEntityBpn = "BPNL0000000000XY", + shortName = "short2", + legalName = "Limited Liability Company Name", + legalForm = "Limited Liability Company" + ), + address = AddressRepresentationInputDto( + addressBpn = "BPNA0000000001XY", + name = "Another address Name", + addressType = null, + physicalPostalAddress = physicalAddressMinimal + ), + externalSequenceTimestamp = externalSequenceTimestamp3 + + ) + val now = Instant.now() diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IBaseBusinessPartnerGateDto.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IBaseBusinessPartnerGateDto.kt index c3f129bb6..336b437e1 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IBaseBusinessPartnerGateDto.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/IBaseBusinessPartnerGateDto.kt @@ -22,6 +22,7 @@ package org.eclipse.tractusx.bpdm.gate.api.model import io.swagger.v3.oas.annotations.media.Schema import org.eclipse.tractusx.bpdm.common.dto.IBaseBusinessPartnerDto import org.eclipse.tractusx.bpdm.common.dto.openapidescription.CommonDescription +import java.time.Instant interface IBaseBusinessPartnerGateDto : IBaseBusinessPartnerDto { @@ -30,5 +31,8 @@ interface IBaseBusinessPartnerGateDto : IBaseBusinessPartnerDto { @get:Schema(description = "Indicates whether the sharing member claims (in the initial upload) the business partner to belong to the company data of the sharing member.") val isOwnCompanyData: Boolean + + @get:Schema(description = "The timestamp indicates the last time point of change from the user side") + val externalSequenceTimestamp: Instant? } diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/BusinessPartnerInputRequest.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/BusinessPartnerInputRequest.kt index 538ef9f08..d78bb8fa5 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/BusinessPartnerInputRequest.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/request/BusinessPartnerInputRequest.kt @@ -27,6 +27,7 @@ import org.eclipse.tractusx.bpdm.gate.api.model.IBaseBusinessPartnerGateDto import org.eclipse.tractusx.bpdm.gate.api.model.response.AddressRepresentationInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.SiteRepresentationInputDto +import java.time.Instant @Schema( description = "Generic business partner with external id", @@ -42,6 +43,7 @@ data class BusinessPartnerInputRequest( override val isOwnCompanyData: Boolean = false, override val legalEntity: LegalEntityRepresentationInputDto = LegalEntityRepresentationInputDto(), override val site: SiteRepresentationInputDto = SiteRepresentationInputDto(), - override val address: AddressRepresentationInputDto = AddressRepresentationInputDto() + override val address: AddressRepresentationInputDto = AddressRepresentationInputDto(), + override val externalSequenceTimestamp: Instant? = null ) : IBaseBusinessPartnerGateDto diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerDto.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerDto.kt index 497ade6a8..51d5fd934 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerDto.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/BusinessPartnerDto.kt @@ -41,6 +41,7 @@ data class BusinessPartnerInputDto( override val legalEntity: LegalEntityRepresentationInputDto = LegalEntityRepresentationInputDto(), override val site: SiteRepresentationInputDto = SiteRepresentationInputDto(), override val address: AddressRepresentationInputDto = AddressRepresentationInputDto(), + override val externalSequenceTimestamp:Instant? = null, @get:Schema(description = CommonDescription.createdAt) val createdAt: Instant, 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 c6c6f4cd1..328798d63 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 @@ -40,6 +40,7 @@ data class BusinessPartnerOutputDto( override val legalEntity: LegalEntityRepresentationOutputDto, override val site: SiteRepresentationOutputDto?, override val address: AddressComponentOutputDto, + override val externalSequenceTimestamp: Instant? = null, @get:Schema(description = CommonDescription.createdAt) val createdAt: Instant, diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerDb.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerDb.kt index fb2301349..2df700e89 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerDb.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/entity/generic/BusinessPartnerDb.kt @@ -24,6 +24,7 @@ import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerRole import org.eclipse.tractusx.bpdm.common.model.BaseEntity import org.eclipse.tractusx.bpdm.common.model.StageType import org.eclipse.tractusx.bpdm.gate.entity.SharingStateDb +import java.time.Instant import java.util.* @Entity @@ -104,6 +105,9 @@ class BusinessPartnerDb( @JoinColumn(name = "address_confidence_id", unique = true) var addressConfidence: ConfidenceCriteriaDb?, + @Column(name = "external_sequence_timestamp") + var externalSequenceTimestamp: Instant? = null, + ) : BaseEntity() { companion object { diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt index c25d649a7..6ec0c1030 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerMappings.kt @@ -105,6 +105,7 @@ class BusinessPartnerMappings { bpnS = dto.site.siteBpn, bpnA = dto.address.addressBpn, postalAddress = toPostalAddress(dto.address), + externalSequenceTimestamp = dto.externalSequenceTimestamp, legalEntityConfidence = null, siteConfidence = null, addressConfidence = null, diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt index 75d566d54..882af5087 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/BusinessPartnerService.kt @@ -87,7 +87,7 @@ class BusinessPartnerService( upsertFromEntity(existingInput, updatedData) - .takeIf { it.hadChanges || sharingState.sharingStateType == SharingStateType.Error } + .takeIf { it.shouldUpdate && (it.hadChanges || sharingState.sharingStateType == SharingStateType.Error) } ?.also { sharingStateService.setInitial(sharingState) } ?.businessPartner } @@ -136,15 +136,20 @@ class BusinessPartnerService( val partnerToUpsert = existingPartner ?: BusinessPartnerDb.createEmpty(upsertData.sharingState, upsertData.stage) val hasChanges = changeType == ChangelogType.CREATE || compareUtil.hasChanges(upsertData, partnerToUpsert) + val shouldUpdate = when { + upsertData.externalSequenceTimestamp == null -> true + existingPartner?.externalSequenceTimestamp == null -> true + else -> upsertData.externalSequenceTimestamp!!.isAfter(existingPartner.externalSequenceTimestamp) + } - if (hasChanges) { - changelogRepository.save(ChangelogEntryDb(sharingState.externalId, sharingState.tenantBpnl, changeType, stage)) + if (hasChanges && shouldUpdate) { + changelogRepository.save(ChangelogEntryDb(sharingState.externalId, sharingState.tenantBpnl, changeType, stage)) - copyUtil.copyValues(upsertData, partnerToUpsert) - businessPartnerRepository.save(partnerToUpsert) + copyUtil.copyValues(upsertData, partnerToUpsert) + businessPartnerRepository.save(partnerToUpsert) } - return UpsertResult(hasChanges, changeType, partnerToUpsert) + return UpsertResult(hasChanges, shouldUpdate, changeType, partnerToUpsert) } private fun getBusinessPartners( @@ -164,6 +169,7 @@ class BusinessPartnerService( data class UpsertResult( val hadChanges: Boolean, + val shouldUpdate: Boolean, val type: ChangelogType, val businessPartner: BusinessPartnerDb ) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerCopyUtil.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerCopyUtil.kt index 62ea7eae6..d0f32b7c0 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerCopyUtil.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/BusinessPartnerCopyUtil.kt @@ -42,6 +42,7 @@ class BusinessPartnerCopyUtil { legalEntityConfidence = fromPartner.legalEntityConfidence siteConfidence = fromPartner.siteConfidence addressConfidence = fromPartner.addressConfidence + externalSequenceTimestamp = fromPartner.externalSequenceTimestamp nameParts.replace(fromPartner.nameParts) roles.replace(fromPartner.roles) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/Extensions.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/Extensions.kt index 401e7e1c8..520e5355a 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/Extensions.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/Extensions.kt @@ -21,6 +21,10 @@ package org.eclipse.tractusx.bpdm.gate.util import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter fun List.containsDuplicates(): Boolean = size != distinct().size diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt index 9bf7007dc..0228a3830 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt @@ -90,7 +90,8 @@ object PartnerFileUtil { legalName = legalName ), site = row.toSiteRepresentationInputDto(formatter, errors, index, row.externalId.orEmpty()), - address = row.toAddressRepresentationInputDto(formatter, errors, index, row.externalId.orEmpty()) + address = row.toAddressRepresentationInputDto(formatter, errors, index, row.externalId.orEmpty()), + externalSequenceTimestamp = null ) } catch (e: Exception) { errors.add("Row - ${index + 2}, External ID - ${row.externalId.orEmpty()} has error: ${e.message}") diff --git a/bpdm-gate/src/main/resources/db/migration/V6_3_0_1__add_externalSequenceTimestamp_column.sql b/bpdm-gate/src/main/resources/db/migration/V6_3_0_1__add_externalSequenceTimestamp_column.sql new file mode 100644 index 000000000..929925a03 --- /dev/null +++ b/bpdm-gate/src/main/resources/db/migration/V6_3_0_1__add_externalSequenceTimestamp_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE business_partners +ADD COLUMN external_sequence_timestamp TIMESTAMP WITHOUT TIME ZONE NULL \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt index 91f499cba..e4d84ea74 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt @@ -49,7 +49,7 @@ abstract class AuthTestBase( @Test fun `PUT Partner Input`() { authAssertions.assert(authExpectations.businessPartner.putInput) { gateClient.businessParters.upsertBusinessPartnersInput(listOf( - BusinessPartnerInputRequest("externalId") + BusinessPartnerInputRequest("externalId", externalSequenceTimestamp = null) )) } } diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt index 5a201fffb..9183060f0 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/BusinessPartnerControllerIT.kt @@ -271,6 +271,35 @@ class BusinessPartnerControllerIT @Autowired constructor( assertEquals(0, searchResponsePage2.content.size) } + @Test + fun `insert a late arrival request with minimal business partner and the record won't be updated`() { + + val firstUpsertRequest = listOf(BusinessPartnerVerboseValues.bpInputRequestWithExternalSequenceTimestamp2) //12:00 + gateClient.businessParters.upsertBusinessPartnersInput(firstUpsertRequest).body!! + + val beforeFirstUpsertRequest = listOf(BusinessPartnerVerboseValues.bpInputRequestWithExternalSequenceTimestamp1) // 11:59 + gateClient.businessParters.upsertBusinessPartnersInput(beforeFirstUpsertRequest).body!! + + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput( + listOf(BusinessPartnerVerboseValues.externalId1)) + + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage.content, firstUpsertRequest) + } + + @Test + fun `upsert a new request with later externalSequenceTimestamp timestamp and the record updated`() { + + val firstUpsertRequest = listOf(BusinessPartnerVerboseValues.bpInputRequestWithExternalSequenceTimestamp2) //12:00 + gateClient.businessParters.upsertBusinessPartnersInput(firstUpsertRequest).body!! + + val laterUpsertRequest = listOf(BusinessPartnerVerboseValues.bpInputRequestWithExternalSequenceTimestamp3) // 12:01 + gateClient.businessParters.upsertBusinessPartnersInput(laterUpsertRequest).body!! + + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput( + listOf(BusinessPartnerVerboseValues.externalId1)) + + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage.content, laterUpsertRequest) + } diff --git a/docs/api/gate.yaml b/docs/api/gate.yaml index c5ee4383f..e0f1f8c43 100644 --- a/docs/api/gate.yaml +++ b/docs/api/gate.yaml @@ -934,6 +934,7 @@ components: - site - states - updatedAt + - externalSequenceTimestamp type: object properties: externalId: @@ -981,6 +982,10 @@ components: type: string description: The date when the data record has been last updated. format: date-time + externalSequenceTimestamp: + type: string + description: The timestamp of the data provided on customer side (system of the customer). + format: date-time description: Generic business partner with external id BusinessPartnerInputRequest: required: @@ -993,6 +998,7 @@ components: - roles - site - states + - externalSequenceTimestamp type: object properties: externalId: @@ -1032,6 +1038,10 @@ components: $ref: '#/components/schemas/SiteRepresentationInputDto' address: $ref: '#/components/schemas/AddressRepresentationInputDto' + externalSequenceTimestamp: + type: string + description: The timestamp of the data provided on customer side (system of the customer). + format: date-time description: Generic business partner with external id BusinessPartnerOutputDto: required: