From f2ee7c311a1445739a75fe0681e82c85d121b016 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 25 Sep 2025 11:27:52 +0200 Subject: [PATCH 01/10] fix: added more detailed validation for reported services --- .../service/ReportedDeliveryService.java | 168 +++++++++++++----- .../logic/services/ReportedDemandService.java | 60 +++++-- .../service/ReportedProductionService.java | 56 ++++-- .../logic/service/CustomerSupplyService.java | 46 +++-- .../logic/service/SupplierSupplyService.java | 48 +++-- 5 files changed, 278 insertions(+), 100 deletions(-) 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 6d2e8862..b82e613d 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 @@ -21,6 +21,7 @@ package org.eclipse.tractusx.puris.backend.delivery.logic.service; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @@ -76,60 +77,133 @@ public final List createAll(List deliveries) } public boolean validate(ReportedDelivery delivery) { - return - delivery.getQuantity() >= 0 && - delivery.getMeasurementUnit() != null && - delivery.getMaterial() != null && - delivery.getPartner() != null && - validateResponsibility(delivery) && - validateTransitEvent(delivery) && - (( - delivery.getCustomerOrderNumber() != null && - delivery.getCustomerOrderPositionNumber() != null - ) || ( - delivery.getCustomerOrderNumber() == null && - delivery.getCustomerOrderPositionNumber() == null && - delivery.getSupplierOrderNumber() == null - )); + return validateWithDetails(delivery).isEmpty(); } - private boolean validateTransitEvent(ReportedDelivery delivery) { + public List validateWithDetails(ReportedDelivery delivery) { + List errors = new ArrayList<>(); + + if (delivery.getQuantity() < 0) { + errors.add("Quantity must be greater than or equal to 0."); + } + if (delivery.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (delivery.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (delivery.getMaterial() == null) { + errors.add("Missing material."); + } + if (delivery.getPartner() == null) { + errors.add("Missing partner."); + } + errors.addAll(validateResponsibility(delivery)); + errors.addAll(validateTransitEvent(delivery)); + if (!((delivery.getCustomerOrderNumber() != null && delivery.getCustomerOrderPositionNumber() != null) || + (delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && delivery.getSupplierOrderNumber() == null))) { + errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + } + + return errors; + } + + private List validateTransitEvent(ReportedDelivery delivery) { + List errors = new ArrayList<>(); var now = new Date().getTime(); - return - delivery.getDepartureType() != null && - (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE || delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE) && - delivery.getArrivalType() != null && - (delivery.getArrivalType() == EventTypeEnumeration.ESTIMATED_ARRIVAL || delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) && - !(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) && - delivery.getDateOfDeparture().getTime() < delivery.getDateOfArrival().getTime() && - (delivery.getArrivalType() != EventTypeEnumeration.ACTUAL_ARRIVAL || delivery.getDateOfArrival().getTime() < now) && - (delivery.getDepartureType() != EventTypeEnumeration.ACTUAL_DEPARTURE || delivery.getDateOfDeparture().getTime() < now); + + if (delivery.getDepartureType() == null) { + errors.add("Missing departure type."); + } else if (!(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE || delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE)) { + errors.add("Invalid departure type."); + } + if (delivery.getArrivalType() == null) { + errors.add("Missing arrival type."); + } else if (!(delivery.getArrivalType() == EventTypeEnumeration.ESTIMATED_ARRIVAL || delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL)) { + errors.add("Invalid arrival type."); + } + if (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) { + errors.add("Estimated departure cannot have actual arrival."); + } + if (delivery.getDateOfDeparture() == null) { + errors.add("Missing date of departure."); + } + if (delivery.getDateOfArrival() == null) { + errors.add("Missing date of arrival."); + } + if (delivery.getDateOfArrival() != null && delivery.getDateOfDeparture() != null && + delivery.getDateOfDeparture().getTime() >= delivery.getDateOfArrival().getTime()) { + errors.add("Date of departure must be before date of arrival."); + } + if (delivery.getDateOfArrival() != null && + delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL && delivery.getDateOfArrival().getTime() >= now) { + errors.add("Actual arrival date must be in the past."); + } + if (delivery.getDateOfDeparture() != null && + delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE && delivery.getDateOfDeparture().getTime() >= now) { + errors.add("Actual departure date must be in the past."); + } + + return errors; } - private boolean validateResponsibility(ReportedDelivery delivery) { + private List validateResponsibility(ReportedDelivery delivery) { + List errors = new ArrayList<>(); 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.getOriginBpns())) && - delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())); - case SUPPLIER -> - 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().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())) - ); - }; + + if (delivery.getIncoterm() == null) { + errors.add("Missing Incoterm."); + } else { + switch (delivery.getIncoterm().getResponsibility()) { + case SUPPLIER: + if (!delivery.getMaterial().isProductFlag()) { + errors.add("Material must have product flag for supplier responsibility."); + } + if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + errors.add("Origin BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { + errors.add("Destination BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + } + + break; + case CUSTOMER: + if (!delivery.getMaterial().isMaterialFlag()) { + errors.add("Material must have material flag for customer responsibility."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + errors.add("Site BPNS must match one of the own partner entity's site BPNS for customer responsibility."); + } + if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { + errors.add("Site BPNA must match one of the partner entity's site' address BPNAs for customer responsibility."); + } + + break; + case PARTIAL: + if (delivery.getMaterial().isProductFlag()) { + if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) + ) { + return new ArrayList<>(); + } + } + if (delivery.getMaterial().isMaterialFlag()) { + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + return new ArrayList<>(); + } + } + errors.add("Responsibility conditions for partial responsibility are not met."); + break; + default: + errors.add("Invalid incoterm responsibility."); + break; + } + } + return errors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java index a6034ef8..98152a01 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java @@ -20,6 +20,10 @@ See the NOTICE file(s) distributed with this work for additional */ package org.eclipse.tractusx.puris.backend.demand.logic.services; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import org.eclipse.tractusx.puris.backend.demand.domain.model.ReportedDemand; import org.eclipse.tractusx.puris.backend.demand.domain.repository.ReportedDemandRepository; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; @@ -36,18 +40,50 @@ public ReportedDemandService(ReportedDemandRepository repository, PartnerService @Override public boolean validate(ReportedDemand demand) { + return validateWithDetails(demand).isEmpty(); + } + + public List validateWithDetails(ReportedDemand demand) { + List errors = new ArrayList<>(); Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - return - demand.getMaterial() != null && - demand.getPartner() != null && - mprService.partnerOrdersProduct(demand.getMaterial(), demand.getPartner()) && - demand.getQuantity() >= 0 && - demand.getMeasurementUnit() != null && - demand.getDay() != null && - demand.getDemandCategoryCode() != null && - demand.getDemandLocationBpns() != null && - !demand.getPartner().equals(ownPartnerEntity) && - (demand.getSupplierLocationBpns() == null || ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) && - demand.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns())); + + if (demand.getMaterial() == null) { + errors.add("Missing Material."); + } + if (demand.getPartner() == null) { + errors.add("Missing Partner."); + } + if (!mprService.partnerOrdersProduct(demand.getMaterial(), demand.getPartner())) { + errors.add("Cannot order specified material from Partner."); + } + if (demand.getQuantity() <= 0) { + errors.add("Quantity must be greater than 0."); + } + if (demand.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (demand.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (demand.getDay() == null) { + errors.add("Missing day."); + } + if (demand.getDemandCategoryCode() == null) { + errors.add("Missing demand category code."); + } + if (demand.getDemandLocationBpns() == null) { + errors.add("Missing demand location BPNS."); + } + if (demand.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same entity."); + } + if ((demand.getSupplierLocationBpns() != null && + ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) + || demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { + errors.add("Invalid demand: supplier or demand location is not valid."); + } + return errors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java index 03207432..8f0317b7 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java @@ -21,6 +21,8 @@ See the NOTICE file(s) distributed with this work for additional package org.eclipse.tractusx.puris.backend.production.logic.service; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.function.Function; @@ -61,21 +63,43 @@ public final List createAll(List product } public boolean validate(ReportedProduction production) { - return - production.getQuantity() >= 0 && - production.getMeasurementUnit() != null && - production.getEstimatedTimeOfCompletion() != null && - production.getMaterial() != null && - production.getPartner() != null && - production.getProductionSiteBpns() != null && - production.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(production.getProductionSiteBpns())) && - (( - production.getCustomerOrderNumber() != null && - production.getCustomerOrderPositionNumber() != null - ) || ( - production.getCustomerOrderNumber() == null && - production.getCustomerOrderPositionNumber() == null && - production.getSupplierOrderNumber() == null - )); + return validateWithDetails(production).isEmpty(); + } + + public List validateWithDetails(ReportedProduction production) { + List errors = new ArrayList<>(); + + if (production.getQuantity() <= 0) { + errors.add("Quantity must be greater than 0."); + } + if (production.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (production.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (production.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (production.getEstimatedTimeOfCompletion() == null) { + errors.add("Missing estimated time of completion."); + } + if (production.getMaterial() == null) { + errors.add("Missing material."); + } + if (production.getPartner() == null) { + errors.add("Missing partner."); + } + if (production.getProductionSiteBpns() == null) { + errors.add("Missing production site BPNS."); + } + if (production.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { + errors.add("Production site BPNS must match."); + } + if (!((production.getCustomerOrderNumber() != null && production.getCustomerOrderPositionNumber() != null) || + (production.getCustomerOrderNumber() == null && production.getCustomerOrderPositionNumber() == null && production.getSupplierOrderNumber() == null))) { + errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + } + + return errors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java index 5cc3df8a..238079f6 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java @@ -21,6 +21,7 @@ package org.eclipse.tractusx.puris.backend.supply.logic.service; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -29,6 +30,7 @@ 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.demand.logic.services.OwnDemandService; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; 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.domain.model.MaterialItemStock; @@ -110,17 +112,37 @@ public final List findAllByFilters(Optional ownM } public boolean validate(ReportedCustomerSupply daysOfSupply) { - return - daysOfSupply.getPartner() != null && - daysOfSupply.getMaterial() != null && - daysOfSupply.getDate() != null && - daysOfSupply.getDaysOfSupply() >= 0 && - daysOfSupply.getStockLocationBPNS() != null && - daysOfSupply.getStockLocationBPNA() != null && - daysOfSupply.getPartner() != partnerService.getOwnPartnerEntity() && - daysOfSupply.getPartner().getSites().stream().anyMatch(site -> - site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) && - site.getAddresses().stream().anyMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) - ); + return validateWithDetails(daysOfSupply).isEmpty(); + } + + public List validateWithDetails(ReportedCustomerSupply daysOfSupply) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + + if (daysOfSupply.getMaterial() == null) { + errors.add("Missing Material."); + } + if (daysOfSupply.getPartner() == null) { + errors.add("Missing Partner."); + } + if (daysOfSupply.getDate() == null) { + errors.add("Missing date."); + } + if (daysOfSupply.getStockLocationBPNS() == null) { + errors.add("Missing stock location BPNS."); + } + if (daysOfSupply.getStockLocationBPNA() == null) { + errors.add("Missing stock location BPNA."); + } + if (daysOfSupply.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same entity."); + } + if (daysOfSupply.getPartner().getSites().stream().noneMatch(site -> + site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) || + site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) + )) { + errors.add("Invalid days of supply: stock location is not valid."); + } + return errors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java index 76eb5a31..7e9e3de0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java @@ -21,6 +21,7 @@ package org.eclipse.tractusx.puris.backend.supply.logic.service; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -28,6 +29,7 @@ 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.Partner; 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.production.logic.service.OwnProductionService; @@ -109,19 +111,39 @@ public final List findAllByFilters(Optional ownM return stream.toList(); } - public boolean validate(ReportedSupplierSupply daysOfSupply) { - return - daysOfSupply.getPartner() != null && - daysOfSupply.getMaterial() != null && - daysOfSupply.getDate() != null && - daysOfSupply.getDaysOfSupply() >= 0 && - daysOfSupply.getStockLocationBPNS() != null && - daysOfSupply.getStockLocationBPNA() != null && - daysOfSupply.getPartner() != partnerService.getOwnPartnerEntity() && - daysOfSupply.getPartner().getSites().stream().anyMatch(site -> - site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) && - site.getAddresses().stream().anyMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) - ); + return validateWithDetails(daysOfSupply).isEmpty(); + } + + public List validateWithDetails(ReportedSupplierSupply daysOfSupply) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + + if (daysOfSupply.getMaterial() == null) { + errors.add("Missing Material."); + } + if (daysOfSupply.getPartner() == null) { + errors.add("Missing Partner."); + } + if (daysOfSupply.getDate() == null) { + errors.add("Missing date."); + } + if (daysOfSupply.getStockLocationBPNS() == null) { + errors.add("Missing stock location BPNS."); + } + if (daysOfSupply.getStockLocationBPNA() == null) { + errors.add("Missing stock location BPNA."); + } + if (daysOfSupply.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same entity."); + } + if (daysOfSupply.getPartner().getSites().stream().noneMatch(site -> + site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) || + site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) + )) { + errors.add("Invalid days of supply: stock location is not valid."); + } + + return errors; } } From 54d97d603573d5558236cd8e4e75362f1d5d03c8 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Fri, 26 Sep 2025 16:25:18 +0200 Subject: [PATCH 02/10] feat: added validation and error handling to refresh material --- .../service/DeliveryRequestApiService.java | 35 ++++++++++-- .../services/DemandRequestApiService.java | 38 ++++++++++--- .../masterdata/domain/model/RefreshError.java | 33 +++++++++++ .../domain/model/RefreshResult.java | 34 +++++++++++ .../logic/service/MaterialRefreshService.java | 41 +++++++++----- .../service/ProductionRequestApiService.java | 36 ++++++++++-- .../service/ItemStockRequestApiService.java | 56 +++++++++++++++++-- .../ReportedMaterialItemStockService.java | 12 ++++ .../ReportedProductItemStockService.java | 12 ++++ .../DaysOfSupplyRequestApiService.java | 43 +++++++++++--- 10 files changed, 293 insertions(+), 47 deletions(-) create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java create mode 100644 backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java 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 index e0a17770..26f524e5 100644 --- 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 @@ -26,11 +26,14 @@ import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; import org.eclipse.tractusx.puris.backend.delivery.domain.model.DeliveryResponsibilityEnumeration; 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.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.domain.model.RefreshError; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; 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; @@ -38,6 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -144,7 +148,9 @@ public DeliveryInformation handleDeliverySubmodelRequest(String bpnl, String mat return sammMapper.ownDeliveryToSamm(currentDeliveries, partner, material); } - public void doReportedDeliveryRequest(Partner partner, Material material) { + public RefreshResult doReportedDeliveryRequest(Partner partner, Material material) { + List errors = new ArrayList<>(); + List validDeliveries = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -159,23 +165,40 @@ public void doReportedDeliveryRequest(Partner partner, Material material) { 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; + errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl()))); + continue; + } + + List validationErrors = reportedDeliveryService.validateWithDetails(delivery); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validDeliveries.add(delivery); } } + + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedDelivery request from partner {} for material {}: {}", + partner.getBpnl(), material.getOwnMaterialNumber(), errors); + return new RefreshResult("Validation failed for reported deliveries", errors); + } + // delete older data: var oldDeliveries = reportedDeliveryService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.empty(), Optional.of(partner.getBpnl()), Optional.empty(), Optional.empty()); for (var oldDelivery : oldDeliveries) { reportedDeliveryService.delete(oldDelivery.getUuid()); } - for (var newDelivery : deliveries) { + for (var newDelivery : validDeliveries) { reportedDeliveryService.create(newDelivery); } - log.info("Updated Reported Deliveries for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); - + log.info("Successfully updated ReportedDelivery for {} and partner {}", + material.getOwnMaterialNumber(), partner.getBpnl()); materialService.updateTimestamp(material.getOwnMaterialNumber()); + return new RefreshResult("Successfully processed all reported deliveries", errors); } catch (Exception e) { log.error("Error in Reported Deliveries Request for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("System error occurred during processing", errors); } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java index 32784e00..f49d1741 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java @@ -24,10 +24,13 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; +import org.eclipse.tractusx.puris.backend.demand.domain.model.ReportedDemand; import org.eclipse.tractusx.puris.backend.demand.logic.adapter.ShortTermMaterialDemandSammMapper; import org.eclipse.tractusx.puris.backend.demand.logic.dto.demandsamm.ShortTermMaterialDemand; 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.domain.model.RefreshError; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; 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; @@ -35,6 +38,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; @Service @@ -93,7 +98,9 @@ public ShortTermMaterialDemand handleDemandSubmodelRequest(String bpnl, String m return sammMapper.ownDemandToSamm(currentDemands, partner, material); } - public void doReportedDemandRequest(Partner partner, Material material) { + public RefreshResult doReportedDemandRequest(Partner partner, Material material) { + List errors = new ArrayList<>(); + List validDemands = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -107,23 +114,40 @@ public void doReportedDemandRequest(Partner partner, Material material) { var demandPartner = demand.getPartner(); var demandMaterial = demand.getMaterial(); if (!partner.equals(demandPartner) || !material.equals(demandMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" + demands); - return; + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch"))); + continue; } + + List validationErrors = reportedDemandService.validateWithDetails(demand); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validDemands.add(demand); + } + } + + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedDemand request from partner {} for material {}: {}", + partner.getBpnl(), material.getOwnMaterialNumber(), errors); + return new RefreshResult("Validation failed for reported demands", errors); } + // delete older data: var oldDemands = reportedDemandService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl()), Optional.empty()); for (var oldDemand : oldDemands) { reportedDemandService.delete(oldDemand.getUuid()); } - for (var newDemand : demands) { + for (var newDemand : validDemands) { reportedDemandService.create(newDemand); } - log.info("Updated ReportedDemand for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); - - materialService.updateTimestamp(material.getOwnMaterialNumber()); + log.info("Successfully updated ReportedDemand for {} and partner {}", + material.getOwnMaterialNumber(), partner.getBpnl()); + materialService.updateTimestamp(material.getOwnMaterialNumber()); + return new RefreshResult("Successfully processed all reported demands", errors); } catch (Exception e) { log.error("Error in ReportedDemandRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("Error in ReportedDemandRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java new file mode 100644 index 00000000..a06c90a1 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Volkswagen AG + * Copyright (c) 2025 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.masterdata.domain.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class RefreshError { + private List errors; +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java new file mode 100644 index 00000000..fe01ee59 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Volkswagen AG + * Copyright (c) 2025 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.masterdata.domain.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class RefreshResult { + private String message; + private List errors; +} \ No newline at end of file diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java index 8dedd991..751dea97 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java @@ -28,6 +28,7 @@ import org.eclipse.tractusx.puris.backend.delivery.logic.service.DeliveryRequestApiService; import org.eclipse.tractusx.puris.backend.demand.logic.services.DemandRequestApiService; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; import org.eclipse.tractusx.puris.backend.production.logic.service.ProductionRequestApiService; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; import org.eclipse.tractusx.puris.backend.stock.logic.service.ItemStockRequestApiService; @@ -73,54 +74,68 @@ public void refreshPartnerData(String ownMaterialNumber) { allPartners.addAll(suppliers); var numberOfTasks = (customers.size() + suppliers.size()) * 3 + allPartners.size(); ExecutorService executorService = Executors.newFixedThreadPool(numberOfTasks); - List> futures = new ArrayList<>(); + List> futures = new ArrayList<>(); + // customers customers.forEach(customer -> { - futures.add(CompletableFuture.runAsync( + futures.add(CompletableFuture.supplyAsync( () -> demandRequestApiService.doReportedDemandRequest(customer, material), executorService)); futures.add( - CompletableFuture.runAsync( + CompletableFuture.supplyAsync( () -> itemStockRequestApiService .doItemStockSubmodelReportedProductItemStockRequest( customer, material), executorService)); futures.add( - CompletableFuture.runAsync( + CompletableFuture.supplyAsync( () -> daysOfSupplyRequestApiService .doReportedDaysOfSupplyRequest(customer, material, DirectionCharacteristic.INBOUND), executorService)); }); + // suppliers suppliers.forEach(supplier -> { - futures.add(CompletableFuture.runAsync( + futures.add(CompletableFuture.supplyAsync( () -> productionRequestApiService.doReportedProductionRequest(supplier, material), executorService)); futures.add( - CompletableFuture.runAsync( + CompletableFuture.supplyAsync( () -> itemStockRequestApiService .doItemStockSubmodelReportedMaterialItemStockRequest( supplier, material), executorService)); futures.add( - CompletableFuture.runAsync( + CompletableFuture.supplyAsync( () -> daysOfSupplyRequestApiService .doReportedDaysOfSupplyRequest(supplier, material, DirectionCharacteristic.OUTBOUND), executorService)); }); + // deliveries allPartners.forEach(partner -> { - futures.add(CompletableFuture.runAsync( + futures.add(CompletableFuture.supplyAsync( () -> deliveryRequestApiService.doReportedDeliveryRequest(partner, material), executorService)); }); - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenRun(() -> { - var topic = "/topic/material/" + material.getOwnMaterialNumber(); - messagingTemplate.convertAndSend(topic, "SUCCESS"); - log.info("Refreshed Material data for " + material.getOwnMaterialNumber()); - }); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream().map(CompletableFuture::join).toList()) + .thenAccept(results -> { + boolean hasErrors = results.stream().anyMatch(r -> !r.getErrors().isEmpty()); + + var topic = "/topic/material/" + material.getOwnMaterialNumber(); + if (hasErrors) { + messagingTemplate.convertAndSend(topic, "ERROR"); + log.warn("Refresh completed with errors for material {}", material.getOwnMaterialNumber()); + } else { + messagingTemplate.convertAndSend(topic, "SUCCESS"); + log.info("Successfully refreshed material {}", material.getOwnMaterialNumber()); + } + }); + executorService.shutdown(); } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java index c89a2296..9e3c6550 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java @@ -26,15 +26,20 @@ import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; 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.domain.model.RefreshError; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; 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.production.domain.model.ReportedProduction; import org.eclipse.tractusx.puris.backend.production.logic.adapter.PlannedProductionSammMapper; import org.eclipse.tractusx.puris.backend.production.logic.dto.plannedproductionsamm.PlannedProductionOutput; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; @Service @@ -77,7 +82,9 @@ public PlannedProductionOutput handleProductionSubmodelRequest(String bpnl, Stri return sammMapper.ownProductionToSamm(currentProduction, partner, material); } - public void doReportedProductionRequest(Partner partner, Material material) { + public RefreshResult doReportedProductionRequest(Partner partner, Material material) { + List errors = new ArrayList<>(); + List validProductions = new ArrayList<>(); try { var mpr = mprService.find(material, partner); var data = edcAdapterService.doSubmodelRequest(AssetType.PRODUCTION_SUBMODEL, mpr, DirectionCharacteristic.OUTBOUND, 1); @@ -87,23 +94,40 @@ public void doReportedProductionRequest(Partner partner, Material material) { var productionPartner = production.getPartner(); var productionMaterial = production.getMaterial(); if (!partner.equals(productionPartner) || !material.equals(productionMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl()); - return; + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch"))); + continue; + } + + List validationErrors = reportedProductionService.validateWithDetails(production); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validProductions.add(production); } } + + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedProduction request from partner {} for material {}: {}", + partner.getBpnl(), material.getOwnMaterialNumber(), errors); + return new RefreshResult("Validation failed for reported productions", errors); + } + // delete older data: var oldProductions = reportedProductionService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl()), Optional.empty(), Optional.empty()); for (var oldProduction : oldProductions) { reportedProductionService.delete(oldProduction.getUuid()); } - for (var newProduction : productions) { + for (var newProduction : validProductions) { reportedProductionService.create(newProduction); } - log.info("Updated ReportedProduction for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); - + log.info("Successfully updated ReportedProduction for {} and partner {}", + material.getOwnMaterialNumber(), partner.getBpnl()); materialService.updateTimestamp(material.getOwnMaterialNumber()); + return new RefreshResult("Successfully processed all reported productions", errors); } catch (Exception e) { log.error("Error in ReportedProductionRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("System error occurred during processing", errors); } } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java index 9ed05a66..c419e9bb 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java @@ -22,14 +22,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; import org.eclipse.tractusx.puris.backend.erpadapter.logic.service.ErpAdapterTriggerService; 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.domain.model.RefreshError; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; 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.domain.model.ReportedMaterialItemStock; +import org.eclipse.tractusx.puris.backend.stock.domain.model.ReportedProductItemStock; import org.eclipse.tractusx.puris.backend.stock.logic.adapter.ItemStockSammMapper; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.ItemStockSamm; @@ -125,7 +133,9 @@ public ItemStockSamm handleItemStockSubmodelRequest(String bpnl, String material } - public void doItemStockSubmodelReportedMaterialItemStockRequest(Partner partner, Material material) { + public RefreshResult doItemStockSubmodelReportedMaterialItemStockRequest(Partner partner, Material material) { + List errors = new ArrayList<>(); + List validStocks = new ArrayList<>(); try { var mpr = mprService.find(material, partner); var data = edcAdapterService.doSubmodelRequest(AssetType.ITEM_STOCK_SUBMODEL, mpr, DirectionCharacteristic.OUTBOUND, 1); @@ -135,10 +145,23 @@ public void doItemStockSubmodelReportedMaterialItemStockRequest(Partner partner, var stockPartner = stock.getPartner(); var stockMaterial = stock.getMaterial(); if (!partner.equals(stockPartner) || !material.equals(stockMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" + stocks); - return; + errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl() + "\n" + stocks))); + continue; } + + List validationErrors = reportedMaterialItemStockService.validateWithDetails(stock); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validStocks.add(stock); + } + } + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedMaterialItemStock request from partner {}: {}", + partner.getBpnl(), errors); + return new RefreshResult("Validation failed for reported materials", errors); } + // delete older data: var oldStocks = reportedMaterialItemStockService.findByPartnerAndMaterial(partner, material); for (var oldStock : oldStocks) { reportedMaterialItemStockService.delete(oldStock.getUuid()); @@ -149,12 +172,17 @@ public void doItemStockSubmodelReportedMaterialItemStockRequest(Partner partner, log.info("Updated ReportedMaterialItemStocks for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); materialService.updateTimestamp(material.getOwnMaterialNumber()); + return new RefreshResult("Updated ReportedMaterialItemStocks for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } catch (Exception e) { log.error("Error in ReportedMaterialItemStockRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("Error in ReportedMaterialItemStockRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } } - public void doItemStockSubmodelReportedProductItemStockRequest(Partner partner, Material material) { + public RefreshResult doItemStockSubmodelReportedProductItemStockRequest(Partner partner, Material material) { + List errors = new ArrayList<>(); + List validStocks = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -168,10 +196,23 @@ public void doItemStockSubmodelReportedProductItemStockRequest(Partner partner, var stockPartner = stock.getPartner(); var stockMaterial = stock.getMaterial(); if (!partner.equals(stockPartner) || !material.equals(stockMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" + stocks); - return; + errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl() + "\n" + stocks))); + continue; + } + + List validationErrors = reportedProductItemStockService.validateWithDetails(stock); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validStocks.add(stock); } } + + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedProductItemStock request from partner {}: {}", + partner.getBpnl(), errors); + return new RefreshResult("Validation failed for reported item stocks", errors); + } // delete older data: var oldStocks = reportedProductItemStockService.findByPartnerAndMaterial(partner, material); for (var oldStock : oldStocks) { @@ -183,8 +224,11 @@ public void doItemStockSubmodelReportedProductItemStockRequest(Partner partner, log.info("Updated ReportedProductItemStocks for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); materialService.updateTimestamp(material.getOwnMaterialNumber()); + return new RefreshResult("Updated ReportedProductItemStocks for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } catch (Exception e) { log.error("Error in ReportedProductItemStockRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("Error in ReportedProductItemStockRequest for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java index de8f6cdf..56181203 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java @@ -21,6 +21,10 @@ package org.eclipse.tractusx.puris.backend.stock.logic.service; import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.eclipse.tractusx.puris.backend.stock.domain.model.ReportedMaterialItemStock; @@ -42,4 +46,12 @@ public ReportedMaterialItemStockService(PartnerService partnerService, MaterialP public boolean validate(ReportedMaterialItemStock itemStock) { return basicValidation(itemStock).isEmpty() && validateMaterialItemStock(itemStock).isEmpty() && validateRemoteStock(itemStock).isEmpty(); } + + public List validateWithDetails(ReportedMaterialItemStock itemStock) { + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(itemStock)); + validationErrors.addAll(validateMaterialItemStock(itemStock)); + validationErrors.addAll(validateRemoteStock(itemStock)); + return validationErrors; + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java index 08124ed7..c35284e0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java @@ -21,6 +21,10 @@ package org.eclipse.tractusx.puris.backend.stock.logic.service; import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.eclipse.tractusx.puris.backend.stock.domain.model.ReportedProductItemStock; @@ -43,4 +47,12 @@ public ReportedProductItemStockService(PartnerService partnerService, MaterialPa public boolean validate(ReportedProductItemStock itemStock) { return basicValidation(itemStock).isEmpty() && validateProductItemStock(itemStock).isEmpty() && validateRemoteStock(itemStock).isEmpty(); } + + public List validateWithDetails(ReportedProductItemStock itemStock) { + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(itemStock)); + validationErrors.addAll(validateProductItemStock(itemStock)); + validationErrors.addAll(validateRemoteStock(itemStock)); + return validationErrors; + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java index 2b1c764c..65fedd8e 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java @@ -27,6 +27,8 @@ 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.domain.model.RefreshError; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.RefreshResult; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; @@ -110,7 +112,10 @@ public DaysOfSupply handleDaysOfSupplySubmodelRequest(String bpnl, String materi } } - public void doReportedDaysOfSupplyRequest(Partner partner, Material material, DirectionCharacteristic direction) { + public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material material, DirectionCharacteristic direction) { + List errors = new ArrayList<>(); + List validSupplierSupply = new ArrayList<>(); + List validCustomerSupply = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -125,16 +130,27 @@ public void doReportedDaysOfSupplyRequest(Partner partner, Material material, Di var supplyPartner = reportedCustomerSupply.getPartner(); var supplyMaterial = reportedCustomerSupply.getMaterial(); if (!partner.equals(supplyPartner) || !material.equals(supplyMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" - + reportedCustomerSupplies); - return; + errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl()))); + continue; } + + List validationErrors = customerSupplyService.validateWithDetails(reportedCustomerSupply); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validCustomerSupply.add(reportedCustomerSupply); + } + } + if (!errors.isEmpty()) { + log.warn("Validation errors found for ReportedSupply request from partner {}: {}", + partner.getBpnl(), errors); + return new RefreshResult("Validation failed for reported supplies", errors); } var oldSupplies = customerSupplyService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl()), Optional.empty()); for (var oldSupply : oldSupplies) { customerSupplyService.deleteReportedSupply(oldSupply); } - for (var newSupply : reportedCustomerSupplies) { + for (var newSupply : validCustomerSupply) { customerSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedCustomerSupply.class)); } } else { @@ -143,22 +159,31 @@ public void doReportedDaysOfSupplyRequest(Partner partner, Material material, Di var supplyPartner = reportedSupplierSupply.getPartner(); var supplyMaterial = reportedSupplierSupply.getMaterial(); if (!partner.equals(supplyPartner) || !material.equals(supplyMaterial)) { - log.warn("Received inconsistent data from " + partner.getBpnl() + "\n" - + reportedSupplierSupplies); - return; + errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl()))); + continue; + } + + List validationErrors = supplierSupplyService.validateWithDetails(reportedSupplierSupply); + if (!validationErrors.isEmpty()) { + errors.add(new RefreshError(validationErrors)); + } else { + validSupplierSupply.add(reportedSupplierSupply); } } var oldSupplies = supplierSupplyService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl()), Optional.empty()); for (var oldSupply : oldSupplies) { supplierSupplyService.deleteReportedSupply(oldSupply); } - for (var newSupply : reportedSupplierSupplies) { + for (var newSupply : validSupplierSupply) { supplierSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedSupplierSupply.class)); } } log.info("Updated ReportedSupply for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); + return new RefreshResult("Updated ReportedSupply for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } catch (Exception e) { log.error("Error in ReportedDaysOfSupply request for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + errors.add(new RefreshError(List.of("System error: " + e.getMessage()))); + return new RefreshResult("Error in ReportedDaysOfSupply request for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), errors); } } } From dd96ed820ee86c80f21267b23a78a5d1dd9bdb81 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 2 Oct 2025 10:15:44 +0200 Subject: [PATCH 03/10] feat: fixed error handling for refresh material --- .../service/DeliveryRequestApiService.java | 5 +--- .../service/ReportedDeliveryService.java | 12 ++++----- .../services/DemandRequestApiService.java | 12 ++++++--- .../logic/service/MaterialRefreshService.java | 27 +++++++++++++++---- .../service/ProductionRequestApiService.java | 5 +--- .../service/ItemStockRequestApiService.java | 6 ----- .../DaysOfSupplyRequestApiService.java | 10 ++----- 7 files changed, 40 insertions(+), 37 deletions(-) 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 index 26f524e5..1aa888d6 100644 --- 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 @@ -150,7 +150,6 @@ public DeliveryInformation handleDeliverySubmodelRequest(String bpnl, String mat public RefreshResult doReportedDeliveryRequest(Partner partner, Material material) { List errors = new ArrayList<>(); - List validDeliveries = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -172,8 +171,6 @@ public RefreshResult doReportedDeliveryRequest(Partner partner, Material materia List validationErrors = reportedDeliveryService.validateWithDetails(delivery); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validDeliveries.add(delivery); } } @@ -188,7 +185,7 @@ public RefreshResult doReportedDeliveryRequest(Partner partner, Material materia for (var oldDelivery : oldDeliveries) { reportedDeliveryService.delete(oldDelivery.getUuid()); } - for (var newDelivery : validDeliveries) { + for (var newDelivery : deliveries) { reportedDeliveryService.create(newDelivery); } log.info("Successfully updated ReportedDelivery for {} and partner {}", 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 b82e613d..c32f0f07 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 @@ -172,8 +172,8 @@ private List validateResponsibility(ReportedDelivery delivery) { break; case CUSTOMER: - if (!delivery.getMaterial().isMaterialFlag()) { - errors.add("Material must have material flag for customer responsibility."); + if (!delivery.getMaterial().isProductFlag()) { + errors.add("Material must have product flag for customer responsibility."); } if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { errors.add("Site BPNS must match one of the own partner entity's site BPNS for customer responsibility."); @@ -185,15 +185,15 @@ private List validateResponsibility(ReportedDelivery delivery) { break; case PARTIAL: if (delivery.getMaterial().isProductFlag()) { - if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) + if (delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) ) { return new ArrayList<>(); } } if (delivery.getMaterial().isMaterialFlag()) { - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + if (ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { return new ArrayList<>(); } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java index f49d1741..9a881eba 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java @@ -22,8 +22,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; + +import org.eclipse.tractusx.puris.backend.common.domain.model.measurement.ItemUnitEnumeration; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; +import org.eclipse.tractusx.puris.backend.demand.domain.model.DemandCategoryEnumeration; +import org.eclipse.tractusx.puris.backend.demand.domain.model.OwnDemand; import org.eclipse.tractusx.puris.backend.demand.domain.model.ReportedDemand; import org.eclipse.tractusx.puris.backend.demand.logic.adapter.ShortTermMaterialDemandSammMapper; import org.eclipse.tractusx.puris.backend.demand.logic.dto.demandsamm.ShortTermMaterialDemand; @@ -100,7 +104,6 @@ public ShortTermMaterialDemand handleDemandSubmodelRequest(String bpnl, String m public RefreshResult doReportedDemandRequest(Partner partner, Material material) { List errors = new ArrayList<>(); - List validDemands = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -110,6 +113,9 @@ public RefreshResult doReportedDemandRequest(Partner partner, Material material) var data = edcAdapterService.doSubmodelRequest(AssetType.DEMAND_SUBMODEL, mpr, DirectionCharacteristic.INBOUND, 1); var samm = objectMapper.treeToValue(data, ShortTermMaterialDemand.class); var demands = sammMapper.sammToReportedDemand(samm, partner); + + demands.get(demands.size() - 1).setQuantity(-20.0); + for (var demand : demands) { var demandPartner = demand.getPartner(); var demandMaterial = demand.getMaterial(); @@ -121,8 +127,6 @@ public RefreshResult doReportedDemandRequest(Partner partner, Material material) List validationErrors = reportedDemandService.validateWithDetails(demand); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validDemands.add(demand); } } @@ -137,7 +141,7 @@ public RefreshResult doReportedDemandRequest(Partner partner, Material material) for (var oldDemand : oldDemands) { reportedDemandService.delete(oldDemand.getUuid()); } - for (var newDemand : validDemands) { + for (var newDemand : demands) { reportedDemandService.create(newDemand); } log.info("Successfully updated ReportedDemand for {} and partner {}", diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java index 751dea97..d5287b7e 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java @@ -22,9 +22,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.tractusx.puris.backend.delivery.logic.service.DeliveryRequestApiService; import org.eclipse.tractusx.puris.backend.demand.logic.services.DemandRequestApiService; @@ -66,6 +68,9 @@ public class MaterialRefreshService { @Autowired private SimpMessagingTemplate messagingTemplate; + @Autowired + private ObjectMapper objectMapper; + public void refreshPartnerData(String ownMaterialNumber) { var material = materialService.findByOwnMaterialNumber(ownMaterialNumber); var customers = partnerService.findAllCustomerPartnersForMaterialId(ownMaterialNumber); @@ -124,15 +129,27 @@ public void refreshPartnerData(String ownMaterialNumber) { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenApply(v -> futures.stream().map(CompletableFuture::join).toList()) .thenAccept(results -> { - boolean hasErrors = results.stream().anyMatch(r -> !r.getErrors().isEmpty()); + var allErrors = results.stream() + .filter(Objects::nonNull) + .flatMap(r -> r.getErrors().stream()) + .toList(); var topic = "/topic/material/" + material.getOwnMaterialNumber(); - if (hasErrors) { - messagingTemplate.convertAndSend(topic, "ERROR"); - log.warn("Refresh completed with errors for material {}", material.getOwnMaterialNumber()); - } else { + if (allErrors.isEmpty()) { messagingTemplate.convertAndSend(topic, "SUCCESS"); log.info("Successfully refreshed material {}", material.getOwnMaterialNumber()); + } else { + try { + var json = objectMapper.writeValueAsString(allErrors); + messagingTemplate.convertAndSend(topic, json); + log.warn("Refresh completed with errors for material {}: {}", + material.getOwnMaterialNumber(), json); + } catch (Exception e) { + messagingTemplate.convertAndSend(topic, "[{\"errors\":[\"Serialization error: " + + e.getMessage().replace("\"","\\\"") + "\"]}]"); + log.error("Failed to serialize error payload for material {}", + material.getOwnMaterialNumber(), e); + } } }); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java index 9e3c6550..e3bf7732 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java @@ -84,7 +84,6 @@ public PlannedProductionOutput handleProductionSubmodelRequest(String bpnl, Stri public RefreshResult doReportedProductionRequest(Partner partner, Material material) { List errors = new ArrayList<>(); - List validProductions = new ArrayList<>(); try { var mpr = mprService.find(material, partner); var data = edcAdapterService.doSubmodelRequest(AssetType.PRODUCTION_SUBMODEL, mpr, DirectionCharacteristic.OUTBOUND, 1); @@ -101,8 +100,6 @@ public RefreshResult doReportedProductionRequest(Partner partner, Material mater List validationErrors = reportedProductionService.validateWithDetails(production); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validProductions.add(production); } } @@ -117,7 +114,7 @@ public RefreshResult doReportedProductionRequest(Partner partner, Material mater for (var oldProduction : oldProductions) { reportedProductionService.delete(oldProduction.getUuid()); } - for (var newProduction : validProductions) { + for (var newProduction : productions) { reportedProductionService.create(newProduction); } log.info("Successfully updated ReportedProduction for {} and partner {}", diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java index c419e9bb..b15343d9 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java @@ -135,7 +135,6 @@ public ItemStockSamm handleItemStockSubmodelRequest(String bpnl, String material public RefreshResult doItemStockSubmodelReportedMaterialItemStockRequest(Partner partner, Material material) { List errors = new ArrayList<>(); - List validStocks = new ArrayList<>(); try { var mpr = mprService.find(material, partner); var data = edcAdapterService.doSubmodelRequest(AssetType.ITEM_STOCK_SUBMODEL, mpr, DirectionCharacteristic.OUTBOUND, 1); @@ -152,8 +151,6 @@ public RefreshResult doItemStockSubmodelReportedMaterialItemStockRequest(Partner List validationErrors = reportedMaterialItemStockService.validateWithDetails(stock); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validStocks.add(stock); } } if (!errors.isEmpty()) { @@ -182,7 +179,6 @@ public RefreshResult doItemStockSubmodelReportedMaterialItemStockRequest(Partner public RefreshResult doItemStockSubmodelReportedProductItemStockRequest(Partner partner, Material material) { List errors = new ArrayList<>(); - List validStocks = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -203,8 +199,6 @@ public RefreshResult doItemStockSubmodelReportedProductItemStockRequest(Partner List validationErrors = reportedProductItemStockService.validateWithDetails(stock); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validStocks.add(stock); } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java index 65fedd8e..7b35f2b0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java @@ -114,8 +114,6 @@ public DaysOfSupply handleDaysOfSupplySubmodelRequest(String bpnl, String materi public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material material, DirectionCharacteristic direction) { List errors = new ArrayList<>(); - List validSupplierSupply = new ArrayList<>(); - List validCustomerSupply = new ArrayList<>(); try { var mpr = mprService.find(material, partner); if (mpr.getPartnerCXNumber() == null) { @@ -137,8 +135,6 @@ public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material mat List validationErrors = customerSupplyService.validateWithDetails(reportedCustomerSupply); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validCustomerSupply.add(reportedCustomerSupply); } } if (!errors.isEmpty()) { @@ -150,7 +146,7 @@ public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material mat for (var oldSupply : oldSupplies) { customerSupplyService.deleteReportedSupply(oldSupply); } - for (var newSupply : validCustomerSupply) { + for (var newSupply : reportedCustomerSupplies) { customerSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedCustomerSupply.class)); } } else { @@ -166,15 +162,13 @@ public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material mat List validationErrors = supplierSupplyService.validateWithDetails(reportedSupplierSupply); if (!validationErrors.isEmpty()) { errors.add(new RefreshError(validationErrors)); - } else { - validSupplierSupply.add(reportedSupplierSupply); } } var oldSupplies = supplierSupplyService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl()), Optional.empty()); for (var oldSupply : oldSupplies) { supplierSupplyService.deleteReportedSupply(oldSupply); } - for (var newSupply : validSupplierSupply) { + for (var newSupply : reportedSupplierSupplies) { supplierSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedSupplierSupply.class)); } } From b679c2120d3e6cd038f56677e9c1440820c2ffb7 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 2 Oct 2025 10:17:28 +0200 Subject: [PATCH 04/10] feat: added frontend download link for material refresh error case --- .../components/MaterialDetails.tsx | 90 +++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/material-details/components/MaterialDetails.tsx b/frontend/src/features/material-details/components/MaterialDetails.tsx index 5a3d440b..1fd6f122 100644 --- a/frontend/src/features/material-details/components/MaterialDetails.tsx +++ b/frontend/src/features/material-details/components/MaterialDetails.tsx @@ -36,7 +36,7 @@ import { scheduleErpUpdateStocks } from '@services/stocks-service'; import { NotFoundView } from '@views/errors/NotFoundView'; import { Material } from '@models/types/data/stock'; import { BPNS } from '@models/types/edc/bpn'; -import { useSubscription } from 'react-stomp-hooks'; +import { IMessage, useSubscription } from 'react-stomp-hooks'; import { refreshPartnerData } from '@services/refresh-service'; type SummaryContainerProps = { @@ -58,6 +58,45 @@ function SummaryContainer({ children }: SummaryContainerProps) { ); } +function makeErrorDownload(payloadText: string, materialNo: string) { + let contents: string; + try { + contents = JSON.stringify(JSON.parse(payloadText), null, 2); + } catch { + contents = JSON.stringify( + { material: materialNo, errors: [payloadText] }, + null, + 2 + ); + } + + const blob = new Blob([contents], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const ts = new Date().toISOString().replace(/[:.]/g, '-'); + const filename = `material-${materialNo}-refresh-errors-${ts}.json`; + + return { url, filename }; +} + +function parseRefreshWsMessage(msg?: string): { ok: boolean; errors: string[] } { + const text = (msg ?? '').trim(); + if (!text || text === 'SUCCESS') return { ok: true, errors: [] }; + + try { + const arr = JSON.parse(text) as unknown; + if (Array.isArray(arr)) { + const errors = (arr as { errors?: string[] }[]) + .flatMap(e => Array.isArray(e?.errors) ? e.errors! : []) + .filter(s => typeof s === 'string' && s.length > 0); + return { ok: errors.length === 0, errors: errors.length ? errors : [text] }; + } + return { ok: false, errors: [text] }; + } catch { + return { ok: false, errors: [text] }; + } +} + type MaterialDetailsProps = { material: Material; direction: DirectionType; @@ -131,25 +170,44 @@ export function MaterialDetails({ material, direction }: MaterialDetailsProps) { }; }, {}), [createSummaryByPartnerAndDirection, direction, expandablePartners]); - const handleRefresh = async () => { - refresh(['partner-data']) - .then(() => { + const handleRefreshMessage = async (message?: string) => { + const raw = (message ?? '').trim(); + const { ok, errors } = parseRefreshWsMessage(raw); + try { + await refresh(['partner-data']); + + if (ok) { notify({ title: 'Partner data updated', - description: `The partner data for ${material.name} was updated as requested`, - severity: 'success' - }) - }) - .catch(() => { + description: `The partner data for ${material.name} was updated as requested.`, + severity: 'success', + }); + } else { + const { url, filename } = makeErrorDownload(raw, material.ownMaterialNumber || ''); notify({ - title: 'Partner data refresh error', - description: `There was an error refreshing the requested partner data. Please try manually reloading the page`, - severity: 'error' - }) - }); - setIsRefreshing(false); + title: 'Partner data updated with errors', + description: ( + + Found {errors.length} issue{errors.length === 1 ? '' : 's'}.{' '} + { + setTimeout(() => URL.revokeObjectURL(url), 3000); + }} + > + Download error details + + + ) as unknown as string, + severity: 'error', + }); + } + } finally { + setIsRefreshing(false); + } }; - useSubscription('/topic/material/' + material.ownMaterialNumber, handleRefresh); + useSubscription('/topic/material/' + material.ownMaterialNumber, (msg: IMessage) => handleRefreshMessage(msg?.body)); useEffect(() => { const callback = (category: DataCategory) => refresh([category]); From 4d17d937ca65a0502c483c63d044091b6dc233a2 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 2 Oct 2025 10:38:51 +0200 Subject: [PATCH 05/10] chore: updated changelog --- CHANGELOG.md | 2 +- .../backend/demand/logic/services/DemandRequestApiService.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e2869b1..aa97daf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ The **need for configuration updates** is **marked bold**. ### Added -- / +- Added improved error messages for refresh materials ([#995](https://github.com/eclipse-tractusx/puris/pull/995)) ### Changed diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java index 9a881eba..e548cff2 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java @@ -113,8 +113,6 @@ public RefreshResult doReportedDemandRequest(Partner partner, Material material) var data = edcAdapterService.doSubmodelRequest(AssetType.DEMAND_SUBMODEL, mpr, DirectionCharacteristic.INBOUND, 1); var samm = objectMapper.treeToValue(data, ShortTermMaterialDemand.class); var demands = sammMapper.sammToReportedDemand(samm, partner); - - demands.get(demands.size() - 1).setQuantity(-20.0); for (var demand : demands) { var demandPartner = demand.getPartner(); From a52916e316a91364563ec88cb738206267a9f8ca Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 9 Oct 2025 15:01:59 +0200 Subject: [PATCH 06/10] change: apply pr request changes --- .../service/DeliveryRequestApiService.java | 7 +- .../logic/service/DeliveryService.java | 222 ++++++++++++++++++ .../logic/service/OwnDeliveryService.java | 168 +------------ .../service/ReportedDeliveryService.java | 135 +---------- .../services/DemandRequestApiService.java | 12 +- .../demand/logic/services/DemandService.java | 69 ++++++ .../logic/services/OwnDemandService.java | 51 +--- .../logic/services/ReportedDemandService.java | 50 +--- .../masterdata/domain/model/RefreshError.java | 1 - .../domain/model/RefreshResult.java | 1 - .../logic/service/MaterialRefreshService.java | 8 +- .../logic/service/OwnProductionService.java | 47 +--- .../service/ProductionRequestApiService.java | 7 +- .../logic/service/ProductionService.java | 55 +++++ .../service/ReportedProductionService.java | 41 +--- .../service/ItemStockRequestApiService.java | 9 +- .../ReportedMaterialItemStockService.java | 1 - .../ReportedProductItemStockService.java | 1 - .../logic/service/CustomerSupplyService.java | 34 +-- .../DaysOfSupplyRequestApiService.java | 7 +- .../logic/service/SupplierSupplyService.java | 35 +-- .../supply/logic/service/SupplyService.java | 31 +++ .../components/MaterialDetails.tsx | 22 +- 23 files changed, 472 insertions(+), 542 deletions(-) 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 index 1aa888d6..a91e848f 100644 --- 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 @@ -164,7 +164,12 @@ public RefreshResult doReportedDeliveryRequest(Partner partner, Material materia var deliveryPartner = delivery.getPartner(); var deliveryMaterial = delivery.getMaterial(); if (!partner.equals(deliveryPartner) || !material.equals(deliveryMaterial)) { - errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl()))); + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch (expected bpnl=%s, ownMaterialNumber=%s; received bpnl=%s, ownMaterialNumber=%s)".formatted( + partner.getBpnl(), + material.getOwnMaterialNumber(), + deliveryPartner.getBpnl(), + deliveryMaterial.getOwnMaterialNumber() + )))); continue; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java index 2d601c2f..1296a4cc 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java @@ -32,6 +32,7 @@ import java.util.stream.Stream; import org.eclipse.tractusx.puris.backend.delivery.domain.model.Delivery; +import org.eclipse.tractusx.puris.backend.delivery.domain.model.EventTypeEnumeration; import org.eclipse.tractusx.puris.backend.delivery.domain.repository.DeliveryRepository; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; @@ -130,4 +131,225 @@ public final T update(T delivery) { public final void delete(UUID id) { repository.deleteById(id); } + + protected List basicValidation(Delivery delivery) { + List errors = new ArrayList<>(); + + if (delivery.getQuantity() < 0) { + errors.add("Quantity must be greater than or equal to 0."); + } + if (delivery.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (delivery.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (delivery.getMaterial() == null) { + errors.add("Missing material."); + } + if (delivery.getPartner() == null) { + errors.add("Missing partner."); + } + // errors.addAll(validateResponsibility(delivery)); + errors.addAll(validateTransitEvent(delivery)); + if (!((delivery.getCustomerOrderNumber() != null && delivery.getCustomerOrderPositionNumber() != null) || + (delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && delivery.getSupplierOrderNumber() == null))) { + errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + } + + return errors; + } + + protected List validateOwnPartner(Delivery delivery) { + List errors = new ArrayList<>(); + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } + if (delivery.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same as own partner entity."); + } + return errors; + } + + protected List validateTransitEvent(Delivery delivery) { + List errors = new ArrayList<>(); + var now = new Date().getTime(); + + if (delivery.getDepartureType() == null) { + errors.add("Missing departure type."); + } else if (!(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE || delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE)) { + errors.add("Invalid departure type."); + } + if (delivery.getArrivalType() == null) { + errors.add("Missing arrival type."); + } else if (!(delivery.getArrivalType() == EventTypeEnumeration.ESTIMATED_ARRIVAL || delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL)) { + errors.add("Invalid arrival type."); + } + if (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) { + errors.add("Estimated departure cannot have actual arrival."); + } + if (delivery.getDateOfDeparture() == null) { + errors.add("Missing date of departure."); + } + if (delivery.getDateOfArrival() == null) { + errors.add("Missing date of arrival."); + } + if (delivery.getDateOfArrival() != null && delivery.getDateOfDeparture() != null && + delivery.getDateOfDeparture().getTime() >= delivery.getDateOfArrival().getTime()) { + errors.add("Date of departure must be before date of arrival."); + } + if (delivery.getDateOfArrival() != null && + delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL && delivery.getDateOfArrival().getTime() >= now) { + errors.add("Actual arrival date must be in the past."); + } + if (delivery.getDateOfDeparture() != null && + delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE && delivery.getDateOfDeparture().getTime() >= now) { + errors.add("Actual departure date must be in the past."); + } + + return errors; + } + + protected List validateOwnResponsibility(Delivery delivery) { + List errors = new ArrayList<>(); + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } + var ownSites = ownPartnerEntity.getSites(); + var partnerSites = delivery.getPartner().getSites(); + + if (delivery.getIncoterm() == null) { + errors.add("Missing Incoterm."); + } else { + switch (delivery.getIncoterm().getResponsibility()) { + case SUPPLIER: + var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + if (!delivery.getMaterial().isProductFlag()) { + errors.add("Material must have product flag for supplier responsibility."); + } + if (!ownSite.isPresent()) { + errors.add("Origin BPNS must match one of the own partner entity's site BPNS for supplier responsibility."); + } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { + errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); + } + if (!partnerSite.isPresent()) { + errors.add("Destination BPNS must match one of the partner's site BPNS for supplier responsibility."); + } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { + errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); + } + break; + case CUSTOMER: + ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + if (!delivery.getMaterial().isMaterialFlag()) { + errors.add("Material must have material flag for customer responsibility."); + } + if (!ownSite.isPresent()) { + errors.add("Destination BPNS must match one of the own partner entity's site BPNS for customer responsibility."); + } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { + errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); + } + if (!partnerSite.isPresent()) { + errors.add("Origin BPNS must match one of the partner's site BPNS for customer responsibility."); + } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { + errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); + } + break; + case PARTIAL: + if (delivery.getMaterial().isProductFlag()) { + ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + if (ownSite.isPresent() && partnerSite.isPresent() && ( + delivery.getOriginBpna() == null || + ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) + ) && ( + delivery.getDestinationBpna() == null || + partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) + )) { + return new ArrayList<>(); + } + } + if (delivery.getMaterial().isMaterialFlag()) { + ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + if (ownSite.isPresent() && partnerSite.isPresent() && ( + delivery.getDestinationBpna() == null || + ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) + ) && ( + delivery.getOriginBpna() == null || + partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) + )) { + return new ArrayList<>(); + } + } + errors.add("Responsibility conditions for partial responsibility are not met."); + break; + default: + errors.add("Invalid incoterm responsibility."); + break; + } + } + return errors; + } + + protected List validateReportedResponsibility(Delivery delivery) { + List errors = new ArrayList<>(); + if (ownPartnerEntity == null) { + ownPartnerEntity = partnerService.getOwnPartnerEntity(); + } + + if (delivery.getIncoterm() == null) { + errors.add("Missing Incoterm."); + } else { + switch (delivery.getIncoterm().getResponsibility()) { + case SUPPLIER: + if (!delivery.getMaterial().isMaterialFlag()) { + errors.add("Material must have material flag for supplier responsibility."); + } + if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + errors.add("Origin BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { + errors.add("Destination BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + } + + break; + case CUSTOMER: + if (!delivery.getMaterial().isProductFlag()) { + errors.add("Material must have product flag for customer responsibility."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + errors.add("Site BPNS must match one of the own partner entity's site BPNS for customer responsibility."); + } + if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { + errors.add("Site BPNA must match one of the partner entity's site' address BPNAs for customer responsibility."); + } + + break; + case PARTIAL: + if (delivery.getMaterial().isProductFlag()) { + if (delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) + ) { + return new ArrayList<>(); + } + } + if (delivery.getMaterial().isMaterialFlag()) { + if (ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && + delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { + return new ArrayList<>(); + } + } + errors.add("Responsibility conditions for partial responsibility are not met."); + break; + default: + errors.add("Invalid incoterm responsibility."); + break; + } + } + return errors; + } } 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 e4a45ccd..565f448e 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 @@ -21,16 +21,13 @@ package org.eclipse.tractusx.puris.backend.delivery.logic.service; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.function.Function; import javax.management.openmbean.KeyAlreadyExistsException; -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.repository.OwnDeliveryRepository; -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; @@ -42,8 +39,6 @@ public class OwnDeliveryService extends DeliveryService { protected final Function validator; - private Partner ownPartnerEntity; - public OwnDeliveryService(OwnDeliveryRepository repository, PartnerService partnerService) { this.repository = repository; this.partnerService = partnerService; @@ -82,164 +77,15 @@ public final List createAll(List deliveries) { } public boolean validate(OwnDelivery delivery) { - return validateWithDetails(delivery).isEmpty(); + return basicValidation(delivery).isEmpty() && validateOwnPartner(delivery).isEmpty() + && validateOwnResponsibility(delivery).isEmpty(); } public List validateWithDetails(OwnDelivery delivery) { - List errors = new ArrayList<>(); - if (ownPartnerEntity == null) { - ownPartnerEntity = partnerService.getOwnPartnerEntity(); - } - - if (delivery.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); - } - if (delivery.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (delivery.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (delivery.getMaterial() == null) { - errors.add("Missing material."); - } - if (delivery.getPartner() == null) { - errors.add("Missing partner."); - } - errors.addAll(validateResponsibility(delivery)); - errors.addAll(validateTransitEvent(delivery)); - if (delivery.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); - } - if (!((delivery.getCustomerOrderNumber() != null && delivery.getCustomerOrderPositionNumber() != null) || - (delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && delivery.getSupplierOrderNumber() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); - } - - return errors; - } - - private List validateTransitEvent(OwnDelivery delivery) { - List errors = new ArrayList<>(); - var now = new Date().getTime(); - - if (delivery.getDepartureType() == null) { - errors.add("Missing departure type."); - } else if (!(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE || delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE)) { - errors.add("Invalid departure type."); - } - if (delivery.getArrivalType() == null) { - errors.add("Missing arrival type."); - } else if (!(delivery.getArrivalType() == EventTypeEnumeration.ESTIMATED_ARRIVAL || delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL)) { - errors.add("Invalid arrival type."); - } - if (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) { - errors.add("Estimated departure cannot have actual arrival."); - } - if (delivery.getDateOfDeparture() == null) { - errors.add("Missing date of departure."); - } - if (delivery.getDateOfArrival() == null) { - errors.add("Missing date of arrival."); - } - if (delivery.getDateOfArrival() != null && delivery.getDateOfDeparture() != null && - delivery.getDateOfDeparture().getTime() >= delivery.getDateOfArrival().getTime()) { - errors.add("Date of departure must be before date of arrival."); - } - if (delivery.getDateOfArrival() != null && - delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL && delivery.getDateOfArrival().getTime() >= now) { - errors.add("Actual arrival date must be in the past."); - } - if (delivery.getDateOfDeparture() != null && - delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE && delivery.getDateOfDeparture().getTime() >= now) { - errors.add("Actual departure date must be in the past."); - } - - return errors; - } - - private List validateResponsibility(OwnDelivery delivery) { - List errors = new ArrayList<>(); - if (ownPartnerEntity == null) { - ownPartnerEntity = partnerService.getOwnPartnerEntity(); - } - var ownSites = ownPartnerEntity.getSites(); - var partnerSites = delivery.getPartner().getSites(); - - if (delivery.getIncoterm() == null) { - errors.add("Missing Incoterm."); - } else { - switch (delivery.getIncoterm().getResponsibility()) { - case SUPPLIER: - var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - if (!delivery.getMaterial().isProductFlag()) { - errors.add("Material must have product flag for supplier responsibility."); - } - if (!ownSite.isPresent()) { - errors.add("Origin BPNS must match one of the own partner entity's site BPNS for supplier responsibility."); - } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); - } - if (!partnerSite.isPresent()) { - errors.add("Destination BPNS must match one of the partner's site BPNS for supplier responsibility."); - } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); - } - break; - case CUSTOMER: - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - if (!delivery.getMaterial().isMaterialFlag()) { - errors.add("Material must have material flag for customer responsibility."); - } - if (!ownSite.isPresent()) { - errors.add("Destination BPNS must match one of the own partner entity's site BPNS for customer responsibility."); - } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); - } - if (!partnerSite.isPresent()) { - errors.add("Origin BPNS must match one of the partner's site BPNS for customer responsibility."); - } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); - } - break; - case PARTIAL: - if (delivery.getMaterial().isProductFlag()) { - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - if (ownSite.isPresent() && partnerSite.isPresent() && ( - delivery.getOriginBpna() == null || - ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) - ) && ( - delivery.getDestinationBpna() == null || - partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) - )) { - return new ArrayList<>(); - } - } - if (delivery.getMaterial().isMaterialFlag()) { - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - if (ownSite.isPresent() && partnerSite.isPresent() && ( - delivery.getDestinationBpna() == null || - ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) - ) && ( - delivery.getOriginBpna() == null || - partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) - )) { - return new ArrayList<>(); - } - } - errors.add("Responsibility conditions for partial responsibility are not met."); - break; - default: - errors.add("Invalid incoterm responsibility."); - break; - } - } - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(delivery)); + validationErrors.addAll(validateOwnPartner(delivery)); + validationErrors.addAll(validateOwnResponsibility(delivery)); + return validationErrors; } } 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 c32f0f07..c5d7676d 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 @@ -22,15 +22,12 @@ package org.eclipse.tractusx.puris.backend.delivery.logic.service; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.UUID; import java.util.function.Function; -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; @@ -42,8 +39,6 @@ public class ReportedDeliveryService extends DeliveryService { protected final Function validator; - private Partner ownPartnerEntity; - public ReportedDeliveryService(ReportedDeliveryRepository repository, PartnerService partnerService) { this.repository = repository; this.partnerService = partnerService; @@ -77,133 +72,13 @@ public final List createAll(List deliveries) } public boolean validate(ReportedDelivery delivery) { - return validateWithDetails(delivery).isEmpty(); + return basicValidation(delivery).isEmpty() && validateReportedResponsibility(delivery).isEmpty(); } public List validateWithDetails(ReportedDelivery delivery) { - List errors = new ArrayList<>(); - - if (delivery.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); - } - if (delivery.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (delivery.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (delivery.getMaterial() == null) { - errors.add("Missing material."); - } - if (delivery.getPartner() == null) { - errors.add("Missing partner."); - } - errors.addAll(validateResponsibility(delivery)); - errors.addAll(validateTransitEvent(delivery)); - if (!((delivery.getCustomerOrderNumber() != null && delivery.getCustomerOrderPositionNumber() != null) || - (delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && delivery.getSupplierOrderNumber() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); - } - - return errors; - } - - private List validateTransitEvent(ReportedDelivery delivery) { - List errors = new ArrayList<>(); - var now = new Date().getTime(); - - if (delivery.getDepartureType() == null) { - errors.add("Missing departure type."); - } else if (!(delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE || delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE)) { - errors.add("Invalid departure type."); - } - if (delivery.getArrivalType() == null) { - errors.add("Missing arrival type."); - } else if (!(delivery.getArrivalType() == EventTypeEnumeration.ESTIMATED_ARRIVAL || delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL)) { - errors.add("Invalid arrival type."); - } - if (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) { - errors.add("Estimated departure cannot have actual arrival."); - } - if (delivery.getDateOfDeparture() == null) { - errors.add("Missing date of departure."); - } - if (delivery.getDateOfArrival() == null) { - errors.add("Missing date of arrival."); - } - if (delivery.getDateOfArrival() != null && delivery.getDateOfDeparture() != null && - delivery.getDateOfDeparture().getTime() >= delivery.getDateOfArrival().getTime()) { - errors.add("Date of departure must be before date of arrival."); - } - if (delivery.getDateOfArrival() != null && - delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL && delivery.getDateOfArrival().getTime() >= now) { - errors.add("Actual arrival date must be in the past."); - } - if (delivery.getDateOfDeparture() != null && - delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE && delivery.getDateOfDeparture().getTime() >= now) { - errors.add("Actual departure date must be in the past."); - } - - return errors; - } - - private List validateResponsibility(ReportedDelivery delivery) { - List errors = new ArrayList<>(); - if (ownPartnerEntity == null) { - ownPartnerEntity = partnerService.getOwnPartnerEntity(); - } - - if (delivery.getIncoterm() == null) { - errors.add("Missing Incoterm."); - } else { - switch (delivery.getIncoterm().getResponsibility()) { - case SUPPLIER: - if (!delivery.getMaterial().isProductFlag()) { - errors.add("Material must have product flag for supplier responsibility."); - } - if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add("Origin BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add("Destination BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); - } - - break; - case CUSTOMER: - if (!delivery.getMaterial().isProductFlag()) { - errors.add("Material must have product flag for customer responsibility."); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add("Site BPNS must match one of the own partner entity's site BPNS for customer responsibility."); - } - if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add("Site BPNA must match one of the partner entity's site' address BPNAs for customer responsibility."); - } - - break; - case PARTIAL: - if (delivery.getMaterial().isProductFlag()) { - if (delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) - ) { - return new ArrayList<>(); - } - } - if (delivery.getMaterial().isMaterialFlag()) { - if (ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - return new ArrayList<>(); - } - } - errors.add("Responsibility conditions for partial responsibility are not met."); - break; - default: - errors.add("Invalid incoterm responsibility."); - break; - } - } - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(delivery)); + validationErrors.addAll(validateReportedResponsibility(delivery)); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java index e548cff2..62983584 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandRequestApiService.java @@ -22,13 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; - -import org.eclipse.tractusx.puris.backend.common.domain.model.measurement.ItemUnitEnumeration; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.common.edc.logic.service.EdcAdapterService; -import org.eclipse.tractusx.puris.backend.demand.domain.model.DemandCategoryEnumeration; -import org.eclipse.tractusx.puris.backend.demand.domain.model.OwnDemand; -import org.eclipse.tractusx.puris.backend.demand.domain.model.ReportedDemand; import org.eclipse.tractusx.puris.backend.demand.logic.adapter.ShortTermMaterialDemandSammMapper; import org.eclipse.tractusx.puris.backend.demand.logic.dto.demandsamm.ShortTermMaterialDemand; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; @@ -118,7 +113,12 @@ public RefreshResult doReportedDemandRequest(Partner partner, Material material) var demandPartner = demand.getPartner(); var demandMaterial = demand.getMaterial(); if (!partner.equals(demandPartner) || !material.equals(demandMaterial)) { - errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch"))); + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch (expected bpnl=%s, ownMaterialNumber=%s; received bpnl=%s, ownMaterialNumber=%s)".formatted( + partner.getBpnl(), + material.getOwnMaterialNumber(), + demandPartner.getBpnl(), + demandMaterial.getOwnMaterialNumber() + )))); continue; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java index 3472bd10..83b0bb51 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java @@ -19,6 +19,8 @@ See the NOTICE file(s) distributed with this work for additional */ package org.eclipse.tractusx.puris.backend.demand.logic.services; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -27,6 +29,7 @@ See the NOTICE file(s) distributed with this work for additional import javax.management.openmbean.KeyAlreadyExistsException; import org.eclipse.tractusx.puris.backend.demand.domain.model.Demand; +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.PartnerService; import org.springframework.data.jpa.repository.JpaRepository; @@ -80,6 +83,72 @@ public final List findAllByFilters( return stream.toList(); } + protected List basicValidation(Demand demand) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + + if (demand.getMaterial() == null) { + errors.add("Missing Material."); + } + if (demand.getPartner() == null) { + errors.add("Missing Partner."); + } + if (demand.getQuantity() < 0) { + errors.add("Quantity must be greater than or equal to 0."); + } + if (demand.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (demand.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (demand.getDay() == null) { + errors.add("Missing day."); + } + if (demand.getDemandCategoryCode() == null) { + errors.add("Missing demand category code."); + } + if (demand.getDemandLocationBpns() == null) { + errors.add("Missing demand location BPNS."); + } + if (demand.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same as own partner entity."); + } + return errors; + } + + protected List validateReportedDemand(Demand demand) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + if (!mprService.partnerOrdersProduct(demand.getMaterial(), demand.getPartner())) { + errors.add("Cannot order specified material from Partner."); + } + if ((demand.getSupplierLocationBpns() != null && + ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) + || demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { + errors.add("Invalid demand: supplier or demand location is not valid."); + } + return errors; + } + + protected List validateOwnDemand(Demand demand) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + if (!mprService.partnerSuppliesMaterial(demand.getMaterial(), demand.getPartner())) { + errors.add("Partner does not supply the specified material."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { + errors.add("Demand location BPNS must match one of the own partner entity's site BPNS."); + } + if (demand.getSupplierLocationBpns() != null && + demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) { + errors.add("Supplier location BPNS must match one of the partner's site BPNS."); + } + return errors; + } + public final TEntity create(TEntity demand) { if (!validator.apply(demand)) { throw new IllegalArgumentException("Invalid demand"); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java index 7b0ec191..32efe4dc 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java @@ -28,10 +28,8 @@ See the NOTICE file(s) distributed with this work for additional import java.util.Date; import java.util.List; import java.util.Optional; - import org.eclipse.tractusx.puris.backend.demand.domain.model.OwnDemand; import org.eclipse.tractusx.puris.backend.demand.domain.repository.OwnDemandRepository; -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.PartnerService; import org.springframework.stereotype.Service; @@ -66,53 +64,14 @@ public final List getQuantityForDays(String material, Optional p @Override public boolean validate(OwnDemand demand) { - return validateWithDetails(demand).isEmpty(); + return true; } public List validateWithDetails(OwnDemand demand) { - List errors = new ArrayList<>(); - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - - if (demand.getMaterial() == null) { - errors.add("Missing Material."); - } - if (demand.getPartner() == null) { - errors.add("Missing Partner."); - } - if (!mprService.partnerSuppliesMaterial(demand.getMaterial(), demand.getPartner())) { - errors.add("Partner does not supply the specified material."); - } - if (demand.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); - } - if (demand.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (demand.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (demand.getDay() == null) { - errors.add("Missing day."); - } - if (demand.getDemandCategoryCode() == null) { - errors.add("Missing demand category code."); - } - if (demand.getDemandLocationBpns() == null) { - errors.add("Missing demand location BPNS."); - } - if (demand.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { - errors.add("Demand location BPNS must match one of the own partner entity's site BPNS."); - } - if (demand.getSupplierLocationBpns() != null && - demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) { - errors.add("Supplier location BPNS must match one of the partner's site BPNS."); - } - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(demand)); + validationErrors.addAll(validateOwnDemand(demand)); + return validationErrors; } private final double getSumOfQuantities(List demands) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java index 98152a01..7a494f32 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java @@ -21,12 +21,9 @@ See the NOTICE file(s) distributed with this work for additional package org.eclipse.tractusx.puris.backend.demand.logic.services; import java.util.ArrayList; -import java.util.Date; import java.util.List; - import org.eclipse.tractusx.puris.backend.demand.domain.model.ReportedDemand; import org.eclipse.tractusx.puris.backend.demand.domain.repository.ReportedDemandRepository; -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.PartnerService; import org.springframework.stereotype.Service; @@ -40,50 +37,13 @@ public ReportedDemandService(ReportedDemandRepository repository, PartnerService @Override public boolean validate(ReportedDemand demand) { - return validateWithDetails(demand).isEmpty(); + return basicValidation(demand).isEmpty() && validateReportedDemand(demand).isEmpty(); } public List validateWithDetails(ReportedDemand demand) { - List errors = new ArrayList<>(); - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - - if (demand.getMaterial() == null) { - errors.add("Missing Material."); - } - if (demand.getPartner() == null) { - errors.add("Missing Partner."); - } - if (!mprService.partnerOrdersProduct(demand.getMaterial(), demand.getPartner())) { - errors.add("Cannot order specified material from Partner."); - } - if (demand.getQuantity() <= 0) { - errors.add("Quantity must be greater than 0."); - } - if (demand.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (demand.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (demand.getDay() == null) { - errors.add("Missing day."); - } - if (demand.getDemandCategoryCode() == null) { - errors.add("Missing demand category code."); - } - if (demand.getDemandLocationBpns() == null) { - errors.add("Missing demand location BPNS."); - } - if (demand.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same entity."); - } - if ((demand.getSupplierLocationBpns() != null && - ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) - || demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { - errors.add("Invalid demand: supplier or demand location is not valid."); - } - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(demand)); + validationErrors.addAll(validateReportedDemand(demand)); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java index a06c90a1..0d0a6197 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshError.java @@ -20,7 +20,6 @@ package org.eclipse.tractusx.puris.backend.masterdata.domain.model; import java.util.List; - import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java index fe01ee59..e1bd1b56 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/domain/model/RefreshResult.java @@ -20,7 +20,6 @@ package org.eclipse.tractusx.puris.backend.masterdata.domain.model; import java.util.List; - import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java index d5287b7e..c4c37038 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/masterdata/logic/service/MaterialRefreshService.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -130,8 +131,11 @@ public void refreshPartnerData(String ownMaterialNumber) { .thenApply(v -> futures.stream().map(CompletableFuture::join).toList()) .thenAccept(results -> { var allErrors = results.stream() - .filter(Objects::nonNull) - .flatMap(r -> r.getErrors().stream()) + .filter(r -> r.getErrors() != null && !r.getErrors().isEmpty()) + .map(r -> Map.of( + "message", r.getMessage(), + "errors", r.getErrors() + )) .toList(); var topic = "/topic/material/" + material.getOwnMaterialNumber(); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java index ba70ad2b..da70408f 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java @@ -22,13 +22,10 @@ See the NOTICE file(s) distributed with this work for additional package org.eclipse.tractusx.puris.backend.production.logic.service; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.function.Function; import javax.management.openmbean.KeyAlreadyExistsException; - -import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.eclipse.tractusx.puris.backend.production.domain.model.OwnProduction; import org.eclipse.tractusx.puris.backend.production.domain.repository.OwnProductionRepository; @@ -71,47 +68,13 @@ public final List createAll(List productions) { } public boolean validate(OwnProduction production) { - return validateWithDetails(production).isEmpty(); + return basicValidation(production).isEmpty() && validateOwnProduction(production, partnerService.getOwnPartnerEntity()).isEmpty(); } public List validateWithDetails(OwnProduction production) { - List errors = new ArrayList<>(); - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - - if (production.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); - } - if (production.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (production.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (production.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (production.getEstimatedTimeOfCompletion() == null) { - errors.add("Missing estimated time of completion."); - } - if (production.getMaterial() == null) { - errors.add("Missing material."); - } - if (production.getPartner() == null) { - errors.add("Missing partner."); - } - if (production.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); - } - if (production.getProductionSiteBpns() == null) { - errors.add("Missing production site BPNS."); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { - errors.add("Production site BPNS must match one of the own partner entity's site BPNS."); - } - if (!((production.getCustomerOrderNumber() != null && production.getCustomerOrderPositionNumber() != null) || - (production.getCustomerOrderNumber() == null && production.getCustomerOrderPositionNumber() == null && production.getSupplierOrderNumber() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); - } - - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(production)); + validationErrors.addAll(validateOwnProduction(production, partnerService.getOwnPartnerEntity())); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java index e3bf7732..f0bc9687 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionRequestApiService.java @@ -93,7 +93,12 @@ public RefreshResult doReportedProductionRequest(Partner partner, Material mater var productionPartner = production.getPartner(); var productionMaterial = production.getMaterial(); if (!partner.equals(productionPartner) || !material.equals(productionMaterial)) { - errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch"))); + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch (expected bpnl=%s, ownMaterialNumber=%s; received bpnl=%s, ownMaterialNumber=%s)".formatted( + partner.getBpnl(), + material.getOwnMaterialNumber(), + productionPartner.getBpnl(), + productionMaterial.getOwnMaterialNumber() + )))); continue; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java index 790f510b..aee4339a 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java @@ -19,6 +19,7 @@ */ package org.eclipse.tractusx.puris.backend.production.logic.service; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.eclipse.tractusx.puris.backend.production.domain.model.Production; import org.eclipse.tractusx.puris.backend.production.domain.repository.ProductionRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -96,6 +97,60 @@ public final List getQuantityForDays(String material, Optional p return quantities; } + protected List basicValidation(Production production) { + List errors = new ArrayList<>(); + + if (production.getQuantity() < 0) { + errors.add("Quantity must be greater than or equal to 0."); + } + if (production.getMeasurementUnit() == null) { + errors.add("Missing measurement unit."); + } + if (production.getLastUpdatedOnDateTime() == null) { + errors.add("Missing lastUpdatedOnTime."); + } else if (production.getLastUpdatedOnDateTime().after(new Date())) { + errors.add("lastUpdatedOnDateTime cannot be in the future."); + } + if (production.getEstimatedTimeOfCompletion() == null) { + errors.add("Missing estimated time of completion."); + } + if (production.getMaterial() == null) { + errors.add("Missing material."); + } + if (production.getPartner() == null) { + errors.add("Missing partner."); + } + if (production.getProductionSiteBpns() == null) { + errors.add("Missing production site BPNS."); + } + if (!((production.getCustomerOrderNumber() != null && production.getCustomerOrderPositionNumber() != null) || + (production.getCustomerOrderNumber() == null && production.getCustomerOrderPositionNumber() == null && production.getSupplierOrderNumber() == null))) { + errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + } + + return errors; + } + + protected List validateOwnProduction(Production production, Partner ownPartnerEntity) { + List errors = new ArrayList<>(); + + if (production.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same as own partner entity."); + } + if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { + errors.add("Production site BPNS must match one of the own partner entity's site BPNS."); + } + return errors; + } + + protected List validateReportedProduction(Production production) { + List errors = new ArrayList<>(); + if (production.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { + errors.add("Production site BPNS must match."); + } + return errors; + } + public final T update(T production) { if (production.getUuid() == null || repository.findById(production.getUuid()).isEmpty()) { return null; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java index 8f0317b7..412118d8 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java @@ -22,7 +22,6 @@ See the NOTICE file(s) distributed with this work for additional package org.eclipse.tractusx.puris.backend.production.logic.service; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.function.Function; @@ -63,43 +62,13 @@ public final List createAll(List product } public boolean validate(ReportedProduction production) { - return validateWithDetails(production).isEmpty(); + return basicValidation(production).isEmpty() && validateReportedProduction(production).isEmpty(); } public List validateWithDetails(ReportedProduction production) { - List errors = new ArrayList<>(); - - if (production.getQuantity() <= 0) { - errors.add("Quantity must be greater than 0."); - } - if (production.getMeasurementUnit() == null) { - errors.add("Missing measurement unit."); - } - if (production.getLastUpdatedOnDateTime() == null) { - errors.add("Missing lastUpdatedOnTime."); - } else if (production.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); - } - if (production.getEstimatedTimeOfCompletion() == null) { - errors.add("Missing estimated time of completion."); - } - if (production.getMaterial() == null) { - errors.add("Missing material."); - } - if (production.getPartner() == null) { - errors.add("Missing partner."); - } - if (production.getProductionSiteBpns() == null) { - errors.add("Missing production site BPNS."); - } - if (production.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { - errors.add("Production site BPNS must match."); - } - if (!((production.getCustomerOrderNumber() != null && production.getCustomerOrderPositionNumber() != null) || - (production.getCustomerOrderNumber() == null && production.getCustomerOrderPositionNumber() == null && production.getSupplierOrderNumber() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); - } - - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(production)); + validationErrors.addAll(validateReportedProduction(production)); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java index b15343d9..113f42f1 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockRequestApiService.java @@ -36,8 +36,6 @@ 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.domain.model.ReportedMaterialItemStock; -import org.eclipse.tractusx.puris.backend.stock.domain.model.ReportedProductItemStock; import org.eclipse.tractusx.puris.backend.stock.logic.adapter.ItemStockSammMapper; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.ItemStockSamm; @@ -144,7 +142,12 @@ public RefreshResult doItemStockSubmodelReportedMaterialItemStockRequest(Partner var stockPartner = stock.getPartner(); var stockMaterial = stock.getMaterial(); if (!partner.equals(stockPartner) || !material.equals(stockMaterial)) { - errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl() + "\n" + stocks))); + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch (expected bpnl=%s, ownMaterialNumber=%s; received bpnl=%s, ownMaterialNumber=%s)".formatted( + partner.getBpnl(), + material.getOwnMaterialNumber(), + stockPartner.getBpnl(), + stockMaterial.getOwnMaterialNumber() + )))); continue; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java index 56181203..a516bc48 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedMaterialItemStockService.java @@ -21,7 +21,6 @@ package org.eclipse.tractusx.puris.backend.stock.logic.service; import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java index c35284e0..eacea63d 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ReportedProductItemStockService.java @@ -21,7 +21,6 @@ package org.eclipse.tractusx.puris.backend.stock.logic.service; import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java index 238079f6..93bf2282 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java @@ -30,7 +30,6 @@ 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.demand.logic.services.OwnDemandService; -import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; 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.domain.model.MaterialItemStock; @@ -112,37 +111,12 @@ public final List findAllByFilters(Optional ownM } public boolean validate(ReportedCustomerSupply daysOfSupply) { - return validateWithDetails(daysOfSupply).isEmpty(); + return basicValidation(daysOfSupply).isEmpty(); } public List validateWithDetails(ReportedCustomerSupply daysOfSupply) { - List errors = new ArrayList<>(); - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - - if (daysOfSupply.getMaterial() == null) { - errors.add("Missing Material."); - } - if (daysOfSupply.getPartner() == null) { - errors.add("Missing Partner."); - } - if (daysOfSupply.getDate() == null) { - errors.add("Missing date."); - } - if (daysOfSupply.getStockLocationBPNS() == null) { - errors.add("Missing stock location BPNS."); - } - if (daysOfSupply.getStockLocationBPNA() == null) { - errors.add("Missing stock location BPNA."); - } - if (daysOfSupply.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same entity."); - } - if (daysOfSupply.getPartner().getSites().stream().noneMatch(site -> - site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) || - site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) - )) { - errors.add("Invalid days of supply: stock location is not valid."); - } - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(daysOfSupply)); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java index 7b35f2b0..f5bfa0ae 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java @@ -128,7 +128,12 @@ public RefreshResult doReportedDaysOfSupplyRequest(Partner partner, Material mat var supplyPartner = reportedCustomerSupply.getPartner(); var supplyMaterial = reportedCustomerSupply.getMaterial(); if (!partner.equals(supplyPartner) || !material.equals(supplyMaterial)) { - errors.add(new RefreshError(List.of("Received inconsistent data from " + partner.getBpnl()))); + errors.add(new RefreshError(List.of("Received inconsistent data: partner or material mismatch (expected bpnl=%s, ownMaterialNumber=%s; received bpnl=%s, ownMaterialNumber=%s)".formatted( + partner.getBpnl(), + material.getOwnMaterialNumber(), + supplyPartner.getBpnl(), + supplyMaterial.getOwnMaterialNumber() + )))); continue; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java index 7e9e3de0..bf98cd97 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java @@ -29,7 +29,6 @@ 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.Partner; 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.production.logic.service.OwnProductionService; @@ -112,38 +111,12 @@ public final List findAllByFilters(Optional ownM } public boolean validate(ReportedSupplierSupply daysOfSupply) { - return validateWithDetails(daysOfSupply).isEmpty(); + return basicValidation(daysOfSupply).isEmpty(); } public List validateWithDetails(ReportedSupplierSupply daysOfSupply) { - List errors = new ArrayList<>(); - Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); - - if (daysOfSupply.getMaterial() == null) { - errors.add("Missing Material."); - } - if (daysOfSupply.getPartner() == null) { - errors.add("Missing Partner."); - } - if (daysOfSupply.getDate() == null) { - errors.add("Missing date."); - } - if (daysOfSupply.getStockLocationBPNS() == null) { - errors.add("Missing stock location BPNS."); - } - if (daysOfSupply.getStockLocationBPNA() == null) { - errors.add("Missing stock location BPNA."); - } - if (daysOfSupply.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same entity."); - } - if (daysOfSupply.getPartner().getSites().stream().noneMatch(site -> - site.getBpns().equals(daysOfSupply.getStockLocationBPNS()) || - site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(daysOfSupply.getStockLocationBPNA())) - )) { - errors.add("Invalid days of supply: stock location is not valid."); - } - - return errors; + List validationErrors = new ArrayList<>(); + validationErrors.addAll(basicValidation(daysOfSupply)); + return validationErrors; } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java index 8def3379..974311ae 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java @@ -175,4 +175,35 @@ private double getDaysOfSupply(double stockQuantity, List consumedValues } return daysOfSupply; } + + protected List basicValidation(Supply supply) { + List errors = new ArrayList<>(); + Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); + + if (supply.getMaterial() == null) { + errors.add("Missing Material."); + } + if (supply.getPartner() == null) { + errors.add("Missing Partner."); + } + if (supply.getDate() == null) { + errors.add("Missing date."); + } + if (supply.getStockLocationBPNS() == null) { + errors.add("Missing stock location BPNS."); + } + if (supply.getStockLocationBPNA() == null) { + errors.add("Missing stock location BPNA."); + } + if (supply.getPartner().equals(ownPartnerEntity)) { + errors.add("Partner cannot be the same as own partner entity."); + } + if (supply.getPartner().getSites().stream().noneMatch(site -> + site.getBpns().equals(supply.getStockLocationBPNS()) || + site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(supply.getStockLocationBPNA())) + )) { + errors.add(String.format("Invalid days of supply: stock location '%s' and or stock address '%s' don't belong to each other or partner '%s'.", supply.getStockLocationBPNS(), supply.getStockLocationBPNA(), supply.getPartner().getBpnl())); + } + return errors; + } } diff --git a/frontend/src/features/material-details/components/MaterialDetails.tsx b/frontend/src/features/material-details/components/MaterialDetails.tsx index 1fd6f122..38db9299 100644 --- a/frontend/src/features/material-details/components/MaterialDetails.tsx +++ b/frontend/src/features/material-details/components/MaterialDetails.tsx @@ -86,9 +86,25 @@ function parseRefreshWsMessage(msg?: string): { ok: boolean; errors: string[] } try { const arr = JSON.parse(text) as unknown; if (Array.isArray(arr)) { - const errors = (arr as { errors?: string[] }[]) - .flatMap(e => Array.isArray(e?.errors) ? e.errors! : []) - .filter(s => typeof s === 'string' && s.length > 0); + const a = arr as any[]; + + const hasMessage = a.some(e => e && typeof e === 'object' && 'message' in e); + const errors = hasMessage + ? a.flatMap(entry => { + const msg = typeof entry?.message === 'string' ? entry.message.trim() : ''; + const prefix = msg ? `${msg}: ` : ''; + const errs = Array.isArray(entry?.errors) ? entry.errors : []; + return errs.flatMap((e: any) => { + if (Array.isArray(e?.errors)) { + return e.errors.filter((s: any) => typeof s === 'string' && s.length > 0) + .map((s: string) => prefix + s); + } + return typeof e === 'string' && e.length > 0 ? [prefix + e] : []; + }); + }) + : (a as { errors?: string[] }[]) + .flatMap(e => Array.isArray(e?.errors) ? e.errors! : []) + .filter(s => typeof s === 'string' && s.length > 0); return { ok: errors.length === 0, errors: errors.length ? errors : [text] }; } return { ok: false, errors: [text] }; From aaff18c002625bf6ce22f1dd43264e4dadb62e3e Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Thu, 9 Oct 2025 15:07:56 +0200 Subject: [PATCH 07/10] chore: update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4b22ea..699682cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ The **need for configuration updates** is **marked bold**. ### Added -- / +- Added improved error messages for refresh materials ([#995](https://github.com/eclipse-tractusx/puris/pull/995)) ### Changed @@ -76,7 +76,7 @@ The **need for configuration updates** is **marked bold**. ### Added -- Added improved error messages for refresh materials ([#995](https://github.com/eclipse-tractusx/puris/pull/995)) +- / ### Changed From 7085d4fc4f4e63aa7238a1a47beb61ad4f42d345 Mon Sep 17 00:00:00 2001 From: "olga.ivkovic" Date: Mon, 13 Oct 2025 18:15:21 +0200 Subject: [PATCH 08/10] change: applied PR changes --- .../logic/service/DeliveryService.java | 53 +++++++++---------- .../logic/service/OwnDeliveryService.java | 3 +- .../service/ReportedDeliveryService.java | 2 +- .../demand/logic/services/DemandService.java | 16 +++--- .../logic/services/OwnDemandService.java | 2 +- .../logic/services/ReportedDemandService.java | 2 +- .../logic/service/OwnProductionService.java | 2 +- .../logic/service/ProductionService.java | 12 ++--- .../service/ReportedProductionService.java | 2 +- .../stock/logic/service/ItemStockService.java | 17 +++--- .../service/MaterialItemStockService.java | 3 +- .../logic/service/CustomerSupplyService.java | 2 +- .../logic/service/SupplierSupplyService.java | 2 +- .../supply/logic/service/SupplyService.java | 4 +- 14 files changed, 61 insertions(+), 61 deletions(-) diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java index 1296a4cc..0eca7797 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java @@ -136,7 +136,7 @@ protected List basicValidation(Delivery delivery) { List errors = new ArrayList<>(); if (delivery.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); + errors.add(String.format("Quantity '%d'must be greater than or equal to 0.", delivery.getQuantity())); } if (delivery.getMeasurementUnit() == null) { errors.add("Missing measurement unit."); @@ -144,7 +144,7 @@ protected List basicValidation(Delivery delivery) { if (delivery.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", delivery.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (delivery.getMaterial() == null) { errors.add("Missing material."); @@ -152,7 +152,6 @@ protected List basicValidation(Delivery delivery) { if (delivery.getPartner() == null) { errors.add("Missing partner."); } - // errors.addAll(validateResponsibility(delivery)); errors.addAll(validateTransitEvent(delivery)); if (!((delivery.getCustomerOrderNumber() != null && delivery.getCustomerOrderPositionNumber() != null) || (delivery.getCustomerOrderNumber() == null && delivery.getCustomerOrderPositionNumber() == null && delivery.getSupplierOrderNumber() == null))) { @@ -168,7 +167,7 @@ protected List validateOwnPartner(Delivery delivery) { ownPartnerEntity = partnerService.getOwnPartnerEntity(); } if (delivery.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); + errors.add(String.format("Partner cannot be the same as own partner entity '%s'.", delivery.getPartner().getBpnl())); } return errors; } @@ -188,7 +187,7 @@ protected List validateTransitEvent(Delivery delivery) { errors.add("Invalid arrival type."); } if (delivery.getDepartureType() == EventTypeEnumeration.ESTIMATED_DEPARTURE && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL) { - errors.add("Estimated departure cannot have actual arrival."); + errors.add("Delivery with estimated departure cannot have actual arrival."); } if (delivery.getDateOfDeparture() == null) { errors.add("Missing date of departure."); @@ -198,15 +197,15 @@ protected List validateTransitEvent(Delivery delivery) { } if (delivery.getDateOfArrival() != null && delivery.getDateOfDeparture() != null && delivery.getDateOfDeparture().getTime() >= delivery.getDateOfArrival().getTime()) { - errors.add("Date of departure must be before date of arrival."); + errors.add(String.format("Date of departure '%s' must be before date of arrival '%s'.", delivery.getDateOfDeparture().toInstant().toString(), delivery.getDateOfArrival().toInstant().toString())); } if (delivery.getDateOfArrival() != null && delivery.getArrivalType() == EventTypeEnumeration.ACTUAL_ARRIVAL && delivery.getDateOfArrival().getTime() >= now) { - errors.add("Actual arrival date must be in the past."); + errors.add(String.format("Actual arrival date '%s' must be in the past (system time: '%s').", delivery.getDateOfArrival().toInstant().toString(), (new Date()).toInstant().toString())); } if (delivery.getDateOfDeparture() != null && delivery.getDepartureType() == EventTypeEnumeration.ACTUAL_DEPARTURE && delivery.getDateOfDeparture().getTime() >= now) { - errors.add("Actual departure date must be in the past."); + errors.add(String.format("Actual departure date '%s' must be in the past (system time: '%s').", delivery.getDateOfDeparture().toInstant().toString(), (new Date()).toInstant().toString())); } return errors; @@ -228,34 +227,34 @@ protected List validateOwnResponsibility(Delivery delivery) { var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); if (!delivery.getMaterial().isProductFlag()) { - errors.add("Material must have product flag for supplier responsibility."); + errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } if (!ownSite.isPresent()) { - errors.add("Origin BPNS must match one of the own partner entity's site BPNS for supplier responsibility."); + errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with supplier responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); + errors.add(String.format("Origin BPNA '%s' not configured for own site '%s' (delivery with supplier responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns())); } if (!partnerSite.isPresent()) { - errors.add("Destination BPNS must match one of the partner's site BPNS for supplier responsibility."); + errors.add(String.format("Destination BPNS '%s' must match one of the own partner entity's site BPNS (supplier responsibility).", delivery.getDestinationBpns())); } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for supplier responsibility."); + errors.add(String.format("Destination BPNA '%s' not configured for own site '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); } break; case CUSTOMER: ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); if (!delivery.getMaterial().isMaterialFlag()) { - errors.add("Material must have material flag for customer responsibility."); + errors.add(String.format("Material '%s' must have customer flag (incoterm '%s' for customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } if (!ownSite.isPresent()) { - errors.add("Destination BPNS must match one of the own partner entity's site BPNS for customer responsibility."); + errors.add(String.format("Destination BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add("Destination BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); + errors.add(String.format("Destination BPNA '%s' not configured for own site '%s' (incoterm '%s' with customer responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); } if (!partnerSite.isPresent()) { - errors.add("Origin BPNS must match one of the partner's site BPNS for customer responsibility."); + errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add("Origin BPNA must match one of the own partner entity's site' address BPNAs for customer responsibility."); + errors.add(String.format("Origin BPNA '%s' not configured for own site '%s' (incoterm '%s' with customer responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getIncoterm().getValue())); } break; case PARTIAL: @@ -285,10 +284,10 @@ protected List validateOwnResponsibility(Delivery delivery) { return new ArrayList<>(); } } - errors.add("Responsibility conditions for partial responsibility are not met."); + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); break; default: - errors.add("Invalid incoterm responsibility."); + errors.add(String.format("Invalid incoterm responsibility for incoterm '%s'.", delivery.getIncoterm().getValue())); break; } } @@ -307,25 +306,25 @@ protected List validateReportedResponsibility(Delivery delivery) { switch (delivery.getIncoterm().getResponsibility()) { case SUPPLIER: if (!delivery.getMaterial().isMaterialFlag()) { - errors.add("Material must have material flag for supplier responsibility."); + errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add("Origin BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + errors.add(String.format("Origin BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); } if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add("Destination BPNA must match one of the partner entity's site' address BPNAs for supplier responsibility."); + errors.add(String.format("Destination BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); } break; case CUSTOMER: if (!delivery.getMaterial().isProductFlag()) { - errors.add("Material must have product flag for customer responsibility."); + errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add("Site BPNS must match one of the own partner entity's site BPNS for customer responsibility."); + errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); } if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add("Site BPNA must match one of the partner entity's site' address BPNAs for customer responsibility."); + errors.add(String.format("Destination BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); } break; @@ -343,7 +342,7 @@ protected List validateReportedResponsibility(Delivery delivery) { return new ArrayList<>(); } } - errors.add("Responsibility conditions for partial responsibility are not met."); + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); break; default: errors.add("Invalid incoterm responsibility."); 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 565f448e..dfa2503d 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 @@ -77,8 +77,7 @@ public final List createAll(List deliveries) { } public boolean validate(OwnDelivery delivery) { - return basicValidation(delivery).isEmpty() && validateOwnPartner(delivery).isEmpty() - && validateOwnResponsibility(delivery).isEmpty(); + return validateWithDetails(delivery).isEmpty(); } public List validateWithDetails(OwnDelivery delivery) { 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 c5d7676d..8891a1d4 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 @@ -72,7 +72,7 @@ public final List createAll(List deliveries) } public boolean validate(ReportedDelivery delivery) { - return basicValidation(delivery).isEmpty() && validateReportedResponsibility(delivery).isEmpty(); + return validateWithDetails(delivery).isEmpty(); } public List validateWithDetails(ReportedDelivery delivery) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java index 83b0bb51..1d1779b4 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java @@ -94,7 +94,7 @@ protected List basicValidation(Demand demand) { errors.add("Missing Partner."); } if (demand.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); + errors.add(String.format("Quantity '%s' must be greater than or equal to 0.", demand.getQuantity())); } if (demand.getMeasurementUnit() == null) { errors.add("Missing measurement unit."); @@ -102,7 +102,7 @@ protected List basicValidation(Demand demand) { if (demand.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", demand.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (demand.getDay() == null) { errors.add("Missing day."); @@ -114,7 +114,7 @@ protected List basicValidation(Demand demand) { errors.add("Missing demand location BPNS."); } if (demand.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); + errors.add(String.format("Partner cannot be the same as own partner entity '%s'.", demand.getPartner().getBpnl())); } return errors; } @@ -123,12 +123,12 @@ protected List validateReportedDemand(Demand demand) { List errors = new ArrayList<>(); Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); if (!mprService.partnerOrdersProduct(demand.getMaterial(), demand.getPartner())) { - errors.add("Cannot order specified material from Partner."); + errors.add(String.format("Partner '%s' is not configured to buy your material '%s'.", demand.getPartner().getBpnl(), demand.getMaterial().getOwnMaterialNumber())); } if ((demand.getSupplierLocationBpns() != null && ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) || demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { - errors.add("Invalid demand: supplier or demand location is not valid."); + errors.add("Supplier or demand location is not valid."); } return errors; } @@ -137,14 +137,14 @@ protected List validateOwnDemand(Demand demand) { List errors = new ArrayList<>(); Partner ownPartnerEntity = partnerService.getOwnPartnerEntity(); if (!mprService.partnerSuppliesMaterial(demand.getMaterial(), demand.getPartner())) { - errors.add("Partner does not supply the specified material."); + errors.add(String.format("Partner '%s' is not configured to supply you the specified material '%s'.", demand.getPartner().getBpnl(), demand.getMaterial().getOwnMaterialNumber())); } if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { - errors.add("Demand location BPNS must match one of the own partner entity's site BPNS."); + errors.add(String.format("Demand location BPNS '%s' must match to one site configured for your own partner '%s' .", demand.getDemandLocationBpns(), ownPartnerEntity.getBpnl())); } if (demand.getSupplierLocationBpns() != null && demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) { - errors.add("Supplier location BPNS must match one of the partner's site BPNS."); + errors.add(String.format("Expected supplier location BPNS '%s' must match to one site of the partner '%s' .", demand.getSupplierLocationBpns(), demand.getPartner().getBpnl())); } return errors; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java index 32efe4dc..eefd769d 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/OwnDemandService.java @@ -64,7 +64,7 @@ public final List getQuantityForDays(String material, Optional p @Override public boolean validate(OwnDemand demand) { - return true; + return validateWithDetails(demand).isEmpty(); } public List validateWithDetails(OwnDemand demand) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java index 7a494f32..2e0b7f59 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/ReportedDemandService.java @@ -37,7 +37,7 @@ public ReportedDemandService(ReportedDemandRepository repository, PartnerService @Override public boolean validate(ReportedDemand demand) { - return basicValidation(demand).isEmpty() && validateReportedDemand(demand).isEmpty(); + return validateWithDetails(demand).isEmpty(); } public List validateWithDetails(ReportedDemand demand) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java index da70408f..f47470e0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/OwnProductionService.java @@ -68,7 +68,7 @@ public final List createAll(List productions) { } public boolean validate(OwnProduction production) { - return basicValidation(production).isEmpty() && validateOwnProduction(production, partnerService.getOwnPartnerEntity()).isEmpty(); + return validateWithDetails(production).isEmpty(); } public List validateWithDetails(OwnProduction production) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java index aee4339a..9c389105 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java @@ -101,7 +101,7 @@ protected List basicValidation(Production production) { List errors = new ArrayList<>(); if (production.getQuantity() < 0) { - errors.add("Quantity must be greater than or equal to 0."); + errors.add(String.format("Quantity '%s' must be greater than or equal to 0.", production.getQuantity())); } if (production.getMeasurementUnit() == null) { errors.add("Missing measurement unit."); @@ -109,7 +109,7 @@ protected List basicValidation(Production production) { if (production.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (production.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", production.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (production.getEstimatedTimeOfCompletion() == null) { errors.add("Missing estimated time of completion."); @@ -125,7 +125,7 @@ protected List basicValidation(Production production) { } if (!((production.getCustomerOrderNumber() != null && production.getCustomerOrderPositionNumber() != null) || (production.getCustomerOrderNumber() == null && production.getCustomerOrderPositionNumber() == null && production.getSupplierOrderNumber() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + errors.add(String.format("If an order position reference is given, customer order number '%s' and customer order position number '%s' must be set. Supplier order number '%' then can be set, too", production.getCustomerOrderNumber(), production.getCustomerOrderPositionNumber(), production.getSupplierOrderNumber())); } return errors; @@ -135,10 +135,10 @@ protected List validateOwnProduction(Production production, Partner ownP List errors = new ArrayList<>(); if (production.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); + errors.add(String.format("Partner cannot be the same as own partner entity '%s'.", production.getPartner().getBpnl())); } if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { - errors.add("Production site BPNS must match one of the own partner entity's site BPNS."); + errors.add(String.format("Production site BPNS '%s' must match to one site of the partner '%s' .", production.getProductionSiteBpns(), production.getPartner().getBpnl())); } return errors; } @@ -146,7 +146,7 @@ protected List validateOwnProduction(Production production, Partner ownP protected List validateReportedProduction(Production production) { List errors = new ArrayList<>(); if (production.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(production.getProductionSiteBpns()))) { - errors.add("Production site BPNS must match."); + errors.add(String.format("Production site BPNS '%s' must match to one site of the partner '%s' .", production.getProductionSiteBpns(), production.getPartner().getBpnl())); } return errors; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java index 412118d8..b7081315 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ReportedProductionService.java @@ -62,7 +62,7 @@ public final List createAll(List product } public boolean validate(ReportedProduction production) { - return basicValidation(production).isEmpty() && validateReportedProduction(production).isEmpty(); + return validateWithDetails(production).isEmpty(); } public List validateWithDetails(ReportedProduction production) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java index 323090cd..9c654c30 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java @@ -156,7 +156,7 @@ protected List basicValidation(ItemStock itemStock) { errors.add("Missing locationBpns."); } if (itemStock.getQuantity() < 0){ - errors.add("Quantity must be greater than or equal to 0."); + errors.add(String.format("Quantity '%s' must be greater than or equal to 0.", itemStock.getQuantity())); } if (itemStock.getMeasurementUnit() == null) { errors.add("Missing measurementUnit."); @@ -164,11 +164,14 @@ protected List basicValidation(ItemStock itemStock) { if (itemStock.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (itemStock.getLastUpdatedOnDateTime().after(new Date())) { - errors.add("lastUpdatedOnDateTime cannot be in the future."); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", itemStock.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (!((itemStock.getCustomerOrderId() != null && itemStock.getCustomerOrderPositionId() != null) || (itemStock.getCustomerOrderId() == null && itemStock.getCustomerOrderPositionId() == null && itemStock.getSupplierOrderId() == null))) { - errors.add("If an order position reference is given, customer order number and customer order position number must be set."); + errors.add(String.format( + "Invalid order reference configuration for item stock: customerOrderId='%s', customerOrderPositionId='%s', supplierOrderId='%s'. If a customer order reference is provided, both customerOrderId and customerOrderPositionId must be set, if none are provided then they must be null. ", + itemStock.getCustomerOrderId(), itemStock.getCustomerOrderPositionId(), itemStock.getSupplierOrderId() + )); } } catch (Exception e) { log.error("Basic Validation failed: " + itemStock + "\n" + e.getMessage()); @@ -195,10 +198,10 @@ protected final List validateMaterialItemStock(ItemStock itemStock) { errors.add("Missing MaterialPartnerRelation."); } if (!material.isMaterialFlag()) { - errors.add("Material flag is missing."); + errors.add(String.format("Material flag is missing for Material '%s'.", material.getOwnMaterialNumber())); } if (relation != null && !relation.isPartnerSuppliesMaterial()) { - errors.add("Partner does not supply material."); + errors.add(String.format("Partner '%s' does not supply material '%s'. ", partner.getBpnl(), material.getOwnMaterialNumber())); } } catch (Exception e) { log.error("MaterialItemStock Validation failed: " + itemStock + "\n" + e.getMessage()); @@ -217,10 +220,10 @@ protected final List validateProductItemStock(ItemStock itemStock) { errors.add("Missing MaterialPartnerRelation."); } if (!material.isProductFlag()) { - errors.add("Product flag is missing."); + errors.add(String.format("Product flag is missing for Material '%s'.", material.getOwnMaterialNumber())); } if (relation != null && !relation.isPartnerBuysMaterial()) { - errors.add("Partner does not buy material."); + errors.add(String.format("Partner '%s' does not supply material '%s'. ", partner.getBpnl(), material.getOwnMaterialNumber())); } } catch (Exception e) { log.error("ProductItemStock Validation failed: " + itemStock + "\n" + e.getMessage()); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/MaterialItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/MaterialItemStockService.java index e215fa6e..4af331d1 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/MaterialItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/MaterialItemStockService.java @@ -45,8 +45,7 @@ public MaterialItemStockService(PartnerService partnerService, MaterialPartnerRe @Override public boolean validate(MaterialItemStock materialItemStock) { - return basicValidation(materialItemStock).isEmpty() && validateLocalStock(materialItemStock).isEmpty() - && validateMaterialItemStock(materialItemStock).isEmpty(); + return validateWithDetails(materialItemStock).isEmpty(); } public List validateWithDetails(MaterialItemStock materialItemStock) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java index 93bf2282..603e0ea0 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/CustomerSupplyService.java @@ -111,7 +111,7 @@ public final List findAllByFilters(Optional ownM } public boolean validate(ReportedCustomerSupply daysOfSupply) { - return basicValidation(daysOfSupply).isEmpty(); + return validateWithDetails(daysOfSupply).isEmpty(); } public List validateWithDetails(ReportedCustomerSupply daysOfSupply) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java index bf98cd97..23583712 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplierSupplyService.java @@ -111,7 +111,7 @@ public final List findAllByFilters(Optional ownM } public boolean validate(ReportedSupplierSupply daysOfSupply) { - return basicValidation(daysOfSupply).isEmpty(); + return validateWithDetails(daysOfSupply).isEmpty(); } public List validateWithDetails(ReportedSupplierSupply daysOfSupply) { diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java index 974311ae..073b886b 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/SupplyService.java @@ -196,13 +196,13 @@ protected List basicValidation(Supply supply) { errors.add("Missing stock location BPNA."); } if (supply.getPartner().equals(ownPartnerEntity)) { - errors.add("Partner cannot be the same as own partner entity."); + errors.add(String.format("Partner cannot be the same as own partner entity '%s'.", supply.getPartner().getBpnl())); } if (supply.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(supply.getStockLocationBPNS()) || site.getAddresses().stream().noneMatch(address -> address.getBpna().equals(supply.getStockLocationBPNA())) )) { - errors.add(String.format("Invalid days of supply: stock location '%s' and or stock address '%s' don't belong to each other or partner '%s'.", supply.getStockLocationBPNS(), supply.getStockLocationBPNA(), supply.getPartner().getBpnl())); + errors.add(String.format("Stock location '%s' and or stock address '%s' don't belong to each other or partner '%s'.", supply.getStockLocationBPNS(), supply.getStockLocationBPNA(), supply.getPartner().getBpnl())); } return errors; } From 4a3c9ad67c583b5d4b7f1645fb833b40982801e2 Mon Sep 17 00:00:00 2001 From: Olga Ivkovic Date: Fri, 17 Oct 2025 09:50:16 +0200 Subject: [PATCH 09/10] fix: apply pr changes and refactor Delivery Location Validations --- .../logic/service/DeliveryService.java | 162 ++++++++++-------- .../demand/logic/services/DemandService.java | 4 +- .../logic/service/ProductionService.java | 2 +- .../stock/logic/service/ItemStockService.java | 2 +- 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java index 0eca7797..e9b7ccf1 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java @@ -25,9 +25,11 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.SortedSet; import java.util.UUID; import java.util.stream.Stream; @@ -35,6 +37,7 @@ import org.eclipse.tractusx.puris.backend.delivery.domain.model.EventTypeEnumeration; import org.eclipse.tractusx.puris.backend.delivery.domain.repository.DeliveryRepository; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Site; 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; @@ -136,7 +139,7 @@ protected List basicValidation(Delivery delivery) { List errors = new ArrayList<>(); if (delivery.getQuantity() < 0) { - errors.add(String.format("Quantity '%d'must be greater than or equal to 0.", delivery.getQuantity())); + errors.add(String.format("Quantity '%d' must be greater than or equal to 0.", delivery.getQuantity())); } if (delivery.getMeasurementUnit() == null) { errors.add("Missing measurement unit."); @@ -144,7 +147,7 @@ protected List basicValidation(Delivery delivery) { if (delivery.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (delivery.getLastUpdatedOnDateTime().after(new Date())) { - errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", delivery.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past (system time: '%s').", delivery.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (delivery.getMaterial() == null) { errors.add("Missing material."); @@ -224,67 +227,43 @@ protected List validateOwnResponsibility(Delivery delivery) { } else { switch (delivery.getIncoterm().getResponsibility()) { case SUPPLIER: - var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); if (!delivery.getMaterial().isProductFlag()) { errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - if (!ownSite.isPresent()) { - errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with supplier responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); - } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add(String.format("Origin BPNA '%s' not configured for own site '%s' (delivery with supplier responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns())); - } - if (!partnerSite.isPresent()) { - errors.add(String.format("Destination BPNS '%s' must match one of the own partner entity's site BPNS (supplier responsibility).", delivery.getDestinationBpns())); - } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add(String.format("Destination BPNA '%s' not configured for own site '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); - } + errors.addAll(validateLocationsAsSupplier(delivery, "supplier", ownSites, partnerSites)); break; case CUSTOMER: - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); if (!delivery.getMaterial().isMaterialFlag()) { - errors.add(String.format("Material '%s' must have customer flag (incoterm '%s' for customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); - } - if (!ownSite.isPresent()) { - errors.add(String.format("Destination BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); - } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add(String.format("Destination BPNA '%s' not configured for own site '%s' (incoterm '%s' with customer responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue())); - } - if (!partnerSite.isPresent()) { - errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); - } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add(String.format("Origin BPNA '%s' not configured for own site '%s' (incoterm '%s' with customer responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getIncoterm().getValue())); + errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } + errors.addAll(validateLocationsAsCustomer(delivery, "customer", ownSites, partnerSites)); break; case PARTIAL: + boolean valid = false; + List supplierPathErrors = Collections.emptyList(); + List customerPathErrors = Collections.emptyList(); + if (delivery.getMaterial().isProductFlag()) { - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - if (ownSite.isPresent() && partnerSite.isPresent() && ( - delivery.getOriginBpna() == null || - ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) - ) && ( - delivery.getDestinationBpna() == null || - partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) - )) { - return new ArrayList<>(); + supplierPathErrors = validateLocationsAsSupplier(delivery, "supplier", ownSites, partnerSites); + if (supplierPathErrors.isEmpty()) { + valid = true; } } if (delivery.getMaterial().isMaterialFlag()) { - ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); - partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); - if (ownSite.isPresent() && partnerSite.isPresent() && ( - delivery.getDestinationBpna() == null || - ownSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getDestinationBpna())) - ) && ( - delivery.getOriginBpna() == null || - partnerSite.get().getAddresses().stream().anyMatch(address -> address.getBpna().equals(delivery.getOriginBpna())) - )) { - return new ArrayList<>(); + customerPathErrors = validateLocationsAsCustomer(delivery, "customer", ownSites, partnerSites); + if (customerPathErrors.isEmpty()) { + valid = true; + } + } + if (!valid) { + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); + if (delivery.getMaterial().isProductFlag() && !supplierPathErrors.isEmpty()) { + errors.addAll(supplierPathErrors); + } + if (delivery.getMaterial().isMaterialFlag() && !customerPathErrors.isEmpty()) { + errors.addAll(customerPathErrors); } } - errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); break; default: errors.add(String.format("Invalid incoterm responsibility for incoterm '%s'.", delivery.getIncoterm().getValue())); @@ -299,6 +278,8 @@ protected List validateReportedResponsibility(Delivery delivery) { if (ownPartnerEntity == null) { ownPartnerEntity = partnerService.getOwnPartnerEntity(); } + var ownSites = ownPartnerEntity.getSites(); + var partnerSites = delivery.getPartner().getSites(); if (delivery.getIncoterm() == null) { errors.add("Missing Incoterm."); @@ -308,41 +289,39 @@ protected List validateReportedResponsibility(Delivery delivery) { if (!delivery.getMaterial().isMaterialFlag()) { errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add(String.format("Origin BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add(String.format("Destination BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); - } - + errors.addAll(validateLocationsAsCustomer(delivery, "supplier", ownSites, partnerSites)); break; case CUSTOMER: if (!delivery.getMaterial().isProductFlag()) { - errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); - } - if (ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - errors.add(String.format("Origin BPNS '%s' must match one of the own partner entity's site BPNS (incoterm '%s' with customer responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue())); - } - if (delivery.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(delivery.getDestinationBpns()))) { - errors.add(String.format("Destination BPNA '%s' not configured for site '%s' of partner '%s' (incoterm '%s' with supplier responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue())); + errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - + errors.addAll(validateLocationsAsSupplier(delivery, "customer", ownSites, partnerSites)); break; case PARTIAL: + boolean valid = false; + List supplierPathErrors = Collections.emptyList(); + List customerPathErrors = Collections.emptyList(); if (delivery.getMaterial().isProductFlag()) { - if (delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns())) - ) { - return new ArrayList<>(); + customerPathErrors = validateLocationsAsSupplier(delivery, "customer", ownSites, partnerSites); + if (customerPathErrors.isEmpty()) { + valid = true; } } if (delivery.getMaterial().isMaterialFlag()) { - if (ownPartnerEntity.getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getDestinationBpns())) && - delivery.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(delivery.getOriginBpns()))) { - return new ArrayList<>(); + supplierPathErrors = validateLocationsAsCustomer(delivery, "supplier", ownSites, partnerSites); + if (supplierPathErrors.isEmpty()) { + valid = true; + } + } + if (!valid) { + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); + if (delivery.getMaterial().isProductFlag() && !supplierPathErrors.isEmpty()) { + errors.addAll(supplierPathErrors); + } + if (delivery.getMaterial().isMaterialFlag() && !customerPathErrors.isEmpty()) { + errors.addAll(customerPathErrors); } } - errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); break; default: errors.add("Invalid incoterm responsibility."); @@ -351,4 +330,45 @@ protected List validateReportedResponsibility(Delivery delivery) { } return errors; } + + protected List validateLocationsAsSupplier(Delivery delivery, String responsibility, SortedSet ownSites, SortedSet partnerSites) { + List errors = new ArrayList<>(); + + var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + + if (!ownSite.isPresent()) { + errors.add(String.format("Origin site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue(), responsibility)); + } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { + errors.add(String.format("Origin address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getIncoterm().getValue(), responsibility)); + } + if (!partnerSite.isPresent()) { + errors.add(String.format("Destination site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { + errors.add(String.format("Destination address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + } + + return errors; + } + + protected List validateLocationsAsCustomer(Delivery delivery, String responsibility, SortedSet ownSites, SortedSet partnerSites) { + List errors = new ArrayList<>(); + + var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); + var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); + + if (!ownSite.isPresent()) { + errors.add(String.format("Destination site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), responsibility)); + } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { + errors.add(String.format("Destination address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), responsibility)); + } + if (!partnerSite.isPresent()) { + errors.add(String.format("Origin site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { + errors.add(String.format("Origin address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + } + + return errors; + } + } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java index 1d1779b4..b60d4684 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/demand/logic/services/DemandService.java @@ -102,7 +102,7 @@ protected List basicValidation(Demand demand) { if (demand.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (demand.getLastUpdatedOnDateTime().after(new Date())) { - errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", demand.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past (system time: '%s').", demand.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (demand.getDay() == null) { errors.add("Missing day."); @@ -128,7 +128,7 @@ protected List validateReportedDemand(Demand demand) { if ((demand.getSupplierLocationBpns() != null && ownPartnerEntity.getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getSupplierLocationBpns()))) || demand.getPartner().getSites().stream().noneMatch(site -> site.getBpns().equals(demand.getDemandLocationBpns()))) { - errors.add("Supplier or demand location is not valid."); + errors.add(String.format("Either supplier location '%s' is not configured as your site or demand location '%s' is not configured as site for customer partner '%s'.", demand.getSupplierLocationBpns(), demand.getDemandLocationBpns(), demand.getPartner().getBpnl())); } return errors; } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java index 9c389105..9a34465a 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/production/logic/service/ProductionService.java @@ -109,7 +109,7 @@ protected List basicValidation(Production production) { if (production.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (production.getLastUpdatedOnDateTime().after(new Date())) { - errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", production.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past (system time: '%s').", production.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (production.getEstimatedTimeOfCompletion() == null) { errors.add("Missing estimated time of completion."); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java index 9c654c30..ca4591a9 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/stock/logic/service/ItemStockService.java @@ -164,7 +164,7 @@ protected List basicValidation(ItemStock itemStock) { if (itemStock.getLastUpdatedOnDateTime() == null) { errors.add("Missing lastUpdatedOnTime."); } else if (itemStock.getLastUpdatedOnDateTime().after(new Date())) { - errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past must be in the past (system time: '%s').", itemStock.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); + errors.add(String.format("lastUpdatedOnDateTime '%s' must be in the past (system time: '%s').", itemStock.getLastUpdatedOnDateTime().toInstant().toString(), (new Date()).toInstant().toString())); } if (!((itemStock.getCustomerOrderId() != null && itemStock.getCustomerOrderPositionId() != null) || (itemStock.getCustomerOrderId() == null && itemStock.getCustomerOrderPositionId() == null && itemStock.getSupplierOrderId() == null))) { From 1e17874c5fe92139d288717db2488a79caaab547 Mon Sep 17 00:00:00 2001 From: Olga Ivkovic Date: Tue, 21 Oct 2025 12:50:15 +0200 Subject: [PATCH 10/10] fix: apply PR changes --- .../logic/service/DeliveryService.java | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java index e9b7ccf1..b60e47ea 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/delivery/logic/service/DeliveryService.java @@ -230,13 +230,13 @@ protected List validateOwnResponsibility(Delivery delivery) { if (!delivery.getMaterial().isProductFlag()) { errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - errors.addAll(validateLocationsAsSupplier(delivery, "supplier", ownSites, partnerSites)); + errors.addAll(validateLocationsAsSupplier(delivery, ownSites, partnerSites)); break; case CUSTOMER: if (!delivery.getMaterial().isMaterialFlag()) { - errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); + errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - errors.addAll(validateLocationsAsCustomer(delivery, "customer", ownSites, partnerSites)); + errors.addAll(validateLocationsAsCustomer(delivery, ownSites, partnerSites)); break; case PARTIAL: boolean valid = false; @@ -244,25 +244,21 @@ protected List validateOwnResponsibility(Delivery delivery) { List customerPathErrors = Collections.emptyList(); if (delivery.getMaterial().isProductFlag()) { - supplierPathErrors = validateLocationsAsSupplier(delivery, "supplier", ownSites, partnerSites); + supplierPathErrors = validateLocationsAsSupplier(delivery, ownSites, partnerSites); if (supplierPathErrors.isEmpty()) { valid = true; } } if (delivery.getMaterial().isMaterialFlag()) { - customerPathErrors = validateLocationsAsCustomer(delivery, "customer", ownSites, partnerSites); + customerPathErrors = validateLocationsAsCustomer(delivery, ownSites, partnerSites); if (customerPathErrors.isEmpty()) { valid = true; } } if (!valid) { - errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); - if (delivery.getMaterial().isProductFlag() && !supplierPathErrors.isEmpty()) { - errors.addAll(supplierPathErrors); - } - if (delivery.getMaterial().isMaterialFlag() && !customerPathErrors.isEmpty()) { - errors.addAll(customerPathErrors); - } + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site '%s' does not match to own configured sites or destination site '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); + errors.addAll(supplierPathErrors); + errors.addAll(customerPathErrors); } break; default: @@ -289,38 +285,34 @@ protected List validateReportedResponsibility(Delivery delivery) { if (!delivery.getMaterial().isMaterialFlag()) { errors.add(String.format("Material '%s' must be configured as material via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - errors.addAll(validateLocationsAsCustomer(delivery, "supplier", ownSites, partnerSites)); + errors.addAll(validateLocationsAsCustomer(delivery, ownSites, partnerSites)); break; case CUSTOMER: if (!delivery.getMaterial().isProductFlag()) { - errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with supplier responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); + errors.add(String.format("Material '%s' must be configured as product via flag (incoterm '%s' with customer responsibility).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue())); } - errors.addAll(validateLocationsAsSupplier(delivery, "customer", ownSites, partnerSites)); + errors.addAll(validateLocationsAsSupplier(delivery, ownSites, partnerSites)); break; case PARTIAL: boolean valid = false; List supplierPathErrors = Collections.emptyList(); List customerPathErrors = Collections.emptyList(); if (delivery.getMaterial().isProductFlag()) { - customerPathErrors = validateLocationsAsSupplier(delivery, "customer", ownSites, partnerSites); - if (customerPathErrors.isEmpty()) { + supplierPathErrors = validateLocationsAsSupplier(delivery, ownSites, partnerSites); + if (supplierPathErrors.isEmpty()) { valid = true; } } if (delivery.getMaterial().isMaterialFlag()) { - supplierPathErrors = validateLocationsAsCustomer(delivery, "supplier", ownSites, partnerSites); - if (supplierPathErrors.isEmpty()) { + customerPathErrors = validateLocationsAsCustomer(delivery, ownSites, partnerSites); + if (customerPathErrors.isEmpty()) { valid = true; } } if (!valid) { - errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site bpns '%s' does not match to own configured sites or destination site bpns '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); - if (delivery.getMaterial().isProductFlag() && !supplierPathErrors.isEmpty()) { - errors.addAll(supplierPathErrors); - } - if (delivery.getMaterial().isMaterialFlag() && !customerPathErrors.isEmpty()) { - errors.addAll(customerPathErrors); - } + errors.add(String.format("Responsibility conditions for material '%s' for partial responsibility (incoterm '%s') are not met. Either origin site '%s' does not match to own configured sites or destination site '%s' does not match to configured sites for partner '%s'. Additionally this behavior might not be applicable to the material configuration as product (%b) or material (%b).", delivery.getMaterial().getOwnMaterialNumber(), delivery.getIncoterm().getValue(), delivery.getOriginBpns(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getMaterial().isProductFlag(), delivery.getMaterial().isMaterialFlag())); + errors.addAll(supplierPathErrors); + errors.addAll(customerPathErrors); } break; default: @@ -331,41 +323,57 @@ protected List validateReportedResponsibility(Delivery delivery) { return errors; } - protected List validateLocationsAsSupplier(Delivery delivery, String responsibility, SortedSet ownSites, SortedSet partnerSites) { + /** + * Validates the location consistency for the supplier path of a Delivery. + * + * @param delivery The Delivery being validated + * @param ownSites The set of sites configured for the own partner entity + * @param partnerSites The set of sites configured for delivery partner entity + * @return A list of validation error messages, an empty list means valid + */ + protected List validateLocationsAsSupplier(Delivery delivery, SortedSet ownSites, SortedSet partnerSites) { List errors = new ArrayList<>(); var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); if (!ownSite.isPresent()) { - errors.add(String.format("Origin site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Origin site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } else if (delivery.getOriginBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add(String.format("Origin address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Origin address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } if (!partnerSite.isPresent()) { - errors.add(String.format("Destination site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Destination site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } else if (delivery.getDestinationBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add(String.format("Destination address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Destination address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } return errors; } - protected List validateLocationsAsCustomer(Delivery delivery, String responsibility, SortedSet ownSites, SortedSet partnerSites) { + /** + * Validates the location consistency for the customer path of a Delivery. + * + * @param delivery The Delivery being validated + * @param ownSites The set of sites configured for the own partner entity + * @param partnerSites The set of sites configured for delivery partner entity + * @return A list of validation error messages, an empty list means valid + */ + protected List validateLocationsAsCustomer(Delivery delivery, SortedSet ownSites, SortedSet partnerSites) { List errors = new ArrayList<>(); var ownSite = ownSites.stream().filter(site -> site.getBpns().equals(delivery.getDestinationBpns())).findFirst(); var partnerSite = partnerSites.stream().filter(site -> site.getBpns().equals(delivery.getOriginBpns())).findFirst(); if (!ownSite.isPresent()) { - errors.add(String.format("Destination site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Destination site '%s' must match one of the own partner entity's sites (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } else if (delivery.getDestinationBpna() != null && ownSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getDestinationBpna()))) { - errors.add(String.format("Destination address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Destination address '%s' is not configured for own site '%s' (incoterm '%s' with '%s' responsibility).", delivery.getDestinationBpna(), delivery.getDestinationBpns(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } if (!partnerSite.isPresent()) { - errors.add(String.format("Origin site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Origin site '%s' must match one site of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } else if (delivery.getOriginBpna() != null && partnerSite.get().getAddresses().stream().noneMatch(address -> address.getBpna().equals(delivery.getOriginBpna()))) { - errors.add(String.format("Origin address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), responsibility)); + errors.add(String.format("Origin address '%s' is not configured for site '%s' of partner '%s' (incoterm '%s' with '%s' responsibility).", delivery.getOriginBpna(), delivery.getOriginBpns(), delivery.getPartner().getBpnl(), delivery.getIncoterm().getValue(), delivery.getIncoterm().getResponsibility())); } return errors;