From d80f9f37ba15ba34fdfa1363971eecd0895a3695 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe <132669361+eschrewe@users.noreply.github.com> Date: Tue, 14 May 2024 07:30:31 +0200 Subject: [PATCH 1/2] Refactor/dtr registrationtasks (#356) * feat: register at DTR only in material partner relationship * feat: update DTR to 0.4.3 * chore: update frontend dependencies * fix: added javadocs and some more safety measurements --- DEPENDENCIES_FRONTEND | 2 +- .../DataInjectionCommandLineRunner.java | 55 +++- .../ddtr/domain/model/DigitalTwinMapping.java | 12 +- .../ddtr/logic/DigitalTwinMappingService.java | 15 +- .../common/ddtr/logic/DtrAdapterService.java | 71 +++-- .../edc/logic/service/EdcAdapterService.java | 38 ++- .../domain/model/MaterialPartnerRelation.java | 12 + .../MaterialPartnerRelationServiceImpl.java | 242 +++++++++++------- .../logic/service/MaterialServiceImpl.java | 47 +--- .../stock/controller/StockViewController.java | 2 + .../logic/adapter/ItemStockSammMapper.java | 22 +- local/docker-compose.yaml | 10 +- 12 files changed, 289 insertions(+), 239 deletions(-) diff --git a/DEPENDENCIES_FRONTEND b/DEPENDENCIES_FRONTEND index dbc2562c..09e994b8 100644 --- a/DEPENDENCIES_FRONTEND +++ b/DEPENDENCIES_FRONTEND @@ -211,7 +211,7 @@ npm/npmjs/-/reusify/1.0.4, MIT, approved, clearlydefined npm/npmjs/-/rimraf/3.0.2, ISC, approved, clearlydefined npm/npmjs/-/rollup/4.9.5, MIT, approved, clearlydefined npm/npmjs/-/run-parallel/1.2.0, MIT, approved, clearlydefined -npm/npmjs/-/scheduler/0.23.0, MIT, approved, clearlydefined +npm/npmjs/-/scheduler/0.23.0, MIT, approved, #14589 npm/npmjs/-/semver/6.3.1, ISC, approved, clearlydefined npm/npmjs/-/semver/7.5.4, ISC, approved, clearlydefined npm/npmjs/-/shebang-command/2.0.0, MIT, approved, clearlydefined diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/DataInjectionCommandLineRunner.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/DataInjectionCommandLineRunner.java index 159bdf5f..c9126c35 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/DataInjectionCommandLineRunner.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/DataInjectionCommandLineRunner.java @@ -148,7 +148,7 @@ private void setupCustomerRole() throws JsonProcessingException { Partner supplierPartner = createAndGetSupplierPartner(); Material semiconductorMaterial = getNewSemiconductorMaterialForCustomer(); Partner mySelf = partnerService.getOwnPartnerEntity(); - + semiconductorMaterial.setProductFlag(true); semiconductorMaterial = materialService.create(semiconductorMaterial); log.info(String.format("Created material: %s", semiconductorMaterial)); List materialsFound = materialService.findAllMaterials(); @@ -161,8 +161,7 @@ private void setupCustomerRole() throws JsonProcessingException { MaterialPartnerRelation semiconductorPartnerRelation = new MaterialPartnerRelation(semiconductorMaterial, - supplierPartner, semiconductorMatNbrSupplier, true, false); -// semiconductorPartnerRelation.setPartnerCXNumber(semiconductorMatNbrCatenaX); + supplierPartner, semiconductorMatNbrSupplier, true, true); mprService.create(semiconductorPartnerRelation); semiconductorPartnerRelation = mprService.find(semiconductorMaterial, supplierPartner); log.info("Found Relation: " + semiconductorPartnerRelation); @@ -220,6 +219,28 @@ private void setupCustomerRole() throws JsonProcessingException { .build(); reportedMaterialItemStock = reportedMaterialItemStockService.create(reportedMaterialItemStock); log.info("Created ReportedMaterialItemStock: \n" + reportedMaterialItemStock); + + ProductItemStock productItemStock = ProductItemStock.builder() + .partner(supplierPartner) + .material(semiconductorMaterial) + .lastUpdatedOnDateTime(new Date()) + .measurementUnit(ItemUnitEnumeration.UNIT_PIECE) + .quantity(33) + .locationBpna(mySelf.getSites().first().getAddresses().first().getBpna()) + .locationBpns(mySelf.getSites().first().getBpns()) + .build(); + productItemStockService.create(productItemStock); + + ReportedProductItemStock reportedProductItemStock = ReportedProductItemStock.builder() + .partner(supplierPartner) + .material(semiconductorMaterial) + .lastUpdatedOnDateTime(new Date()) + .measurementUnit(ItemUnitEnumeration.UNIT_PIECE) + .quantity(44) + .locationBpns(supplierPartner.getSites().first().getBpns()) + .locationBpna(supplierPartner.getSites().first().getAddresses().first().getBpna()) + .build(); + reportedProductItemStockService.create(reportedProductItemStock); } /** @@ -228,6 +249,7 @@ private void setupCustomerRole() throws JsonProcessingException { private void setupSupplierRole() { Partner customerPartner = createAndGetCustomerPartner(); Material semiconductorMaterial = getNewSemiconductorMaterialForSupplier(); + semiconductorMaterial.setMaterialFlag(true); Partner mySelf = partnerService.getOwnPartnerEntity(); Site secondSite = new Site( @@ -247,8 +269,7 @@ private void setupSupplierRole() { log.info(String.format("Created product: %s", semiconductorMaterial)); MaterialPartnerRelation semiconductorPartnerRelation = new MaterialPartnerRelation(semiconductorMaterial, - customerPartner, semiconductorMatNbrCustomer, false, true); -// semiconductorPartnerRelation.setPartnerCXNumber(semiconductorMatNbrCatenaX); + customerPartner, semiconductorMatNbrCustomer, true, true); semiconductorPartnerRelation = mprService.create(semiconductorPartnerRelation); log.info("Created Relation " + semiconductorPartnerRelation); @@ -308,6 +329,28 @@ private void setupSupplierRole() { .build(); reportedProductItemStock = reportedProductItemStockService.create(reportedProductItemStock); log.info("Created ReportedProductItemStock \n" + reportedProductItemStock); + + MaterialItemStock materialItemStock = MaterialItemStock.builder() + .partner(customerPartner) + .material(semiconductorMaterial) + .lastUpdatedOnDateTime(new Date()) + .measurementUnit(ItemUnitEnumeration.UNIT_PIECE) + .quantity(400) + .locationBpna(siteLa.getAddresses().stream().findFirst().get().getBpna()) + .locationBpns(siteLa.getBpns()) + .build(); + materialItemStockService.create(materialItemStock); + + ReportedMaterialItemStock reportedMaterialItemStock = ReportedMaterialItemStock.builder() + .partner(customerPartner) + .material(semiconductorMaterial) + .lastUpdatedOnDateTime(new Date()) + .measurementUnit(ItemUnitEnumeration.UNIT_PIECE) + .quantity(23) + .locationBpna(customerPartner.getSites().first().getAddresses().first().getBpna()) + .locationBpns(customerPartner.getSites().first().getBpns()) + .build(); + reportedMaterialItemStockService.create(reportedMaterialItemStock); } /** @@ -402,7 +445,6 @@ private Partner createAndGetNonScenarioCustomer() { private Material getNewSemiconductorMaterialForSupplier() { Material material = new Material(); material.setOwnMaterialNumber(semiconductorMatNbrSupplier); - material.setMaterialNumberCx(semiconductorMatNbrCatenaX); material.setProductFlag(true); material.setName("Semiconductor"); return material; @@ -411,7 +453,6 @@ private Material getNewSemiconductorMaterialForSupplier() { private Material getNewSemiconductorMaterialForCustomer() { Material material = new Material(); material.setOwnMaterialNumber(semiconductorMatNbrCustomer); -// material.setMaterialNumberCx(semiconductorMatNbrCatenaX); material.setMaterialFlag(true); material.setName("Semiconductor"); return material; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/domain/model/DigitalTwinMapping.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/domain/model/DigitalTwinMapping.java index 6de09898..ef5bb247 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/domain/model/DigitalTwinMapping.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/domain/model/DigitalTwinMapping.java @@ -20,13 +20,11 @@ package org.eclipse.tractusx.puris.backend.common.ddtr.domain.model; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; import lombok.*; -import java.util.HashMap; -import java.util.Map; - @Entity @AllArgsConstructor @NoArgsConstructor @@ -41,10 +39,4 @@ public class DigitalTwinMapping { private String productTwinId; - @ElementCollection - @CollectionTable(name = "suppliersmap", joinColumns = @JoinColumn(name = "own_mat_nbr", referencedColumnName = "ownMaterialNumber")) - @MapKeyColumn(name = "key") - @Column(name = "value") - private Map materialSupplierTwinIds = new HashMap<>(); - } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DigitalTwinMappingService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DigitalTwinMappingService.java index 7dbc8dbe..2d2ff60a 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DigitalTwinMappingService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DigitalTwinMappingService.java @@ -24,7 +24,6 @@ import org.eclipse.tractusx.puris.backend.common.ddtr.domain.model.DigitalTwinMapping; import org.eclipse.tractusx.puris.backend.common.ddtr.domain.repository.DigitalTwinMappingRepository; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; -import org.eclipse.tractusx.puris.backend.masterdata.domain.model.MaterialPartnerRelation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -37,6 +36,7 @@ public class DigitalTwinMappingService { @Autowired private DigitalTwinMappingRepository repository; + public DigitalTwinMapping create(Material material) { if(repository.findById(material.getOwnMaterialNumber()).isPresent()) { log.error("DTR Mapping for " + material.getOwnMaterialNumber() + " already exists"); @@ -63,19 +63,6 @@ public DigitalTwinMapping update(Material material) { return repository.save(dtm); } - public DigitalTwinMapping update(MaterialPartnerRelation mpr) { - var searchResult = repository.findById(mpr.getMaterial().getOwnMaterialNumber()); - if (searchResult.isEmpty()) { - log.error("DTR Mapping did not exist. Update failed for " + mpr); - return null; - } - var dtm = searchResult.get(); - if (mpr.getMaterial().isMaterialFlag() && mpr.isPartnerSuppliesMaterial()) { - dtm.getMaterialSupplierTwinIds().put(mpr.getPartner().getBpnl(), mpr.getPartnerCXNumber()); - } - return repository.save(dtm); - } - public DigitalTwinMapping get(String ownMaterialNumber) { return repository.findById(ownMaterialNumber).orElse(null); } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DtrAdapterService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DtrAdapterService.java index 10e1fa92..67e8d7ec 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DtrAdapterService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/DtrAdapterService.java @@ -103,84 +103,73 @@ private Response sendDtrGetRequest(List pathSegments, Map mprs) { + public Integer updateProduct(Material material, List mprs) { String twinId = digitalTwinMappingService.get(material).getProductTwinId(); String idAsBase64 = Base64.getEncoder().encodeToString(twinId.getBytes(StandardCharsets.UTF_8)); var body = dtrRequestBodyBuilder.createProductRegistrationRequestBody(material, twinId, mprs); - try (var response = sendDtrPutRequest(body, List.of("api", "v3.0", "shell-descriptors", idAsBase64))) { - var bodyString = response.body().string(); - log.info("Response Code " + response.code()); - if (response.isSuccessful()) { - return true; - } - log.error("Failure in update for product twin " + material.getOwnMaterialNumber() + "\n" + bodyString); + try (var response = sendDtrPutRequest(body, List.of("api", "v3", "shell-descriptors", idAsBase64))) { + return response.code(); } catch (Exception e) { log.error("Failure in update for product twin " + material.getOwnMaterialNumber(), e); } - return false; + return null; } /** * Call this method when a new Material with a product flag was created in your MaterialService - or if a product * flag was later added to an existing Material. - * + *

* A new AAS will be registered for this Material at your dDTR. * - * @param material The Material - * @return true, if the DTR signaled a successful registration + * @param material The Material + * @return The HTTP response code from the DTR, or null if none was received */ - public boolean registerProductAtDtr(Material material) { + public Integer registerProductAtDtr(Material material) { String twinId = digitalTwinMappingService.get(material).getProductTwinId(); var body = dtrRequestBodyBuilder.createProductRegistrationRequestBody(material, twinId, List.of()); - try (var response = sendDtrPostRequest(body, List.of("api", "v3.0", "shell-descriptors"))) { - var bodyString = response.body().string(); - if (response.isSuccessful()) { - return true; - } - log.error("Failed to register product at DTR " + material.getOwnMaterialNumber() + "\n" + bodyString); + try (var response = sendDtrPostRequest(body, List.of("api", "v3", "shell-descriptors"))) { + return response.code(); } catch (Exception e) { log.error("Failed to register product at DTR " + material.getOwnMaterialNumber(), e); } - return false; + return null; } /** * Call this method when a MaterialPartnerRelation was created or updated it's flag signals that this partner is * a supplier for the referenced Material. * - * @param supplierPartnerRelation The MaterialPartnerRelation indicating a supplier for a given Material. - * @return + * @param supplierPartnerRelation The MaterialPartnerRelation indicating a supplier for a given Material. + * @return The HTTP response code from the DTR, or null if none was received */ - public boolean registerMaterialAtDtr(MaterialPartnerRelation supplierPartnerRelation) { + public Integer registerMaterialAtDtr(MaterialPartnerRelation supplierPartnerRelation) { var body = dtrRequestBodyBuilder.createMaterialRegistrationRequestBody(supplierPartnerRelation); - try (var response = sendDtrPostRequest(body, List.of("api", "v3.0", "shell-descriptors"))) { - var bodyString = response.body().string(); - if(response.isSuccessful()) { - return true; - } - log.error("Failed to register material at DTR " + supplierPartnerRelation.getMaterial().getOwnMaterialNumber() + "\n" + bodyString); + try (var response = sendDtrPostRequest(body, List.of("api", "v3", "shell-descriptors"))) { + return response.code(); } catch (Exception e) { log.error("Failed to register material at DTR " + supplierPartnerRelation.getMaterial().getOwnMaterialNumber(), e); } - return false; + return null; } - public boolean updateMaterialAtDtr(MaterialPartnerRelation supplierPartnerRelation) { + /** + * Updates an existing material-AAS with the Information from the given MaterialPartnerRelation + * + * @param supplierPartnerRelation The MPR that indicates the material and the partner + * @return The HTTP response code from the DTR, or null if none was received + */ + public Integer updateMaterialAtDtr(MaterialPartnerRelation supplierPartnerRelation) { var body = dtrRequestBodyBuilder.createMaterialRegistrationRequestBody(supplierPartnerRelation); String idAsBase64 = Base64.getEncoder().encodeToString(supplierPartnerRelation.getPartnerCXNumber().getBytes(StandardCharsets.UTF_8)); - try (var response = sendDtrPutRequest(body, List.of("api", "v3.0", "shell-descriptors", idAsBase64))) { - var bodyString = response.body().string(); - if(response.isSuccessful()) { - return true; - } - log.error("Failed to register material at DTR " + supplierPartnerRelation.getMaterial().getOwnMaterialNumber() + "\n" + bodyString); + try (var response = sendDtrPutRequest(body, List.of("api", "v3", "shell-descriptors", idAsBase64))) { + return response.code(); } catch (Exception e) { log.error("Failed to register material at DTR " + supplierPartnerRelation.getMaterial().getOwnMaterialNumber(), e); } - return false; + return null; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java index 1c5c9a78..8650b7bf 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Optional; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.SubmodelType; @@ -40,6 +39,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; /** @@ -726,13 +726,14 @@ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String man } HttpUrl.Builder urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() .addPathSegment("api") - .addPathSegment("v3.0") + .addPathSegment("v3") .addPathSegment("lookup") .addPathSegment("shells"); String query = "{\"name\":\"manufacturerPartId\",\"value\":\"" + manufacturerPartId + "\"}"; query += ",{\"name\":\"digitalTwinType\",\"value\":\"PartType\"}"; query += ",{\"name\":\"manufacturerId\",\"value\":\"" + manufacturerId + "\"}"; - urlBuilder.addQueryParameter("assetIds", Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8))); + String encodedQuery = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8)); + urlBuilder.addQueryParameter("assetIds", encodedQuery); var request = new Request.Builder() .get() .header(edrDto.authKey(), edrDto.authCode()) @@ -742,14 +743,18 @@ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String man var bodyString = response.body().string(); var jsonResponse = objectMapper.readTree(bodyString); var resultArray = jsonResponse.get("result"); - if (resultArray.isArray()) { + if (resultArray != null && resultArray.isArray() && !resultArray.isEmpty()) { + if (resultArray.size() > 1) { + log.warn("Found more than one result for query " + query); + log.info(resultArray.toPrettyString()); + } String aasId = resultArray.get(0).asText(); urlBuilder = HttpUrl.parse(edrDto.endpoint()).newBuilder() .addPathSegment("api") - .addPathSegment("v3.0") + .addPathSegment("v3") .addPathSegment("shell-descriptors"); String base64AasId = Base64.getEncoder().encodeToString(aasId.getBytes(StandardCharsets.UTF_8)); - urlBuilder.addQueryParameter("aasIdentifier", base64AasId); + urlBuilder.addPathSegment(base64AasId); request = new Request.Builder() .get() .header(edrDto.authKey(), edrDto.authCode()) @@ -758,10 +763,23 @@ private JsonNode getAasSubmodelDescriptors(String manufacturerPartId, String man try (var response2 = CLIENT.newCall(request).execute()) { var body2String = response2.body().string(); var aasJson = objectMapper.readTree(body2String); - var resultObject = aasJson.get("result").get(0); - var submodelDescriptors = resultObject.get("submodelDescriptors"); - failed = false; - return submodelDescriptors; + var submodelDescriptors = aasJson.get("submodelDescriptors"); + if (submodelDescriptors != null) { + failed = false; + return submodelDescriptors; + } else { + log.warn("No SubmodelDescriptors found in DTR shell-descriptors response:\n" + aasJson.toPrettyString()); + } + } + } else { + if (resultArray != null) { + if (resultArray.isArray() && resultArray.isEmpty()) { + log.warn("Empty Result array received"); + } else { + log.warn("Unexpected Response for DTR lookup with query " + query + "\n" + resultArray.toPrettyString()); + } + } else { + log.warn("No Result Array received in DTR lookup response: \n" + jsonResponse.toPrettyString()); } } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/MaterialPartnerRelation.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/MaterialPartnerRelation.java index 953db106..7a66abef 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/MaterialPartnerRelation.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/MaterialPartnerRelation.java @@ -92,6 +92,18 @@ public String toString() { '}'; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MaterialPartnerRelation that)) return false; + return Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hashCode(key); + } + @Embeddable @Getter @Setter diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialPartnerRelationServiceImpl.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialPartnerRelationServiceImpl.java index 33f10bce..8f75ee15 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialPartnerRelationServiceImpl.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialPartnerRelationServiceImpl.java @@ -21,7 +21,6 @@ */ package org.eclipse.tractusx.puris.backend.masterdata.logic.service; -import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.puris.backend.common.ddtr.logic.DigitalTwinMappingService; @@ -40,6 +39,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.stream.Collectors; @RequiredArgsConstructor @@ -85,13 +85,12 @@ public MaterialPartnerRelation create(MaterialPartnerRelation materialPartnerRel flagConsistencyTest(materialPartnerRelation); var searchResult = find(materialPartnerRelation.getMaterial(), materialPartnerRelation.getPartner()); if (searchResult == null) { - dtmService.update(materialPartnerRelation); - executorService.submit(new DtrRegistrationTask(materialPartnerRelation, "CREATE", 3)); + executorService.submit(new DtrRegistrationTask(materialPartnerRelation, 3)); if (materialPartnerRelation.getMaterial().isMaterialFlag() && materialPartnerRelation.isPartnerSuppliesMaterial() && materialPartnerRelation.getPartnerCXNumber() == null) { log.info("Attempting CX-Id fetch for Material " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " from Supplier-Partner " + materialPartnerRelation.getPartner().getBpnl()); - executorService.submit(new PartTypeInformationRetrievalTask(materialPartnerRelation, 3)); + executorService.submit(new PartTypeInformationRetrievalTask(materialPartnerRelation, 1)); } return mprRepository.save(materialPartnerRelation); } @@ -102,24 +101,51 @@ public MaterialPartnerRelation create(MaterialPartnerRelation materialPartnerRel @Override public void triggerPartTypeRetrievalTask(MaterialPartnerRelation mpr) { if (!currentPartTypeFetches.contains(mpr)) { - executorService.submit(new PartTypeInformationRetrievalTask(mpr, 3)); + executorService.submit(new PartTypeInformationRetrievalTask(mpr, 1)); } } private class PartTypeInformationRetrievalTask implements Callable { + /** + * The MaterialPartnerRelation indicating the supplier partner we want to retrieve data from + * and the material entity we want to fetch the partner's CatenaX-Id for. + */ final MaterialPartnerRelation materialPartnerRelation; + /** + * The number of retries that this task has currently left. + */ int retries; + /** + * The number of retries that this task was given at creation. + */ final int initialRetries; + + /** + * Is set to true if all goals of this task were accomplished or otherwise + * if the task failed and has no more retries left. + */ boolean done = false; - public PartTypeInformationRetrievalTask(MaterialPartnerRelation materialPartnerRelation, int retries) { + /** + * Constructor for a Task that tries to asynchronously fetch the CatenaX-Id from a + * supplier partner for a material entity, as specified by the materialPartnerRelation parameter. + * + * @param materialPartnerRelation the MaterialPartnerRelation + * @param retries a non-negative number of possible retry-attempts. + */ + PartTypeInformationRetrievalTask(MaterialPartnerRelation materialPartnerRelation, int retries) { this.materialPartnerRelation = materialPartnerRelation; this.retries = retries; this.initialRetries = retries; currentPartTypeFetches.add(materialPartnerRelation); } + /** + * This method contains all the duties which the PartTypeInformationRetrievalTask is trying to fulfill. + * + * @return true, if the task finished successfully + */ @Override public Boolean call() { try { @@ -135,7 +161,6 @@ public Boolean call() { } String partnerCXId = edcAdapterService.getCxIdFromPartTypeInformation(materialPartnerRelation); if (partnerCXId != null && PatternStore.URN_OR_UUID_PATTERN.matcher(partnerCXId).matches()) { - materialPartnerRelation.setPartnerCXNumber(partnerCXId); mprRepository.save(materialPartnerRelation); log.info("Successfully inserted Partner CX Id for Partner " + @@ -148,7 +173,7 @@ public Boolean call() { retries--; return call(); } - + done = true; return true; } catch (Exception e) { @@ -165,26 +190,60 @@ public Boolean call() { } - @AllArgsConstructor private class DtrRegistrationTask implements Callable { + + /** + * The MaterialPartnerRelation that was created or updated and therefore makes it necessary to + * create or update material- and/or product AAS's in the dDTR. + */ MaterialPartnerRelation materialPartnerRelation; /** - * Must be either "CREATE" or "UPDATE". The distinction is important, - * because for an existing AAS, the PUT endpoint on the DTR must be called. - * While, on the other hand, for a new AAS the POST endpoint must be called - * at the DTR. + * The number of retries that this task has currently left. */ - final String job; int retries; + /** + * The number of retries that this task was given at creation (minimum value is 1). + */ final int initialRetries; + /** + * If the materialPartnerRelation indicates that the partner is a customer, this + * is set to true + */ + final boolean needProductRegistration; + /** + * Is set to true during runtime if a product registration call to the dDTR ran successfully + */ + boolean completedProductRegistration = false; + /** + * If the materialPartnerRelation indicates that the partner is a supplier, this + * is set to true + */ + final boolean needMaterialRegistration; + /** + * Is set to true during runtime if a material registration call to the dDTR ran successfully + */ + boolean completedMaterialRegistration = false; - public DtrRegistrationTask(MaterialPartnerRelation materialPartnerRelation, String job, int retries) { + /** + * Constructor for a task that makes sure that all potentially needed material and/or product AAS's + * are inserted into the dDTR, when a MaterialPartnerRelation entity is created or updated. + * + * @param materialPartnerRelation + * @param retries + */ + public DtrRegistrationTask(MaterialPartnerRelation materialPartnerRelation, int retries) { this.materialPartnerRelation = materialPartnerRelation; - this.job = job; - this.retries = retries; - this.initialRetries = retries; + this.initialRetries = Math.max(retries, 1); + this.retries = initialRetries; + this.needProductRegistration = materialPartnerRelation.isPartnerBuysMaterial(); + this.needMaterialRegistration = materialPartnerRelation.isPartnerSuppliesMaterial(); } + /** + * This method contains all the duties which the DtrRegistrationTask is trying to fulfill. + * + * @return true, if the task finished successfully + */ @Override public Boolean call() throws Exception { if (retries < 0) { @@ -192,96 +251,92 @@ public Boolean call() throws Exception { } if (retries < initialRetries) { Thread.sleep(2000); + } else { + Thread.sleep(400); } - if (materialPartnerRelation.isPartnerSuppliesMaterial() && materialPartnerRelation.getPartnerCXNumber() == null) { - log.info("Missing partnerCX Number in " + materialPartnerRelation); - log.info("Current list " + currentPartTypeFetches.stream().map(mpr -> mpr.getPartner().getBpnl() + " / " + mpr.getMaterial().getOwnMaterialNumber()).toList()); - if (currentPartTypeFetches.contains(materialPartnerRelation)) { - log.info("Awaiting PartTypeInformation Fetch"); - // await return of ongoing fetch task - while (currentPartTypeFetches.contains(materialPartnerRelation)) { - Thread.yield(); + + if (needProductRegistration && !completedProductRegistration) { + var allCustomers = + mprRepository.findAllByMaterial_OwnMaterialNumberAndPartnerBuysMaterialIsTrue( + materialPartnerRelation.getMaterial().getOwnMaterialNumber()); + if (allCustomers.contains(materialPartnerRelation)) { + Integer result = dtrAdapterService.updateProduct(materialPartnerRelation.getMaterial(), allCustomers); + if (result != null) { + if (result < 400) { + log.info("Updated product ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " and " + + allCustomers.size() + " customer partners. Result: " + result); + completedProductRegistration = true; + } else { + if (result == 404) { + Integer registrationResult = dtrAdapterService.registerProductAtDtr(materialPartnerRelation.getMaterial()); + log.info("Tried to create product AAS for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + + ", result: " + registrationResult); + } + } + } else { + log.warn("Update of product ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber()); } } else { - // initiate new fetch - log.info("Initiating new PartTypeInformation Fetch"); - var futureResult = executorService.submit(new PartTypeInformationRetrievalTask(materialPartnerRelation, 3)); - while (!futureResult.isDone()) { - Thread.yield(); - } - } - Thread.sleep(500); - // get result from database - materialPartnerRelation = find(materialPartnerRelation.getMaterial(), materialPartnerRelation.getPartner()); - if (materialPartnerRelation.getPartnerCXNumber() == null) { - log.error("Missing partnerCX Number in " + materialPartnerRelation + ", retries left: " + retries); - retries--; - return call(); + log.warn("AllCustomers did not contain " + materialPartnerRelation.getKey()); + log.warn("Update of product ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber()); } } - boolean success = true; - switch (job) { - case "UPDATE" -> { - if (materialPartnerRelation.getMaterial().isProductFlag()) { - var allCustomers = - mprRepository.findAllByMaterial_OwnMaterialNumberAndPartnerBuysMaterialIsTrue( - materialPartnerRelation.getMaterial().getOwnMaterialNumber()); - boolean result = dtrAdapterService.updateProduct(materialPartnerRelation.getMaterial(), allCustomers); - if (result) { - log.info("Updated product ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber()); - } else { - log.warn("Update of product ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " Retries left: " + retries); + if (needMaterialRegistration && !completedMaterialRegistration) { + if (materialPartnerRelation.getPartnerCXNumber() == null) { + if (currentPartTypeFetches.contains(materialPartnerRelation)) { + log.info("Awaiting PartTypeInformation Fetch"); + // await return of ongoing fetch task + while (currentPartTypeFetches.contains(materialPartnerRelation)) { + Thread.yield(); } - success &= result; - } - if (materialPartnerRelation.getMaterial().isMaterialFlag()) { - boolean result = dtrAdapterService.updateMaterialAtDtr(materialPartnerRelation); - if (result) { - log.info("Updated material ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + - " and supplier partner " + materialPartnerRelation.getPartner().getBpnl()); - } else { - log.warn("Update of material ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + - " and supplier partner " + materialPartnerRelation.getPartner().getBpnl() + " Retries left: " + retries); + } else { + // initiate new fetch + log.info("Initiating new PartTypeInformation Fetch"); + Future futureResult = executorService.submit(new PartTypeInformationRetrievalTask(materialPartnerRelation, 1)); + while (!futureResult.isDone()) { + Thread.yield(); } - success &= result; } - return success; + Thread.sleep(500); + // get result from database + materialPartnerRelation = find(materialPartnerRelation.getMaterial(), materialPartnerRelation.getPartner()); + if (materialPartnerRelation.getPartnerCXNumber() == null) { + log.error("Missing partnerCX Number in " + materialPartnerRelation + ", retries left: " + retries); + retries--; + return call(); + } } - case "CREATE" -> { - if (materialPartnerRelation.getMaterial().isProductFlag()) { - var allCustomers = - mprRepository.findAllByMaterial_OwnMaterialNumberAndPartnerBuysMaterialIsTrue( - materialPartnerRelation.getMaterial().getOwnMaterialNumber()); - boolean result = dtrAdapterService.updateProduct(materialPartnerRelation.getMaterial(), allCustomers); - if (result) { - log.info("Updated product ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber()); - } else { - log.warn("Update of product ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " Retries left: " + retries); - } - success &= result; - } - if (materialPartnerRelation.getMaterial().isMaterialFlag()) { - boolean result = dtrAdapterService.registerMaterialAtDtr(materialPartnerRelation); - if (result) { - log.info("Created material ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + - " and supplier partner " + materialPartnerRelation.getPartner().getBpnl()); - } else { - log.warn("Creation of material ShellDescriptor failed at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + - " and supplier partner " + materialPartnerRelation.getPartner().getBpnl() + " Retries left: " + retries); + Integer result = dtrAdapterService.updateMaterialAtDtr(materialPartnerRelation); + if (result != null) { + if (result < 400) { + completedMaterialRegistration = true; + log.info("Updated material ShellDescriptor at DTR for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + + " and supplier partner " + materialPartnerRelation.getPartner().getBpnl()); + } else { + if (result == 404) { + Integer registrationResult = dtrAdapterService.registerMaterialAtDtr(materialPartnerRelation); + log.info("Tried to create material AAS for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + + " and partner " + materialPartnerRelation.getPartner().getBpnl() + + ", result: " + registrationResult); } - success &= result; } - } } - if (success) { - return true; - } else { + if ((needMaterialRegistration && !completedMaterialRegistration) || (needProductRegistration && !completedProductRegistration)) { retries--; + String message = "DTR Registration for " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " and " + materialPartnerRelation.getPartner().getBpnl() + " failed"; + if (needMaterialRegistration && !completedMaterialRegistration) { + message += ", Material Registration still needed"; + } + if (needProductRegistration && !completedProductRegistration) { + message += ", Product Registration still needed"; + } + log.warn(message); return call(); } + return true; } } @@ -296,18 +351,13 @@ public MaterialPartnerRelation update(MaterialPartnerRelation materialPartnerRel flagConsistencyTest(materialPartnerRelation); var foundEntity = mprRepository.findById(materialPartnerRelation.getKey()); if (foundEntity.isPresent()) { - dtmService.update(materialPartnerRelation); if (materialPartnerRelation.getMaterial().isMaterialFlag() && materialPartnerRelation.isPartnerSuppliesMaterial() && materialPartnerRelation.getPartnerCXNumber() == null) { log.info("Attempting CX-Id fetch for Material " + materialPartnerRelation.getMaterial().getOwnMaterialNumber() + " from Supplier-Partner " + materialPartnerRelation.getPartner().getBpnl()); executorService.submit(new PartTypeInformationRetrievalTask(materialPartnerRelation, 3)); } - if (!foundEntity.get().isPartnerSuppliesMaterial() && materialPartnerRelation.isPartnerSuppliesMaterial()) { - executorService.submit(new DtrRegistrationTask(materialPartnerRelation, "CREATE", 3)); - } else { - executorService.submit(new DtrRegistrationTask(materialPartnerRelation, "UPDATE", 3)); - } + executorService.submit(new DtrRegistrationTask(materialPartnerRelation, 3)); return mprRepository.save(materialPartnerRelation); } log.error("Could not update MaterialPartnerRelation, " + materialPartnerRelation.getKey() + " didn't exist before"); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialServiceImpl.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialServiceImpl.java index d654e791..25927bfb 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialServiceImpl.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialServiceImpl.java @@ -21,10 +21,8 @@ */ package org.eclipse.tractusx.puris.backend.masterdata.logic.service; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.puris.backend.common.ddtr.logic.DigitalTwinMappingService; -import org.eclipse.tractusx.puris.backend.common.ddtr.logic.DtrAdapterService; import org.eclipse.tractusx.puris.backend.common.util.VariablesService; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.MaterialPartnerRelation; @@ -36,8 +34,6 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; @Service @Slf4j @@ -52,15 +48,9 @@ public class MaterialServiceImpl implements MaterialService { @Autowired private VariablesService variablesService; - @Autowired - private DtrAdapterService dtrAdapterService; - @Autowired private DigitalTwinMappingService dtmService; - @Autowired - private ExecutorService executorService; - @Override public Material create(Material material) { @@ -70,6 +60,7 @@ public Material create(Material material) { do { uuid = UUID.randomUUID(); } while (!materialRepository.findByMaterialNumberCx(uuid.toString()).isEmpty()); + log.info("Auto-generated CX Id for " + material.getOwnMaterialNumber() + " number: " + uuid); material.setMaterialNumberCx(uuid.toString()); } else { log.error("Could not create material " + material.getOwnMaterialNumber() + " because of missing CatenaXId"); @@ -83,42 +74,12 @@ public Material create(Material material) { var searchResult = materialRepository.findById(material.getOwnMaterialNumber()); if (searchResult.isEmpty()) { dtmService.create(material); - if (material.isProductFlag()) { - executorService.submit(new DtrRegistrationTask(material, 3)); - } return materialRepository.save(material); } log.error("Could not create material " + material.getOwnMaterialNumber() + " because it already exists"); return null; } - /** - * Registers a Product at the dDTR. - */ - @AllArgsConstructor - private class DtrRegistrationTask implements Callable { - - private Material material; - int retries; - - @Override - public Boolean call() throws Exception { - if (retries < 0) { - return false; - } - boolean result = dtrAdapterService.registerProductAtDtr(material); - if (result) { - log.info("Registered " + material.getOwnMaterialNumber() + " as a Product at DTR"); - return true; - } else { - log.warn("Registration for " + material.getOwnMaterialNumber() + " as a Product at DTR failed. Retries left: " + retries); - Thread.sleep(500); - retries--; - return call(); - } - - } - } @Override public Material update(Material material) { @@ -132,7 +93,6 @@ public Material update(Material material) { if (!foundMaterial.isProductFlag() && material.isProductFlag()) { dtmService.update(material); - executorService.submit(new DtrRegistrationTask(material, 3)); } return materialRepository.save(material); @@ -154,10 +114,7 @@ public List findAllProducts() { @Override public Material findByOwnMaterialNumber(String ownMaterialNumber) { var searchResult = materialRepository.findById(ownMaterialNumber); - if (searchResult.isPresent()) { - return searchResult.get(); - } - return null; + return searchResult.orElse(null); } @Override diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/controller/StockViewController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/controller/StockViewController.java index 446f54a6..23fcebfe 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/controller/StockViewController.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/controller/StockViewController.java @@ -543,6 +543,7 @@ public ResponseEntity> triggerReportedMaterialStockUpdateForMat return new ResponseEntity<>(HttpStatusCode.valueOf(400)); } Material materialEntity = materialService.findByOwnMaterialNumber(ownMaterialNumber); + log.info("Trigger Reported MaterialStockUpdate"); log.info("Found material: " + (materialEntity != null) + " " + ownMaterialNumber); List allSupplierPartnerEntities = mprService.findAllSuppliersForOwnMaterialNumber(ownMaterialNumber); @@ -571,6 +572,7 @@ public ResponseEntity> triggerReportedProductStockUpdateForMate return new ResponseEntity<>(HttpStatusCode.valueOf(400)); } Material materialEntity = materialService.findByOwnMaterialNumber(ownMaterialNumber); + log.info("Trigger Reported ProductStockUpdate"); log.info("Found material: " + (materialEntity != null) + " " + ownMaterialNumber); List allCustomerPartnerEntities = mprService.findAllCustomersForOwnMaterialNumber(ownMaterialNumber); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/adapter/ItemStockSammMapper.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/adapter/ItemStockSammMapper.java index 9a2c6b64..496d7814 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/adapter/ItemStockSammMapper.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/adapter/ItemStockSammMapper.java @@ -117,8 +117,7 @@ public List itemStockSammToReportedProductItemStock(It String matNbrCatenaX = samm.getMaterialGlobalAssetId(); ArrayList outputList = new ArrayList<>(); if (samm.getDirection() != DirectionCharacteristic.INBOUND) { - log.warn("Direction should be INBOUND, aborting"); - return outputList; + throw new IllegalArgumentException("Direction should be INBOUND, aborting"); } // When deserializing a Samm from a customer, who has sent a report on the @@ -127,11 +126,14 @@ public List itemStockSammToReportedProductItemStock(It // the Samm is the one in our Material entity. Material material = materialService.findByMaterialNumberCx(matNbrCatenaX); if (material == null) { - log.warn("Could not identify materialPartnerRelation with matNbrCatenaX " + matNbrCatenaX + " and partner bpnl " + partner.getBpnl()); - return outputList; + throw new IllegalArgumentException("Could not identify material with CatenaXNbr " + matNbrCatenaX); } var mpr = mprService.find(material, partner); + if (mpr == null) { + throw new IllegalArgumentException("Could not identify materialPartnerRelation with matNbrCatenaX " + + matNbrCatenaX + " and partner bpnl " + partner.getBpnl()); + } for (var position : samm.getPositions()) { String supplierOrderId = null, customerOrderPositionId = null, customerOrderId = null; @@ -165,23 +167,21 @@ public List itemStockSammToReportedMaterialItemStock( String matNbrCatenaX = samm.getMaterialGlobalAssetId(); ArrayList outputList = new ArrayList<>(); if (samm.getDirection() != DirectionCharacteristic.OUTBOUND) { - log.warn("Direction should be OUTBOUND, aborting"); - return outputList; + throw new IllegalArgumentException("Direction should be OUTBOUND, aborting"); } var mpr = mprService.findByPartnerAndPartnerCXNumber(partner, matNbrCatenaX); if (mpr == null) { - log.warn("Could not identify materialPartnerRelation with matNbrCatenaX " + matNbrCatenaX + " and partner bpnl " + partner.getBpnl()); - return outputList; + throw new IllegalArgumentException("Could not identify materialPartnerRelation with matNbrCatenaX " + + matNbrCatenaX + " and partner bpnl " + partner.getBpnl()); } // When deserializing a Samm from a supplier, who has sent a report on the // stocks he has prepared for us, the materialGlobalAssetId used in the communication - // was set by the supplying partner. Therefore the materialGlobalAssetId in + // was set by the supplying partner. Therefore, the materialGlobalAssetId in // the Samm is the one in our MaterialPartnerRelation entity with that partner. Material material = mpr.getMaterial(); if (material == null) { - log.warn("Could not identify material with CatenaXNbr " + matNbrCatenaX); - return outputList; + throw new IllegalArgumentException("Could not identify material with CatenaXNbr " + matNbrCatenaX); } for (var position : samm.getPositions()) { diff --git a/local/docker-compose.yaml b/local/docker-compose.yaml index b0accbcd..bba40fc6 100644 --- a/local/docker-compose.yaml +++ b/local/docker-compose.yaml @@ -77,19 +77,20 @@ services: - "host.docker.internal:host-gateway" # Adjusts container's host file to allow for communication with docker-host machine dtr-customer: - image: tractusx/sldt-digital-twin-registry:0.3.23 + image: tractusx/sldt-digital-twin-registry:0.4.3 container_name: dtr-customer depends_on: postgres-customer: condition: service_healthy healthcheck: - test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3.0/shell-descriptors" ] + test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors" ] interval: 4s timeout: 3s retries: 15 ports: - "127.0.0.1:4243:4243" environment: + REGISTRY_IDM_OWNING_TENANT_ID: BPNL4444444444XX SPRING_DATASOURCE_DRIVERCLASSNAME: org.postgresql.Driver SPRING_DATASOURCE_URL: jdbc:postgresql://customer-postgres:5432/dtr_database SPRING_DATASOURCE_USERNAME: ${PG_USER} @@ -221,19 +222,20 @@ services: - "host.docker.internal:host-gateway" # Adjusts container's host file to allow for communication with docker-host machine dtr-supplier: - image: tractusx/sldt-digital-twin-registry:0.3.23 + image: tractusx/sldt-digital-twin-registry:0.4.3 container_name: dtr-supplier depends_on: postgres-supplier: condition: service_healthy healthcheck: - test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3.0/shell-descriptors" ] + test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4243/api/v3/shell-descriptors" ] interval: 4s timeout: 3s retries: 15 ports: - "127.0.0.1:4244:4243" environment: + REGISTRY_IDM_OWNING_TENANT_ID: BPNL1234567890ZZ SPRING_DATASOURCE_DRIVERCLASSNAME: org.postgresql.Driver SPRING_DATASOURCE_URL: jdbc:postgresql://supplier-postgres:5432/dtr_database SPRING_DATASOURCE_USERNAME: ${PG_USER} From c7eded8da20fc22176e29b59e45e1ceffad3c78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schr=C3=B6der?= <131770181+ReneSchroederLJ@users.noreply.github.com> Date: Tue, 14 May 2024 08:08:53 +0200 Subject: [PATCH 2/2] feat: implemented delivery information edc flow (#355) * feat: implemented delivery information edc flow * chore: updated frontend dependencies * feat: implemented logic for delivery creation based on incoterm * fix: CodeQl findings --- .../logic/util/DtrRequestBodyBuilder.java | 1 + .../common/security/SecurityConfig.java | 2 +- .../controller/DeliveryController.java | 81 +++++++-- .../DeliveryRequestApiController.java | 84 +++++++++ .../DeliveryResponsibilityEnumeration.java | 24 +++ .../domain/model/IncotermEnumeration.java | 29 +++ .../DeliveryInformationSammMapper.java | 168 ++++++++++++++++++ .../delivery/logic/dto/DeliveryDto.java | 2 + .../logic/dto/deliverysamm/Delivery.java | 102 +++++++++++ .../dto/deliverysamm/DeliveryInformation.java | 76 ++++++++ .../logic/dto/deliverysamm/Location.java | 73 ++++++++ .../deliverysamm/OrderPositionReference.java | 73 ++++++++ .../logic/dto/deliverysamm/Position.java | 70 ++++++++ .../logic/dto/deliverysamm/TransitEvent.java | 72 ++++++++ .../dto/deliverysamm/TransitLocations.java | 75 ++++++++ .../service/DeliveryRequestApiService.java | 112 ++++++++++++ .../logic/service/OwnDeliveryService.java | 43 ++++- .../service/ReportedDeliveryService.java | 47 ++++- frontend/DEPENDENCIES | 2 +- 19 files changed, 1109 insertions(+), 27 deletions(-) create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryRequestApiController.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/DeliveryResponsibilityEnumeration.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/adapter/DeliveryInformationSammMapper.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Delivery.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/DeliveryInformation.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Location.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/OrderPositionReference.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Position.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitEvent.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitLocations.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryRequestApiService.java diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java index 584c7d7c..1e9e3b63 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java @@ -97,6 +97,7 @@ public JsonNode createMaterialRegistrationRequestBody(MaterialPartnerRelation ma submodelDescriptorsArray.add(createSubmodelObject(SubmodelType.ITEM_STOCK.URN_SEMANTIC_ID, href + DirectionCharacteristic.INBOUND + "/", variablesService.getItemStockSubmodelApiAssetId())); submodelDescriptorsArray.add(createSubmodelObject(SubmodelType.DEMAND.URN_SEMANTIC_ID, href, variablesService.getDemandSubmodelApiAssetId())); + submodelDescriptorsArray.add(createSubmodelObject(SubmodelType.DELIVERY.URN_SEMANTIC_ID, href, variablesService.getDeliverySubmodelApiAssetId())); log.debug("Created body for material " + material.getOwnMaterialNumber() + "\n" + body.toPrettyString()); return body; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/security/SecurityConfig.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/security/SecurityConfig.java index 4390b2e9..6cf564b6 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/security/SecurityConfig.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/security/SecurityConfig.java @@ -77,7 +77,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests( // any request in spring context (authorizeHttpRequests) -> authorizeHttpRequests - .requestMatchers("/stockView/**", "/partners/**", "/materials/**", "/materialpartnerrelations/**", "/item-stock/**", "/production/**", "/delivery/**", "/demand/**", "/planned-production/**", "/material-demand/**", "/edrendpoint/**", "/edc/**", "/parttypeinformation/**") + .requestMatchers("/stockView/**", "/partners/**", "/materials/**", "/materialpartnerrelations/**", "/item-stock/**", "/production/**", "/delivery/**", "/demand/**", "/planned-production/**", "/material-demand/**", "/delivery-information/**", "/edrendpoint/**", "/edc/**", "/parttypeinformation/**") .authenticated() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/health/**").permitAll() .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll() diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryController.java index 3444c8ec..df82500e 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryController.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryController.java @@ -23,22 +23,30 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.management.openmbean.KeyAlreadyExistsException; -import org.eclipse.tractusx.puris.backend.delivery.domain.model.Delivery; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; import org.eclipse.tractusx.puris.backend.delivery.domain.model.OwnDelivery; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.ReportedDelivery; import org.eclipse.tractusx.puris.backend.delivery.logic.dto.DeliveryDto; +import org.eclipse.tractusx.puris.backend.delivery.logic.service.DeliveryRequestApiService; import org.eclipse.tractusx.puris.backend.delivery.logic.service.OwnDeliveryService; import org.eclipse.tractusx.puris.backend.delivery.logic.service.ReportedDeliveryService; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.dto.PartnerDto; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -46,6 +54,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -67,25 +76,43 @@ public class DeliveryController { @Autowired private ReportedDeliveryService reportedDeliveryService; + @Autowired + private DeliveryRequestApiService deliveryRequestApiService; + @Autowired private MaterialService materialService; @Autowired private PartnerService partnerService; + @Autowired + private MaterialPartnerRelationService mprService; + @Autowired private ModelMapper modelMapper; @Autowired private Validator validator; + private final Pattern materialPattern = PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN; + + @Autowired + private ExecutorService executorService; + @GetMapping() @ResponseBody @Operation(summary = "Get all planned deliveries for the given Material", - description = "Get all planned deliveries for the given material number. Optionally the delivery can be filtered by its partner bpnl.") - public List getAllDeliveries(String materialNumber, Optional bpnl) { - return ownDeliveryService.findAllByFilters(Optional.of(materialNumber), bpnl) + description = "Get all planned deliveries for the given material number. Optionally a bpns and partner bpnl can be provided to filter the deliveries further.") + public List getAllDeliveries(String ownMaterialNumber, Optional bpns, Optional bpnl) { + Material material = materialService.findByOwnMaterialNumber(ownMaterialNumber); + if (material == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Material does not exist."); + } + var reportedDeliveries = reportedDeliveryService.findAllByFilters(Optional.of(ownMaterialNumber), bpns, bpnl) + .stream().map(this::convertToDto).collect(Collectors.toList()); + var ownDeliveries = ownDeliveryService.findAllByFilters(Optional.of(ownMaterialNumber), bpns, bpnl) .stream().map(this::convertToDto).collect(Collectors.toList()); + return List.of(reportedDeliveries, ownDeliveries).stream().flatMap(List::stream).toList(); } @PostMapping() @@ -157,13 +184,35 @@ public void deleteDelivery(@PathVariable UUID id) { ownDeliveryService.delete(id); } - @GetMapping("reported") + @GetMapping("reported/refresh") @ResponseBody - @Operation(summary = "Get all deliveries of partners for a material", - description = "Get all deliveries of partners for a material number. Optionally the partners can be filtered by their bpnl.") - public List getAllDeliveriesForPartner(String materialNumber, Optional bpnl) { - return reportedDeliveryService.findAllByFilters(Optional.of(materialNumber), bpnl) - .stream().map(this::convertToDto).collect(Collectors.toList()); + @Operation( + summary = "Refreshes all reported deliveries", + description = "Refreshes all reported deliveries from the delivery request API." + ) + public ResponseEntity> refreshReportedDeliveries(@RequestParam String ownMaterialNumber) { + if (!materialPattern.matcher(ownMaterialNumber).matches()) { + return new ResponseEntity<>(HttpStatusCode.valueOf(400)); + } + Material materialEntity = materialService.findByOwnMaterialNumber(ownMaterialNumber); + if (materialEntity == null) { + return new ResponseEntity<>(HttpStatusCode.valueOf(404)); + } + + List partners; + if (materialEntity.isMaterialFlag()) { + partners = mprService.findAllSuppliersForOwnMaterialNumber(ownMaterialNumber); + } else { + partners = mprService.findAllCustomersForOwnMaterialNumber(ownMaterialNumber); + } + for (Partner partner : partners) { + executorService.submit(() -> + deliveryRequestApiService.doReportedDeliveryRequest(partner, materialEntity)); + } + + return ResponseEntity.ok(partners.stream() + .map(partner -> modelMapper.map(partner, PartnerDto.class)) + .toList()); } private OwnDelivery convertToEntity(DeliveryDto dto) { @@ -183,13 +232,19 @@ private OwnDelivery convertToEntity(DeliveryDto dto) { return entity; } - private DeliveryDto convertToDto(Delivery entity) { + private DeliveryDto convertToDto(OwnDelivery entity) { DeliveryDto dto = modelMapper.map(entity, DeliveryDto.class); - dto.setOwnMaterialNumber(entity.getMaterial().getOwnMaterialNumber()); + dto.setPartnerBpnl(entity.getPartner().getBpnl()); + dto.setReported(false); + return dto; + } + private DeliveryDto convertToDto(ReportedDelivery entity) { + DeliveryDto dto = modelMapper.map(entity, DeliveryDto.class); + dto.setOwnMaterialNumber(entity.getMaterial().getOwnMaterialNumber()); dto.setPartnerBpnl(entity.getPartner().getBpnl()); - + dto.setReported(true); return dto; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryRequestApiController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryRequestApiController.java new file mode 100644 index 00000000..55f7e4fe --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/controller/DeliveryRequestApiController.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.DeliveryInformation; +import org.eclipse.tractusx.puris.backend.delivery.logic.service.DeliveryRequestApiService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.regex.Pattern; + +@RestController +@RequestMapping("delivery-information") +@Slf4j +/** + * This class offers the endpoint for requesting the PlannedProduction Submodel 2.0.0 + */ +public class DeliveryRequestApiController { + + @Autowired + private DeliveryRequestApiService deliveryRequestApiSrvice; + + private final Pattern bpnlPattern = PatternStore.BPNL_PATTERN; + + private final Pattern urnPattern = PatternStore.URN_OR_UUID_PATTERN; + + + @Operation(summary = "This endpoint receives the Delivery Information Submodel 2.0.0 requests") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error"), + @ApiResponse(responseCode = "501", description = "Unsupported representation") + }) + @GetMapping("request/{materialNumberCx}/{representation}") + public ResponseEntity getDeliveryMapping( + @RequestHeader("edc-bpn") String bpnl, + @PathVariable String materialNumberCx, + @PathVariable String representation + ) { + if (!bpnlPattern.matcher(bpnl).matches() || !urnPattern.matcher(materialNumberCx).matches()) { + log.warn("Rejecting request at Delivery Information Submodel request 2.0.0 endpoint"); + return ResponseEntity.badRequest().build(); + } + + if (!"$value".equals(representation)) { + log.warn("Rejecting request at Delivery Information Submodel request 2.0.0 endpoint, missing '$value' in request"); + if (!PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN.matcher(representation).matches()) { + representation = ""; + } + log.warn("Received " + representation + " from " + bpnl); + return ResponseEntity.status(501).build(); + } + var samm = deliveryRequestApiSrvice.handleDeliverySubmodelRequest(bpnl, materialNumberCx); + if (samm == null) { + return ResponseEntity.status(500).build(); + } + return ResponseEntity.ok(samm); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/DeliveryResponsibilityEnumeration.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/DeliveryResponsibilityEnumeration.java new file mode 100644 index 00000000..0bdc8a87 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/DeliveryResponsibilityEnumeration.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.domain.model; + +public enum DeliveryResponsibilityEnumeration { + CUSTOMER, SUPPLIER, PARTIAL +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/IncotermEnumeration.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/IncotermEnumeration.java index 3d79adda..995b0bc9 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/IncotermEnumeration.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/domain/model/IncotermEnumeration.java @@ -45,4 +45,33 @@ public enum IncotermEnumeration { public String getValue() { return value; } + + public DeliveryResponsibilityEnumeration getResponsibility() { + switch (this) { + case EXW: + return DeliveryResponsibilityEnumeration.CUSTOMER; + case FCA: + return DeliveryResponsibilityEnumeration.PARTIAL; + case FAS: + return DeliveryResponsibilityEnumeration.PARTIAL; + case FOB: + return DeliveryResponsibilityEnumeration.PARTIAL; + case CFR: + return DeliveryResponsibilityEnumeration.PARTIAL; + case CIF: + return DeliveryResponsibilityEnumeration.PARTIAL; + case DAP: + return DeliveryResponsibilityEnumeration.SUPPLIER; + case DPU: + return DeliveryResponsibilityEnumeration.SUPPLIER; + case CPT: + return DeliveryResponsibilityEnumeration.SUPPLIER; + case CIP: + return DeliveryResponsibilityEnumeration.SUPPLIER; + case DDP: + return DeliveryResponsibilityEnumeration.SUPPLIER; + default: + throw new IllegalArgumentException("Unknown Incoterm"); + } + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/adapter/DeliveryInformationSammMapper.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/adapter/DeliveryInformationSammMapper.java new file mode 100644 index 00000000..46629942 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/adapter/DeliveryInformationSammMapper.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.adapter; + +import org.eclipse.tractusx.puris.backend.common.domain.model.measurement.ItemQuantityEntity; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.EventTypeEnumeration; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.OwnDelivery; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.ReportedDelivery; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.Delivery; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.DeliveryInformation; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.Location; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.OrderPositionReference; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.Position; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.TransitEvent; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.TransitLocations; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class DeliveryInformationSammMapper { + @Autowired + private MaterialPartnerRelationService mprService; + + @Autowired + private MaterialService materialService; + + public DeliveryInformation ownDeliveryToSamm(List deliveryList) { + if (deliveryList == null || deliveryList.isEmpty()) { + log.warn("Can't map empty list"); + return null; + } + Partner partner = deliveryList.get(0).getPartner(); + if (deliveryList.stream().anyMatch(deli -> !deli.getPartner().equals(partner))) { + log.warn("Can't map delivery list with different partners"); + return null; + } + Material material = deliveryList.get(0).getMaterial(); + if (deliveryList.stream().anyMatch(deli -> !deli.getMaterial().equals(material))) { + log.warn("Can't map delivery list with different materials"); + return null; + } + var groupedByPositionAttributes = deliveryList + .stream() + .collect(Collectors.groupingBy(prod -> new PositionsMappingHelper( + prod.getCustomerOrderNumber(), + prod.getSupplierOrderNumber(), + prod.getCustomerOrderPositionNumber() + ))); + DeliveryInformation samm = new DeliveryInformation(); + var matNumberCx = material.isProductFlag() ? material.getMaterialNumberCx() : mprService.find(material, partner).getPartnerCXNumber(); + samm.setMaterialGlobalAssetId(matNumberCx); + + var posList = new HashSet(); + samm.setPositions(posList); + for (var mappingHelperListEntry : groupedByPositionAttributes.entrySet()) { + var key = mappingHelperListEntry.getKey(); + var delivery = mappingHelperListEntry.getValue().get(0); + Position position = new Position(); + + posList.add(position); + if (key.customerOrderId != null || key.customerOrderPositionId != null) { + OrderPositionReference opr = new OrderPositionReference( + delivery.getSupplierOrderNumber(), + delivery.getCustomerOrderNumber(), + delivery.getCustomerOrderPositionNumber() + ); + position.setOrderPositionReference(opr); + } + var deliveries = new HashSet(); + position.setDeliveries(deliveries); + for (var v : mappingHelperListEntry.getValue()) { + ItemQuantityEntity itemQuantityEntity = new ItemQuantityEntity(v.getQuantity(), v.getMeasurementUnit()); + Set events = new HashSet(); + events.add(new TransitEvent(v.getDateOfDeparture(), v.getDepartureType())); + events.add(new TransitEvent(v.getDateOfArrival(), v.getArrivalType())); + TransitLocations locations = new TransitLocations(new Location(v.getOriginBpna(), v.getOriginBpns()), new Location(v.getDestinationBpna(), v.getDestinationBpns())); + Delivery newDelivery = new Delivery( + itemQuantityEntity, new Date(), events, locations, v.getTrackingNumber(), v.getIncoterm()); + deliveries.add(newDelivery); + } + } + return samm; + } + + public List sammToReportedDeliveries(DeliveryInformation samm, Partner partner) { + String matNbrCatenaX = samm.getMaterialGlobalAssetId(); + ArrayList outputList = new ArrayList<>(); + var mpr = mprService.findByPartnerAndPartnerCXNumber(partner, matNbrCatenaX); + Material material = materialService.findByMaterialNumberCx(matNbrCatenaX); + if (material == null && mpr == null) { + log.warn("Could not identify material with CatenaXNbr " + matNbrCatenaX); + return outputList; + } + if (material == null) { + material = mpr.getMaterial(); + } + + for (var position : samm.getPositions()) { + String supplierOrderNumber = null, customerOrderPositionNumber = null, customerOrderNumber = null; + if (position.getOrderPositionReference() != null) { + supplierOrderNumber = position.getOrderPositionReference().getSupplierOrderId(); + customerOrderNumber = position.getOrderPositionReference().getCustomerOrderId(); + customerOrderPositionNumber = position.getOrderPositionReference().getCustomerOrderPositionId(); + } + for (var delivery : position.getDeliveries()) { + var builder = ReportedDelivery.builder(); + var arrivalEvent = delivery.getTransitEvents().stream() + .filter(e -> e.getEventType().equals(EventTypeEnumeration.ACTUAL_ARRIVAL) || e.getEventType().equals(EventTypeEnumeration.ESTIMATED_ARRIVAL)) + .findFirst().get(); + var departureEvent = delivery.getTransitEvents().stream() + .filter(e -> e.getEventType().equals(EventTypeEnumeration.ACTUAL_DEPARTURE) || e.getEventType().equals(EventTypeEnumeration.ESTIMATED_DEPARTURE)) + .findFirst().get(); + var newDelivery = builder + .material(material) + .partner(partner) + .customerOrderNumber(customerOrderNumber) + .customerOrderPositionNumber(customerOrderPositionNumber) + .supplierOrderNumber(supplierOrderNumber) + .quantity(delivery.getDeliveryQuantity().getValue()) + .measurementUnit(delivery.getDeliveryQuantity().getUnit()) + .arrivalType(arrivalEvent.getEventType()) + .dateOfArrival(arrivalEvent.getDateTimeOfEvent()) + .departureType(departureEvent.getEventType()) + .dateOfDeparture(departureEvent.getDateTimeOfEvent()) + .originBpns(delivery.getTransitLocations().getOrigin().getBpnsProperty()) + .originBpna(delivery.getTransitLocations().getOrigin().getBpnaProperty()) + .destinationBpns(delivery.getTransitLocations().getDestination().getBpnsProperty()) + .destinationBpna(delivery.getTransitLocations().getDestination().getBpnaProperty()) + .trackingNumber(delivery.getTrackingNumber()) + .incoterm(delivery.getIncoterm()) + .build(); + outputList.add(newDelivery); + } + } + return outputList; + } + + private record PositionsMappingHelper(String customerOrderId, String supplierOrderId, String customerOrderPositionId) {} +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/DeliveryDto.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/DeliveryDto.java index 19dbd6e1..909b0b30 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/DeliveryDto.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/DeliveryDto.java @@ -78,4 +78,6 @@ public class DeliveryDto implements Serializable { private Date dateOfArrival; private EventTypeEnumeration departureType; private EventTypeEnumeration arrivalType; + + private boolean isReported; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Delivery.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Delivery.java new file mode 100644 index 00000000..a9c6b7ca --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Delivery.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.Date; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.tractusx.puris.backend.common.domain.model.measurement.ItemQuantityEntity; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.IncotermEnumeration; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class Delivery { + + @NotNull + private ItemQuantityEntity deliveryQuantity; + + @NotNull + private Date lastUpdatedOnDateTime; + + @NotNull + @Size(min = 1, max = 2) + private Set transitEvents; + + @NotNull + private TransitLocations transitLocations; + + @Pattern(regexp = PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_STRING) + private String trackingNumber; + + private IncotermEnumeration incoterm; + + @JsonCreator + public Delivery(@JsonProperty(value = "deliveryQuantity") ItemQuantityEntity deliveryQuantity, + @JsonProperty(value = "lastUpdatedOnDateTime") Date lastUpdatedOnDateTime, + @JsonProperty(value = "transitEvents") Set transitEvents, + @JsonProperty(value = "transitLocations") TransitLocations transitLocations, + @JsonProperty(value = "trackingNumber") String trackingNumber, + @JsonProperty(value = "incoterm") IncotermEnumeration incoterm) { + this.deliveryQuantity = deliveryQuantity; + this.lastUpdatedOnDateTime = lastUpdatedOnDateTime; + this.transitEvents = transitEvents; + this.transitLocations = transitLocations; + this.trackingNumber = trackingNumber; + this.incoterm = incoterm; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Delivery that = (Delivery) o; + return Objects.equals(deliveryQuantity, that.deliveryQuantity) + && Objects.equals(lastUpdatedOnDateTime, that.lastUpdatedOnDateTime) + && Objects.equals(transitEvents, that.transitEvents) + && Objects.equals(transitLocations, that.transitLocations) + && Objects.equals(trackingNumber, that.trackingNumber) && Objects.equals(incoterm, that.incoterm); + } + + @Override + public int hashCode() { + return Objects.hash(deliveryQuantity, lastUpdatedOnDateTime, transitEvents, transitLocations, trackingNumber, + incoterm); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/DeliveryInformation.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/DeliveryInformation.java new file mode 100644 index 00000000..999a71c7 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/DeliveryInformation.java @@ -0,0 +1,76 @@ + +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.HashSet; +import java.util.Objects; + +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class DeliveryInformation { + + @NotNull + private HashSet positions; + + @NotNull + @Pattern(regexp = PatternStore.URN_OR_UUID_STRING) + private String materialGlobalAssetId; + + @JsonCreator + public DeliveryInformation(@JsonProperty(value = "positions") HashSet positions, + @JsonProperty(value = "materialGlobalAssetId") String materialGlobalAssetId) { + this.positions = positions; + this.materialGlobalAssetId = materialGlobalAssetId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final DeliveryInformation that = (DeliveryInformation) o; + return Objects.equals(positions, that.positions) + && Objects.equals(materialGlobalAssetId, that.materialGlobalAssetId); + } + + @Override + public int hashCode() { + return Objects.hash(positions, materialGlobalAssetId); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Location.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Location.java new file mode 100644 index 00000000..6d92121e --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Location.java @@ -0,0 +1,73 @@ + +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.Objects; + +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class Location { + @Pattern(regexp = PatternStore.BPNA_STRING) + private String bpnaProperty; + + @NotNull + @Pattern(regexp = PatternStore.BPNS_STRING) + private String bpnsProperty; + + @JsonCreator + public Location(@JsonProperty(value = "bpnaProperty") String bpnaProperty, + @JsonProperty(value = "bpnsProperty") String bpnsProperty) { + this.bpnaProperty = bpnaProperty; + this.bpnsProperty = bpnsProperty; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Location that = (Location) o; + return Objects.equals(bpnaProperty, that.bpnaProperty) && Objects.equals(bpnsProperty, that.bpnsProperty); + } + + @Override + public int hashCode() { + return Objects.hash(bpnaProperty, bpnsProperty); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/OrderPositionReference.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/OrderPositionReference.java new file mode 100644 index 00000000..1886b4ea --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/OrderPositionReference.java @@ -0,0 +1,73 @@ + +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class OrderPositionReference { + private String supplierOrderId; + + @NotNull + private String customerOrderId; + + @NotNull + private String customerOrderPositionId; + + @JsonCreator + public OrderPositionReference(@JsonProperty(value = "supplierOrderId") String supplierOrderId, + @JsonProperty(value = "customerOrderId") String customerOrderId, + @JsonProperty(value = "customerOrderPositionId") String customerOrderPositionId) { + this.supplierOrderId = supplierOrderId; + this.customerOrderId = customerOrderId; + this.customerOrderPositionId = customerOrderPositionId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final OrderPositionReference that = (OrderPositionReference) o; + return Objects.equals(supplierOrderId, that.supplierOrderId) + && Objects.equals(customerOrderId, that.customerOrderId) + && Objects.equals(customerOrderPositionId, that.customerOrderPositionId); + } + + @Override + public int hashCode() { + return Objects.hash(supplierOrderId, customerOrderId, customerOrderPositionId); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Position.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Position.java new file mode 100644 index 00000000..8c3c3cd8 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/Position.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.HashSet; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class Position { + private OrderPositionReference orderPositionReference; + + @NotNull + private HashSet deliveries; + + @JsonCreator + public Position( + @JsonProperty(value = "orderPositionReference") OrderPositionReference orderPositionReference, + @JsonProperty(value = "deliveries") HashSet deliveries) { + this.orderPositionReference = orderPositionReference; + this.deliveries = deliveries; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Position that = (Position) o; + return Objects.equals(orderPositionReference, that.orderPositionReference) + && Objects.equals(deliveries, that.deliveries); + } + + @Override + public int hashCode() { + return Objects.hash(orderPositionReference, deliveries); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitEvent.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitEvent.java new file mode 100644 index 00000000..806b7e04 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitEvent.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.Date; +import java.util.Objects; + +import org.eclipse.tractusx.puris.backend.delivery.domain.model.EventTypeEnumeration; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class TransitEvent { + + @NotNull + private Date dateTimeOfEvent; + + @NotNull + private EventTypeEnumeration eventType; + + @JsonCreator + public TransitEvent(@JsonProperty(value = "dateTimeOfEvent") Date dateTimeOfEvent, + @JsonProperty(value = "eventType") EventTypeEnumeration eventType) { + this.dateTimeOfEvent = dateTimeOfEvent; + this.eventType = eventType; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final TransitEvent that = (TransitEvent) o; + return Objects.equals(dateTimeOfEvent, that.dateTimeOfEvent) && Objects.equals(eventType, that.eventType); + } + + @Override + public int hashCode() { + return Objects.hash(dateTimeOfEvent, eventType); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitLocations.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitLocations.java new file mode 100644 index 00000000..6a75a84e --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/dto/deliverysamm/TransitLocations.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.dto.deliverysamm; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * Generated class for Transit Locations. Transit locations describes the + * specific location where products undergo the process of being transported + * from the source or production facility (origin) to the designated endpoint + * location (destination). + */ +@Getter +@Setter +@NoArgsConstructor +@ToString +public class TransitLocations { + + @NotNull + private Location origin; + + @NotNull + private Location destination; + + @JsonCreator + public TransitLocations(@JsonProperty(value = "origin") Location origin, + @JsonProperty(value = "destination") Location destination) { + this.origin = origin; + this.destination = destination; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final TransitLocations that = (TransitLocations) o; + return Objects.equals(origin, that.origin) && Objects.equals(destination, that.destination); + } + + @Override + public int hashCode() { + return Objects.hash(origin, destination); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryRequestApiService.java new file mode 100644 index 00000000..c4c9b1da --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryRequestApiService.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 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.puris.backend.delivery.logic.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.SubmodelType; +import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; +import org.eclipse.tractusx.puris.backend.delivery.logic.adapter.DeliveryInformationSammMapper; +import org.eclipse.tractusx.puris.backend.delivery.logic.dto.deliverysamm.DeliveryInformation; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.MaterialPartnerRelation; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +/** + * This class is a Service that handles requests for Delivery Information + */ +public class DeliveryRequestApiService { + @Autowired + private PartnerService partnerService; + @Autowired + private MaterialService materialService; + @Autowired + private MaterialPartnerRelationService mprService; + @Autowired + private OwnDeliveryService ownDeliveryService; + @Autowired + private ReportedDeliveryService reportedDeliveryService; + @Autowired + private EdcAdapterService edcAdapterService; + @Autowired + private DeliveryInformationSammMapper sammMapper; + @Autowired + private ObjectMapper objectMapper; + + public DeliveryInformation handleDeliverySubmodelRequest(String bpnl, String materialNumberCx) { + Partner partner = partnerService.findByBpnl(bpnl); + if (partner == null) { + log.error("Unknown Partner BPNL " + bpnl); + return null; + } + MaterialPartnerRelation mpr = mprService.findByPartnerAndPartnerCXNumber(partner, materialNumberCx); + Material material = materialService.findByMaterialNumberCx(materialNumberCx); + if (material == null && mpr == null) { + log.error("Unknown Material " + materialNumberCx); + return null; + } + if (material == null) { + material = mpr.getMaterial(); + } + var currentDeliveries = ownDeliveryService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.empty(), Optional.of(partner.getBpnl())); + return sammMapper.ownDeliveryToSamm(currentDeliveries); + } + + public void doReportedDeliveryRequest(Partner partner, Material material) { + try { + var mpr = mprService.find(material, partner); + var direction = material.isMaterialFlag() ? DirectionCharacteristic.OUTBOUND : DirectionCharacteristic.INBOUND; + var data = edcAdapterService.doSubmodelRequest(SubmodelType.DELIVERY, mpr, direction, 1); + var samm = objectMapper.treeToValue(data, DeliveryInformation.class); + var deliveries = sammMapper.sammToReportedDeliveries(samm, partner); + for (var delivery : deliveries) { + var deliveryPartner = delivery.getPartner(); + var deliveryMaterial = delivery.getMaterial(); + if (!partner.equals(deliveryPartner) || !material.equals(deliveryMaterial)) { + log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" + deliveries); + return; + } + } + // delete older data: + var oldDeliveries = reportedDeliveryService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.empty(), Optional.of(partner.getBpnl())); + for (var oldDelivery : oldDeliveries) { + reportedDeliveryService.delete(oldDelivery.getUuid()); + } + for (var newDelivery : deliveries) { + reportedDeliveryService.create(newDelivery); + } + log.info("Updated Reported Deliveries for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); + } catch (Exception e) { + log.error("Error in Reported Deliveries Request for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + } + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/OwnDeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/OwnDeliveryService.java index a99dd661..039185e0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/OwnDeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/OwnDeliveryService.java @@ -43,6 +43,8 @@ public class OwnDeliveryService { protected final Function validator; + private Partner ownPartnerEntity; + public OwnDeliveryService(DeliveryRepository repository, PartnerService partnerService) { this.repository = repository; this.partnerService = partnerService; @@ -63,11 +65,14 @@ public final List findAllByOwnMaterialNumber(String ownMaterialNumb .toList(); } - public final List findAllByFilters(Optional ownMaterialNumber, Optional bpnl) { + public final List findAllByFilters(Optional ownMaterialNumber, Optional bpns, Optional bpnl) { Stream stream = repository.findAll().stream(); if (ownMaterialNumber.isPresent()) { stream = stream.filter(delivery -> delivery.getMaterial().getOwnMaterialNumber().equals(ownMaterialNumber.get())); } + if (bpns.isPresent()) { + stream = stream.filter(delivery -> delivery.getDestinationBpns().equals(bpns.get()) || delivery.getOriginBpns().equals(bpns.get())); + } if (bpnl.isPresent()) { stream = stream.filter(delivery -> delivery.getPartner().getBpnl().equals(bpnl.get())); } @@ -111,21 +116,21 @@ public final void delete(UUID id) { } public boolean validate(OwnDelivery delivery) { - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } return delivery.getQuantity() > 0 && delivery.getMeasurementUnit() != null && delivery.getMaterial() != null && delivery.getPartner() != null && delivery.getTrackingNumber() != null && - delivery.getIncoterm() != null && + validateResponsibility(delivery) && this.validateTransitEvent(delivery) && !delivery.getPartner().equals(ownPartnerEntity) && - !ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && (( delivery.getCustomerOrderNumber() != null && - delivery.getCustomerOrderPositionNumber() != null && - delivery.getSupplierOrderNumber() != null + delivery.getCustomerOrderPositionNumber() != null ) || ( delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && @@ -142,4 +147,30 @@ private boolean validateTransitEvent(OwnDelivery delivery) { !(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) && delivery.getDateOfDeparture().getTime() < delivery.getDateOfArrival().getTime(); } + + private boolean validateResponsibility(OwnDelivery delivery) { + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } + return delivery.getIncoterm() != null && switch (delivery.getIncoterm().getResponsibility()) { + case SUPPLIER -> + delivery.getMaterial().isProductFlag() && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())); + case CUSTOMER -> + delivery.getMaterial().isMaterialFlag() && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())); + case PARTIAL -> + ( + delivery.getMaterial().isProductFlag() && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) + ) || ( + delivery.getMaterial().isMaterialFlag() && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) + ); + }; + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/ReportedDeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/ReportedDeliveryService.java index a791478b..ee99d61c 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/ReportedDeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/ReportedDeliveryService.java @@ -29,16 +29,23 @@ import org.eclipse.tractusx.puris.backend.delivery.domain.model.EventTypeEnumeration; import org.eclipse.tractusx.puris.backend.delivery.domain.model.ReportedDelivery; import org.eclipse.tractusx.puris.backend.delivery.domain.repository.ReportedDeliveryRepository; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.springframework.stereotype.Service; @Service public class ReportedDeliveryService { public final ReportedDeliveryRepository repository; + private final PartnerService partnerService; + protected final Function validator; - public ReportedDeliveryService(ReportedDeliveryRepository repository) { + private Partner ownPartnerEntity; + + public ReportedDeliveryService(ReportedDeliveryRepository repository, PartnerService partnerService) { this.repository = repository; + this.partnerService = partnerService; this.validator = this::validate; } @@ -55,11 +62,14 @@ public final ReportedDelivery findById(UUID id) { return repository.findById(id).orElse(null); } - public final List findAllByFilters(Optional ownMaterialNumber, Optional bpnl) { + public final List findAllByFilters(Optional ownMaterialNumber, Optional bpns, Optional bpnl) { Stream stream = repository.findAll().stream(); if (ownMaterialNumber.isPresent()) { stream = stream.filter(delivery -> delivery.getMaterial().getOwnMaterialNumber().equals(ownMaterialNumber.get())); } + if (bpns.isPresent()) { + stream = stream.filter(delivery -> delivery.getDestinationBpns().equals(bpns.get()) || delivery.getOriginBpns().equals(bpns.get())); + } if (bpnl.isPresent()) { stream = stream.filter(delivery -> delivery.getPartner().getBpnl().equals(bpnl.get())); } @@ -105,13 +115,11 @@ public boolean validate(ReportedDelivery delivery) { delivery.getMaterial() != null && delivery.getPartner() != null && delivery.getTrackingNumber() != null && - delivery.getIncoterm() != null && + validateResponsibility(delivery) && this.validateTransitEvent(delivery) && - !delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) && (( delivery.getCustomerOrderNumber() != null && - delivery.getCustomerOrderPositionNumber() != null && - delivery.getSupplierOrderNumber() != null + delivery.getCustomerOrderPositionNumber() != null ) || ( delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && @@ -128,4 +136,31 @@ private boolean validateTransitEvent(ReportedDelivery delivery) { !(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) && delivery.getDateOfDeparture().getTime() < delivery.getDateOfArrival().getTime(); } + + private boolean validateResponsibility(ReportedDelivery delivery) { + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } + return delivery.getIncoterm() != null && switch (delivery.getIncoterm().getResponsibility()) { + case CUSTOMER -> + delivery.getMaterial().isProductFlag() && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())); + case SUPPLIER -> + delivery.getMaterial().isMaterialFlag() && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())); + case PARTIAL -> + ( + delivery.getMaterial().isMaterialFlag() && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) + + ) || ( + delivery.getMaterial().isProductFlag() && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) + ); + }; + } } diff --git a/frontend/DEPENDENCIES b/frontend/DEPENDENCIES index dbc2562c..09e994b8 100644 --- a/frontend/DEPENDENCIES +++ b/frontend/DEPENDENCIES @@ -211,7 +211,7 @@ npm/npmjs/-/reusify/1.0.4, MIT, approved, clearlydefined npm/npmjs/-/rimraf/3.0.2, ISC, approved, clearlydefined npm/npmjs/-/rollup/4.9.5, MIT, approved, clearlydefined npm/npmjs/-/run-parallel/1.2.0, MIT, approved, clearlydefined -npm/npmjs/-/scheduler/0.23.0, MIT, approved, clearlydefined +npm/npmjs/-/scheduler/0.23.0, MIT, approved, #14589 npm/npmjs/-/semver/6.3.1, ISC, approved, clearlydefined npm/npmjs/-/semver/7.5.4, ISC, approved, clearlydefined npm/npmjs/-/shebang-command/2.0.0, MIT, approved, clearlydefined