diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java index 33d2fd82..a411ccd4 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/ddtr/logic/util/DtrRequestBodyBuilder.java @@ -98,7 +98,7 @@ public JsonNode createMaterialRegistrationRequestBody(MaterialPartnerRelation ma submodelDescriptorsArray.add(createSubmodelObject(AssetType.ITEM_STOCK_SUBMODEL.URN_SEMANTIC_ID, href + DirectionCharacteristic.INBOUND + "/", variablesService.getItemStockSubmodelApiAssetId())); submodelDescriptorsArray.add(createSubmodelObject(AssetType.DEMAND_SUBMODEL.URN_SEMANTIC_ID, href, variablesService.getDemandSubmodelApiAssetId())); submodelDescriptorsArray.add(createSubmodelObject(AssetType.DELIVERY_SUBMODEL.URN_SEMANTIC_ID, href, variablesService.getDeliverySubmodelApiAssetId())); - + submodelDescriptorsArray.add(createSubmodelObject(AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, href + DirectionCharacteristic.INBOUND + "/", variablesService.getDaysOfSupplySubmodelApiAssetId())); log.debug("Created body for material " + material.getOwnMaterialNumber() + "\n" + body.toPrettyString()); return body; } @@ -145,6 +145,7 @@ public JsonNode createProductRegistrationRequestBody(Material material, String p submodelDescriptorsArray.add(createSubmodelObject(AssetType.ITEM_STOCK_SUBMODEL.URN_SEMANTIC_ID, href + DirectionCharacteristic.OUTBOUND + "/", variablesService.getItemStockSubmodelApiAssetId())); submodelDescriptorsArray.add(createSubmodelObject(AssetType.PRODUCTION_SUBMODEL.URN_SEMANTIC_ID, href, variablesService.getProductionSubmodelApiAssetId())); submodelDescriptorsArray.add(createSubmodelObject(AssetType.DELIVERY_SUBMODEL.URN_SEMANTIC_ID, href, variablesService.getDeliverySubmodelApiAssetId())); + submodelDescriptorsArray.add(createSubmodelObject(AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, href + DirectionCharacteristic.OUTBOUND + "/", variablesService.getDaysOfSupplySubmodelApiAssetId())); submodelDescriptorsArray.add(createPartTypeSubmodelObject(material.getOwnMaterialNumber())); log.debug("Created body for product " + material.getOwnMaterialNumber() + "\n" + body.toPrettyString()); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/AssetType.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/AssetType.java index 4e435b28..7fb93f01 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/AssetType.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/AssetType.java @@ -27,6 +27,7 @@ public enum AssetType { DEMAND_SUBMODEL("urn:samm:io.catenax.short_term_material_demand:1.0.0#ShortTermMaterialDemand", "$value", "ShortTermMaterialDemand", "1.0"), DELIVERY_SUBMODEL("urn:samm:io.catenax.delivery_information:2.0.0#DeliveryInformation", "$value", "DeliveryInformation", "2.0"), NOTIFICATION("urn:samm:io.catenax.demand_and_capacity_notification:2.0.0#DemandAndCapacityNotification", "none", "none", "2.0"), + DAYS_OF_SUPPLY("urn:samm:io.catenax.days_of_supply:1.0.0#DaysOfSupply", "$value", "DaysOfSupply", "1.0"), PART_TYPE_INFORMATION_SUBMODEL("urn:samm:io.catenax.part_type_information:1.0.0#PartTypeInformation", "$value", "none", "1.0"); public final String URN_SEMANTIC_ID; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/DaysOfSupplyContractMapping.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/DaysOfSupplyContractMapping.java new file mode 100644 index 00000000..9124a85f --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/model/DaysOfSupplyContractMapping.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.tractusx.puris.backend.common.edc.domain.model; + +import jakarta.persistence.Entity; +import lombok.ToString; + +@Entity +@ToString(callSuper = true) +public class DaysOfSupplyContractMapping extends ContractMapping { +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/repository/DaysOfSupplyContractMappingRepository.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/repository/DaysOfSupplyContractMappingRepository.java new file mode 100644 index 00000000..c6355c2e --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/domain/repository/DaysOfSupplyContractMappingRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.tractusx.puris.backend.common.edc.domain.repository; + +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.ContractMapping; +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.DaysOfSupplyContractMapping; +import org.springframework.stereotype.Repository; + +@Repository +public interface DaysOfSupplyContractMappingRepository extends GeneralContractMappingRepository { + @Override + default Class getType() { + return DaysOfSupplyContractMapping.class; + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java index 51960410..9d55a343 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcAdapterService.java @@ -174,6 +174,11 @@ public boolean registerAssetsInitially() { variablesService.getNotificationApiAssetId(), variablesService.getNotificationEndpoint() ))); + log.info("Registration of Days of Supply 1.0.0 submodel successful {}", (assetRegistration = registerSubmodelAsset( + variablesService.getDaysOfSupplySubmodelApiAssetId(), + variablesService.getDaysOfSupplySubmodelEndpoint(), + AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID + ))); log.info("Registration of PartTypeInformation 1.0.0 submodel successful {}", (assetRegistration = registerPartTypeInfoSubmodelAsset())); result &= assetRegistration; return result; @@ -194,6 +199,7 @@ public boolean createPolicyAndContractDefForPartner(Partner partner) { result &= createSubmodelContractDefinitionForPartner(AssetType.DEMAND_SUBMODEL.URN_SEMANTIC_ID, variablesService.getDemandSubmodelApiAssetId(), partner); result &= createSubmodelContractDefinitionForPartner(AssetType.DELIVERY_SUBMODEL.URN_SEMANTIC_ID, variablesService.getDeliverySubmodelApiAssetId(), partner); result &= createSubmodelContractDefinitionForPartner(AssetType.NOTIFICATION.URN_SEMANTIC_ID, variablesService.getNotificationApiAssetId(), partner); + result &= createSubmodelContractDefinitionForPartner(AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, variablesService.getDaysOfSupplySubmodelApiAssetId(), partner); result &= createDtrContractDefinitionForPartner(partner); return createSubmodelContractDefinitionForPartner(AssetType.PART_TYPE_INFORMATION_SUBMODEL.URN_SEMANTIC_ID, variablesService.getPartTypeSubmodelApiAssetId(), partner) && result; } @@ -598,6 +604,7 @@ private JsonNode getSubmodelFromPartner(MaterialPartnerRelation mpr, AssetType t case DEMAND_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DEMAND_SUBMODEL.URN_SEMANTIC_ID, direction); case DELIVERY_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DELIVERY_SUBMODEL.URN_SEMANTIC_ID, direction); case NOTIFICATION -> throw new IllegalArgumentException("DemandAndCapacityNotification not supported"); + case DAYS_OF_SUPPLY -> fetchSubmodelDataByDirection(mpr, AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, direction); case PART_TYPE_INFORMATION_SUBMODEL -> fetchPartTypeSubmodelData(mpr); }; boolean failed = true; @@ -1033,6 +1040,7 @@ private boolean negotiateContractForSubmodel(MaterialPartnerRelation mpr, AssetT case DEMAND_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DEMAND_SUBMODEL.URN_SEMANTIC_ID, direction); case DELIVERY_SUBMODEL -> fetchSubmodelDataByDirection(mpr, AssetType.DELIVERY_SUBMODEL.URN_SEMANTIC_ID, direction); case NOTIFICATION -> throw new IllegalArgumentException("DemandAndCapacityNotification not supported"); + case DAYS_OF_SUPPLY -> fetchSubmodelDataByDirection(mpr, AssetType.DAYS_OF_SUPPLY.URN_SEMANTIC_ID, direction); case PART_TYPE_INFORMATION_SUBMODEL -> fetchPartTypeSubmodelData(mpr); }; Map equalFilters = new HashMap<>(); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcContractMappingService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcContractMappingService.java index 893fe8b6..9ce7e434 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcContractMappingService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/edc/logic/service/EdcContractMappingService.java @@ -24,6 +24,7 @@ import org.eclipse.tractusx.puris.backend.common.edc.domain.model.ContractMapping; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.DtrContractMapping; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; +import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.DaysOfSupplyContractMappingRepository; import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.DeliveryContractMappingRepository; import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.DemandAndCapacityNotificationContractMappingRepository; import org.eclipse.tractusx.puris.backend.common.edc.domain.repository.DemandContractMappingRepository; @@ -60,6 +61,9 @@ public class EdcContractMappingService { @Autowired private DemandAndCapacityNotificationContractMappingRepository demandAndCapacityNotificationContractMappingRepository; + @Autowired + private DaysOfSupplyContractMappingRepository daysOfSupplyContractMappingRepository; + @Autowired private PartTypeContractMappingRepository partTypeContractMappingRepository; @@ -124,6 +128,7 @@ private GeneralContractMappingRepository getContractM case DEMAND_SUBMODEL -> demandContractMappingRepository; case DELIVERY_SUBMODEL -> deliveryContractMappingRepository; case NOTIFICATION -> demandAndCapacityNotificationContractMappingRepository; + case DAYS_OF_SUPPLY -> daysOfSupplyContractMappingRepository; case PART_TYPE_INFORMATION_SUBMODEL -> partTypeContractMappingRepository; }; return repository; diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java index 55dc965b..fb22d5a8 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/common/util/VariablesService.java @@ -143,6 +143,20 @@ public String getNotificationEndpoint() { */ private String notificationAssetId; + @Value("${puris.baseurl}" + "catena/days-of-supply/request") + /** + * The url under which this application's request endpoint can + * be reached by external machines. + */ + private String daysOfSupplySubmodelEndpoint; + + @Value("${puris.daysofsupplysubmodel.apiassetid}") + /** + * The assetId that shall be assigned to the request API + * during asset creation. + */ + private String daysOfSupplySubmodelAssetId; + @Value("${puris.frameworkagreement.credential}") /** * The name of the framework agreement to be used. @@ -285,6 +299,10 @@ public String getDeliverySubmodelApiAssetId() { return deliverySubmodelAssetId + "@" + ownBpnl; } + public String getDaysOfSupplySubmodelApiAssetId() { + return daysOfSupplySubmodelAssetId + "@" + ownBpnl; + } + public String getNotificationApiAssetId() { return notificationAssetId + "@" + ownBpnl; } 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 027a8de2..96d89fad 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 @@ -19,8 +19,10 @@ See the NOTICE file(s) distributed with this work for additional */ package org.eclipse.tractusx.puris.backend.demand.logic.services; +import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -33,6 +35,9 @@ See the NOTICE file(s) distributed with this work for additional import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Service public class OwnDemandService extends DemandService { public OwnDemandService(OwnDemandRepository repository, PartnerService partnerService, MaterialPartnerRelationService mprService) { @@ -42,11 +47,16 @@ public OwnDemandService(OwnDemandRepository repository, PartnerService partnerSe public final List getQuantityForDays(String material, String partnerBpnl, String siteBpns, int numberOfDays) { List quantities = new ArrayList<>(); LocalDate localDate = LocalDate.now(); - + List demands = findAllByFilters(Optional.of(material), Optional.of(partnerBpnl), Optional.of(siteBpns), Optional.empty()); for (int i = 0; i < numberOfDays; i++) { Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); - List demands = findAllByFilters(Optional.of(material), Optional.of(partnerBpnl), Optional.of(siteBpns), Optional.of(date)); - double demandQuantity = getSumOfQuantities(demands); + LocalDate localDayDate = Instant.ofEpochMilli(date.getTime()).atOffset(ZoneOffset.UTC).toLocalDate(); + List demandsForDate = demands.stream().filter(demand -> { + log.info("Demand: ", demand.toString()); + LocalDate demandDayDate = Instant.ofEpochMilli(demand.getDay().getTime()).atOffset(ZoneOffset.UTC).toLocalDate(); + return demandDayDate.getDayOfMonth() == localDayDate.getDayOfMonth(); + }).toList(); + double demandQuantity = getSumOfQuantities(demandsForDate); quantities.add(demandQuantity); localDate = localDate.plusDays(1); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/DaysOfSupplyRequestApiController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/DaysOfSupplyRequestApiController.java new file mode 100644 index 00000000..66eb0d8e --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/DaysOfSupplyRequestApiController.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023, 2024 Volkswagen AG + * Copyright (c) 2023, 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.puris.backend.supply.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm.DaysOfSupply; +import org.eclipse.tractusx.puris.backend.supply.logic.service.DaysOfSupplyRequestApiService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.regex.Pattern; + +@RestController +@RequestMapping("days-of-supply") +@Slf4j +/** + * This class offers the endpoint for requesting the DaysOfSupply Submodel 1.0.0 + */ +public class DaysOfSupplyRequestApiController { + + @Autowired + private DaysOfSupplyRequestApiService daysOfSupplyRequestApiService; + + private final Pattern bpnlPattern = PatternStore.BPNL_PATTERN; + + private final Pattern urnPattern = PatternStore.URN_OR_UUID_PATTERN; + + @Operation(summary = "This endpoint receives the DaysOfSupply Submodel 1.0.0 requests") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error"), + @ApiResponse(responseCode = "501", description = "Unsupported representation") + }) + @GetMapping("request/{materialnumbercx}/{direction}/{representation}") + public ResponseEntity getDaysOfSupplyMapping( + @RequestHeader("edc-bpn") String bpnl, + @PathVariable String materialnumbercx, + @PathVariable DirectionCharacteristic direction, + @PathVariable String representation) { + if (!bpnlPattern.matcher(bpnl).matches() || !urnPattern.matcher(materialnumbercx).matches()) { + log.warn("Rejecting request at DaysOfSupply Submodel request 1.0.0 endpoint"); + return ResponseEntity.badRequest().build(); + } + if (!"$value".equals(representation)) { + log.warn("Rejecting request at DaysOfSupply Submodel request 1.0.0 endpoint, missing '$value' in request"); + if (!PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN.matcher(representation).matches()) { + representation = ""; + } + return ResponseEntity.status(501).build(); + } + var samm = daysOfSupplyRequestApiService.handleDaysOfSupplySubmodelRequest(bpnl, materialnumbercx, direction); + if (samm == null) { + return ResponseEntity.status(500).build(); + } + return ResponseEntity.ok(samm); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/SupplyController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/SupplyController.java index 33ef35f1..c9448cd6 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/SupplyController.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/controller/SupplyController.java @@ -21,20 +21,34 @@ package org.eclipse.tractusx.puris.backend.supply.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.dto.PartnerDto; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialService; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; import org.eclipse.tractusx.puris.backend.supply.domain.model.Supply; import org.eclipse.tractusx.puris.backend.supply.logic.dto.SupplyDto; import org.eclipse.tractusx.puris.backend.supply.logic.service.CustomerSupplyService; +import org.eclipse.tractusx.puris.backend.supply.logic.service.DaysOfSupplyRequestApiService; import org.eclipse.tractusx.puris.backend.supply.logic.service.SupplierSupplyService; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.regex.Pattern; @RestController @RequestMapping("days-of-supply") @@ -44,8 +58,17 @@ public class SupplyController { @Autowired private SupplierSupplyService supplierSupplyService; + @Autowired + private DaysOfSupplyRequestApiService daysOfSupplyRequestApiService; + @Autowired + private MaterialService materialService; + @Autowired + private MaterialPartnerRelationService mprService; @Autowired private ModelMapper modelMapper; + private final Pattern materialPattern = PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN; + @Autowired + private ExecutorService executorService; @GetMapping("customer") @ResponseBody @@ -64,10 +87,30 @@ public List calculateCustomerDaysOfSupply(String materialNumber, Stri "materialNumber is expected to be base64 encoded") public List getCustomerDaysOfSupply(String materialNumber, String bpnl) { materialNumber = new String(Base64.getDecoder().decode(materialNumber.getBytes(StandardCharsets.UTF_8))); - return customerSupplyService.findByPartnerBpnlAndOwnMaterialNumber(materialNumber, bpnl) + return customerSupplyService.findAllByMaterialNumberAndPartnerBpnl(materialNumber, bpnl) .stream().map(this::convertToDto).toList(); } + @GetMapping("customer/reported/refresh") + @ResponseBody + @Operation( + summary = "Refreshes all reported customer days of supply", + description = "Refreshes all reported customer Days of Supply for the specified Material from the days of supply request API." + ) + public ResponseEntity> refreshReportedCustomerSupply(@RequestParam @Parameter(description = "base64 encoded") String ownMaterialNumber) { + ownMaterialNumber = new String(Base64.getDecoder().decode(ownMaterialNumber)); + if (!materialPattern.matcher(ownMaterialNumber).matches()) { + return new ResponseEntity<>(HttpStatusCode.valueOf(400)); + } + Material materialEntity = materialService.findByOwnMaterialNumber(ownMaterialNumber); + List allCustomerPartnerEntities = mprService.findAllCustomersForOwnMaterialNumber(ownMaterialNumber); + for (Partner customerPartner : allCustomerPartnerEntities) { + executorService.submit(() -> + daysOfSupplyRequestApiService.doReportedDaysOfSupplyRequest(customerPartner, materialEntity, DirectionCharacteristic.INBOUND)); + } + return ResponseEntity.ok(allCustomerPartnerEntities.stream().map(partner -> modelMapper.map(partner, PartnerDto.class)).toList()); + } + @GetMapping("supplier") @Operation(summary = "Calculate days of supply for supplier for given number of days.", description = "Calculate days of supply for supplier for given number of days. Filtered by given material number, partner bpnl and site bpns. "+ @@ -84,9 +127,29 @@ public List calculateSupplierDaysOfSupply(String materialNumber, Stri "materialNumber is expected to be base64 encoded") public List getSupplierDaysOfSupply(String materialNumber, String bpnl) { materialNumber = new String(Base64.getDecoder().decode(materialNumber.getBytes(StandardCharsets.UTF_8))); - return supplierSupplyService.findByPartnerBpnlAndOwnMaterialNumber(materialNumber, bpnl) + return supplierSupplyService.findAllByMaterialNumberAndPartnerBpnl(materialNumber, bpnl) .stream().map(this::convertToDto).toList(); } + + @GetMapping("supplier/reported/refresh") + @ResponseBody + @Operation( + summary = "Refreshes all reported supplier days of supply", + description = "Refreshes all reported supplier Days of Supply for the specified Material from the days of supply request API." + ) + public ResponseEntity> refreshReportedSupplierSupply(@RequestParam @Parameter(description = "base64 encoded") String ownMaterialNumber) { + ownMaterialNumber = new String(Base64.getDecoder().decode(ownMaterialNumber)); + if (!materialPattern.matcher(ownMaterialNumber).matches()) { + return new ResponseEntity<>(HttpStatusCode.valueOf(400)); + } + Material materialEntity = materialService.findByOwnMaterialNumber(ownMaterialNumber); + List allSupplierPartnerEntities = mprService.findAllSuppliersForOwnMaterialNumber(ownMaterialNumber); + for (Partner supplierPartner : allSupplierPartnerEntities) { + executorService.submit(() -> + daysOfSupplyRequestApiService.doReportedDaysOfSupplyRequest(supplierPartner, materialEntity, DirectionCharacteristic.OUTBOUND)); + } + return ResponseEntity.ok(allSupplierPartnerEntities.stream().map(partner -> modelMapper.map(partner, PartnerDto.class)).toList()); + } private SupplyDto convertToDto(Supply entity) { SupplyDto dto = modelMapper.map(entity, SupplyDto.class); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedCustomerSupplyRepository.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedCustomerSupplyRepository.java index a4577527..1f5b7af1 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedCustomerSupplyRepository.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedCustomerSupplyRepository.java @@ -27,5 +27,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ReportedCustomerSupplyRepository extends JpaRepository { - List findByPartner_BpnlAndMaterial_OwnMaterialNumber(String partnerBpnl, String ownMaterialNumber); + List findByMaterial_OwnMaterialNumberAndPartner_Bpnl(String ownMaterialNumber, String bpnl); } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedSupplierSupplyRepository.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedSupplierSupplyRepository.java index f949ddb9..7a62e8e9 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedSupplierSupplyRepository.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/domain/repository/ReportedSupplierSupplyRepository.java @@ -22,11 +22,10 @@ import java.util.List; import java.util.UUID; - import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedSupplierSupply; import org.springframework.data.jpa.repository.JpaRepository; public interface ReportedSupplierSupplyRepository extends JpaRepository { - List findByPartner_BpnlAndMaterial_OwnMaterialNumber(String partnerBpnl, String ownMaterialNumber); + List findByMaterial_OwnMaterialNumberAndPartner_Bpnl(String ownMaterialNumber, String bpnl); } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/adapter/DaysOfSupplySammMapper.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/adapter/DaysOfSupplySammMapper.java new file mode 100644 index 00000000..1218182c --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/adapter/DaysOfSupplySammMapper.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.tractusx.puris.backend.supply.logic.adapter; + +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Material; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialService; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnCustomerSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnSupplierSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedCustomerSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedSupplierSupply; +import org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm.AllocatedDaysOfSupply; +import org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm.DaysOfSupply; +import org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm.QuantityOfDaysOfSupply; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +@Service +@Slf4j +public class DaysOfSupplySammMapper { + @Autowired + private MaterialPartnerRelationService mprService; + @Autowired + private MaterialService materialService; + + public DaysOfSupply supplierSupplyToSamm(List> suppliesBySite, Partner partner, Material material) { + if (suppliesBySite.stream().anyMatch(siteSupplies -> siteSupplies.stream().anyMatch(sup -> !sup.getPartner().equals(partner)))) { + log.warn("Can't map supply list with different partners"); + return null; + } + if (suppliesBySite.stream().anyMatch(siteSupplies -> siteSupplies.stream().anyMatch(sup -> !sup.getMaterial().equals(material)))) { + log.warn("Can't map supply list with different materials"); + return null; + } + DaysOfSupply samm = new DaysOfSupply(); + var mpr = mprService.findAll().stream().filter(mr -> mr.getMaterial().equals(material) && mr.getPartner().equals(partner)).findFirst().orElse(null); + if (mpr == null) { + log.warn("Could not identify materialPartnerRelation with ownMaterialNumber " + material.getOwnMaterialNumber() + + " and partner bpnl " + partner.getBpnl()); + return null; + } + samm.setMaterialGlobalAssetId(material.getMaterialNumberCx()); + samm.setDirection(DirectionCharacteristic.OUTBOUND); + var suppliesPerDay = new HashSet(); + samm.setAllocatedDaysOfSupply(suppliesPerDay); + for (var supplyBySite : suppliesBySite) { + var allocatedDaysOfSupply = new AllocatedDaysOfSupply(); + allocatedDaysOfSupply.setStockLocationBPNS(supplyBySite.get(0).getStockLocationBPNS()); + allocatedDaysOfSupply.setStockLocationBPNA(supplyBySite.get(0).getStockLocationBPNA()); + allocatedDaysOfSupply.setLastUpdatedOnDateTime(new Date()); + allocatedDaysOfSupply.setAmountOfAllocatedDaysOfSupply( + supplyBySite.stream().map(sup -> new QuantityOfDaysOfSupply(sup.getDate(), sup.getDaysOfSupply())).toList()); + suppliesPerDay.add(allocatedDaysOfSupply); + } + return samm; + } + + public DaysOfSupply customerSupplyToSamm(List> suppliesBySite, Partner partner, Material material) { + if (suppliesBySite.stream().anyMatch(siteSupplies -> siteSupplies.stream().anyMatch(sup -> !sup.getPartner().equals(partner)))) { + log.warn("Can't map supply list with different partners"); + return null; + } + if (suppliesBySite.stream().anyMatch(siteSupplies -> siteSupplies.stream().anyMatch(sup -> !sup.getMaterial().equals(material)))) { + log.warn("Can't map supply list with different materials"); + return null; + } + var mpr = mprService.findAll().stream().filter(mr -> mr.getMaterial().equals(material) && mr.getPartner().equals(partner)).findFirst().orElse(null); + if (mpr == null) { + log.warn("Could not identify materialPartnerRelation with ownMaterialNumber " + material.getOwnMaterialNumber() + + " and partner bpnl " + partner.getBpnl()); + return null; + } + DaysOfSupply samm = new DaysOfSupply(); + samm.setMaterialGlobalAssetId(mpr.getPartnerCXNumber()); + samm.setDirection(DirectionCharacteristic.INBOUND); + var suppliesPerDay = new HashSet(); + samm.setAllocatedDaysOfSupply(suppliesPerDay); + for (var supplyBySite : suppliesBySite) { + var allocatedDaysOfSupply = new AllocatedDaysOfSupply(); + allocatedDaysOfSupply.setStockLocationBPNS(supplyBySite.get(0).getStockLocationBPNS()); + allocatedDaysOfSupply.setStockLocationBPNA(supplyBySite.get(0).getStockLocationBPNA()); + allocatedDaysOfSupply.setLastUpdatedOnDateTime(new Date()); + allocatedDaysOfSupply.setAmountOfAllocatedDaysOfSupply( + supplyBySite.stream().map(sup -> new QuantityOfDaysOfSupply(sup.getDate(), sup.getDaysOfSupply())).toList()); + suppliesPerDay.add(allocatedDaysOfSupply); + } + return samm; + } + + public List sammToReportedCustomerSupply(DaysOfSupply samm, Partner partner) { + String matNbrCatenaX = samm.getMaterialGlobalAssetId(); + ArrayList outputList = new ArrayList<>(); + var material = materialService.findByMaterialNumberCx(matNbrCatenaX); + if (material == null) { + log.warn("Could not identify material with given CatenaXNbr "); + return outputList; + } + for (var allocatedSupplies : samm.getAllocatedDaysOfSupply()) { + for (var supply : allocatedSupplies.getAmountOfAllocatedDaysOfSupply()) { + var builder = ReportedCustomerSupply.builder(); + var reportedCustomerSupply = builder + .date(supply.getDate()) + .daysOfSupply(supply.getDaysOfSupply()) + .material(material) + .partner(partner) + .stockLocationBPNA(allocatedSupplies.getStockLocationBPNA()) + .stockLocationBPNS(allocatedSupplies.getStockLocationBPNS()) + .build(); + outputList.add(reportedCustomerSupply); + } + } + return outputList; + } + + public List sammToReportedSupplierSupply(DaysOfSupply samm, Partner partner) { + String matNbrCatenaX = samm.getMaterialGlobalAssetId(); + ArrayList outputList = new ArrayList<>(); + var mpr = mprService.findByPartnerAndPartnerCXNumber(partner, matNbrCatenaX); + if (mpr == null) { + log.warn("Could not identify material partner relation with given partner and Material Number Cx"); + return outputList; + } + var material = mpr.getMaterial(); + for (var allocatedSupplies : samm.getAllocatedDaysOfSupply()) { + for (var supply : allocatedSupplies.getAmountOfAllocatedDaysOfSupply()) { + var builder = ReportedSupplierSupply.builder(); + var reportedSupplierSupply = builder + .date(supply.getDate()) + .daysOfSupply(supply.getDaysOfSupply()) + .material(material) + .partner(partner) + .stockLocationBPNA(allocatedSupplies.getStockLocationBPNA()) + .stockLocationBPNS(allocatedSupplies.getStockLocationBPNS()) + .build(); + outputList.add(reportedSupplierSupply); + } + } + return outputList; + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/AllocatedDaysOfSupply.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/AllocatedDaysOfSupply.java new file mode 100644 index 00000000..365b212b --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/AllocatedDaysOfSupply.java @@ -0,0 +1,93 @@ +/* +Copyright (c) 2024 Volkswagen AG +Copyright (c) 2024 Contributors to the Eclipse Foundation + +See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership. + +This program and the accompanying materials are made available under the +terms of the Apache License, Version 2.0 which is available at +https://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class AllocatedDaysOfSupply { + + @NotNull + @Valid + private List amountOfAllocatedDaysOfSupply; + + @NotNull + @Pattern(regexp = PatternStore.BPNS_STRING) + private String stockLocationBPNS; + + @NotNull + @Pattern(regexp = PatternStore.BPNA_STRING) + private String stockLocationBPNA; + + @NotNull + private Date lastUpdatedOnDateTime; + + @JsonCreator + public AllocatedDaysOfSupply( + @JsonProperty(value = "amountOfAllocatedDaysOfSupply") List amountOfAllocatedDaysOfSupply, + @JsonProperty(value = "stockLocationBPNS") String stockLocationBPNS, + @JsonProperty(value = "stockLocationBPNA") String stockLocationBPNA, + @JsonProperty(value = "lastUpdatedOnDateTime") Date lastUpdatedOnDateTime) { + this.amountOfAllocatedDaysOfSupply = amountOfAllocatedDaysOfSupply; + this.stockLocationBPNS = stockLocationBPNS; + this.stockLocationBPNA = stockLocationBPNA; + this.lastUpdatedOnDateTime = lastUpdatedOnDateTime; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final AllocatedDaysOfSupply that = (AllocatedDaysOfSupply) o; + return Objects.equals(amountOfAllocatedDaysOfSupply, that.amountOfAllocatedDaysOfSupply) + && Objects.equals(stockLocationBPNS, that.stockLocationBPNS) + && Objects.equals(stockLocationBPNA, that.stockLocationBPNA) + && Objects.equals(lastUpdatedOnDateTime, that.lastUpdatedOnDateTime); + } + + @Override + public int hashCode() { + return Objects.hash(amountOfAllocatedDaysOfSupply, stockLocationBPNS, stockLocationBPNA, lastUpdatedOnDateTime); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/DaysOfSupply.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/DaysOfSupply.java new file mode 100644 index 00000000..08b66456 --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/DaysOfSupply.java @@ -0,0 +1,81 @@ +/* +Copyright (c) 2024 Volkswagen AG +Copyright (c) 2024 Contributors to the Eclipse Foundation + +See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership. + +This program and the accompanying materials are made available under the +terms of the Apache License, Version 2.0 which is available at +https://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm; + +import java.util.HashSet; +import java.util.Objects; + +import org.eclipse.tractusx.puris.backend.common.util.PatternStore; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class DaysOfSupply { + @NotNull + @Pattern(regexp = PatternStore.URN_OR_UUID_STRING) + private String materialGlobalAssetId; + + @NotNull + private DirectionCharacteristic direction; + + @NotNull + private HashSet allocatedDaysOfSupply; + + @JsonCreator + public DaysOfSupply( + @JsonProperty(value = "materialGlobalAssetId") String materialGlobalAssetId, + @JsonProperty(value = "direction") DirectionCharacteristic direction, + @JsonProperty(value = "allocatedDaysOfSupply") HashSet allocatedDaysOfSupply) { + this.materialGlobalAssetId = materialGlobalAssetId; + this.direction = direction; + this.allocatedDaysOfSupply = allocatedDaysOfSupply; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DaysOfSupply that = (DaysOfSupply) o; + return Objects.equals(materialGlobalAssetId, that.materialGlobalAssetId) && + Objects.equals(direction, that.direction) && + Objects.equals(allocatedDaysOfSupply, that.allocatedDaysOfSupply); + } + + @Override + public int hashCode() { + return Objects.hash(materialGlobalAssetId, direction, allocatedDaysOfSupply); + } +} diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/QuantityOfDaysOfSupply.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/QuantityOfDaysOfSupply.java new file mode 100644 index 00000000..b93cde9f --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/dto/daysofsupplysamm/QuantityOfDaysOfSupply.java @@ -0,0 +1,68 @@ +/* +Copyright (c) 2024 Volkswagen AG +Copyright (c) 2024 Contributors to the Eclipse Foundation + +See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership. + +This program and the accompanying materials are made available under the +terms of the Apache License, Version 2.0 which is available at +https://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm; + +import java.util.Date; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class QuantityOfDaysOfSupply { + + @NotNull + private Date date; + + @NotNull + private Double daysOfSupply; + + @JsonCreator + public QuantityOfDaysOfSupply(@JsonProperty(value = "date") Date date, @JsonProperty(value = "daysOfSupply") Double daysOfSupply) { + this.date = date; + this.daysOfSupply = daysOfSupply; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final QuantityOfDaysOfSupply that = (QuantityOfDaysOfSupply) o; + return Objects.equals(date, that.date) && Objects.equals(daysOfSupply, that.daysOfSupply); + } + + @Override + public int hashCode() { + return Objects.hash(date, daysOfSupply); + } +} 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 565a7e0c..96214301 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 @@ -23,14 +23,16 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Stream; 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.logic.service.MaterialService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; +import org.eclipse.tractusx.puris.backend.stock.domain.model.MaterialItemStock; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.eclipse.tractusx.puris.backend.stock.logic.service.MaterialItemStockService; import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnCustomerSupply; import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedCustomerSupply; import org.eclipse.tractusx.puris.backend.supply.domain.repository.ReportedCustomerSupplyRepository; @@ -38,11 +40,7 @@ import org.springframework.stereotype.Service; @Service -public class CustomerSupplyService extends SupplyService { - @Autowired - private ReportedCustomerSupplyRepository repository; - @Autowired - private PartnerService partnerService; +public class CustomerSupplyService extends SupplyService { @Autowired private OwnDeliveryService ownDeliveryService; @Autowired @@ -50,10 +48,8 @@ public class CustomerSupplyService extends SupplyService { @Autowired private OwnDemandService demandService; - protected final Function validator; - - public CustomerSupplyService() { - this.validator = this::validate; + public CustomerSupplyService(ReportedCustomerSupplyRepository repository, PartnerService partnerService, MaterialService materialService) { + super(repository, partnerService, materialService); } @Override @@ -89,8 +85,8 @@ public final List calculateCustomerDaysOfSupply(String materi return calculateDaysOfSupply(material, partnerBpnl, siteBpns, numberOfDays); } - public final List findAll() { - return repository.findAll(); + public final List findAllByMaterialNumberAndPartnerBpnl(String ownMaterialNumber, String partnerBpnl) { + return repository.findByMaterial_OwnMaterialNumberAndPartner_Bpnl(ownMaterialNumber, partnerBpnl); } public final ReportedCustomerSupply findById(UUID id) { @@ -108,10 +104,6 @@ public final List findAllByFilters(Optional ownM return stream.toList(); } - public final List findByPartnerBpnlAndOwnMaterialNumber(String partnerBpnl, String ownMaterialNumber) { - return repository.findByPartner_BpnlAndMaterial_OwnMaterialNumber(partnerBpnl, ownMaterialNumber); - } - public boolean validate(ReportedCustomerSupply daysOfSupply) { return daysOfSupply.getPartner() != null && @@ -119,7 +111,6 @@ public boolean validate(ReportedCustomerSupply daysOfSupply) { daysOfSupply.getDate() != null && daysOfSupply.getStockLocationBPNS() != null && daysOfSupply.getPartner() != partnerService.getOwnPartnerEntity() && - daysOfSupply.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(daysOfSupply.getStockLocationBPNS())) && - (daysOfSupply.getStockLocationBPNA().equals(null) || daysOfSupply.getStockLocationBPNA().equals(daysOfSupply.getStockLocationBPNS())); + daysOfSupply.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(daysOfSupply.getStockLocationBPNS())); } } 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 new file mode 100644 index 00000000..1b20f15a --- /dev/null +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/supply/logic/service/DaysOfSupplyRequestApiService.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024 Volkswagen AG + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.puris.backend.supply.logic.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.masterdata.domain.model.Material; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.MaterialPartnerRelation; +import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; +import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnCustomerSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnSupplierSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedCustomerSupply; +import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedSupplierSupply; +import org.eclipse.tractusx.puris.backend.supply.logic.adapter.DaysOfSupplySammMapper; +import org.eclipse.tractusx.puris.backend.supply.logic.dto.daysofsupplysamm.DaysOfSupply; +import org.modelmapper.ModelMapper; +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 +@Slf4j +public class DaysOfSupplyRequestApiService { + @Autowired + private PartnerService partnerService; + @Autowired + private MaterialPartnerRelationService mprService; + @Autowired + private SupplierSupplyService supplierSupplyService; + @Autowired + private CustomerSupplyService customerSupplyService; + @Autowired + private EdcAdapterService edcAdapterService; + @Autowired + private DaysOfSupplySammMapper sammMapper; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private ModelMapper modelMapper; + + public DaysOfSupply handleDaysOfSupplySubmodelRequest(String bpnl, String materialNumberCx, DirectionCharacteristic direction) { + Partner partner = partnerService.findByBpnl(bpnl); + if (partner == null) { + log.error("Unknown Partner BPNL"); + return null; + } + MaterialPartnerRelation mpr = switch (direction) { + case OUTBOUND -> mprService.findAll().stream() + .filter(m -> m.getPartner().equals(partner) && m.getMaterial().getMaterialNumberCx().equals(materialNumberCx)) + .findFirst().orElse(null); + case INBOUND -> mprService.findByPartnerAndPartnerCXNumber(partner, materialNumberCx); + }; + if (mpr == null) { + log.error("Could not identify Material-Partner Relation with material number " + materialNumberCx + " and partner bpnl " + partner.getBpnl()); + return null; + } + Material material = mpr.getMaterial(); + + var sites = partnerService.getOwnPartnerEntity().getSites(); + if (direction == DirectionCharacteristic.OUTBOUND) { + List> suppliesBySite = new ArrayList<>(); + for (var site : sites) { + var supplierSupply = supplierSupplyService.calculateSupplierDaysOfSupply( + material.getOwnMaterialNumber(), partner.getBpnl(), site.getBpns(), 28); + supplierSupply.forEach(supply -> { + supply.setStockLocationBPNS(site.getBpns()); + supply.setStockLocationBPNA(site.getAddresses().first().getBpna()); + }); + suppliesBySite.add(supplierSupply); + } + return sammMapper.supplierSupplyToSamm(suppliesBySite, partner, material); + } else { + List> suppliesBySite = new ArrayList<>(); + for (var site : sites) { + var customerSupply = customerSupplyService.calculateCustomerDaysOfSupply( + material.getOwnMaterialNumber(), partner.getBpnl(), site.getBpns(), 28); + customerSupply.forEach(supply -> { + supply.setStockLocationBPNS(site.getBpns()); + supply.setStockLocationBPNA(site.getAddresses().first().getBpna()); + }); + suppliesBySite.add(customerSupply); + } + return sammMapper.customerSupplyToSamm(suppliesBySite, partner, material); + } + } + + public void doReportedDaysOfSupplyRequest(Partner partner, Material material, DirectionCharacteristic direction) { + try { + var mpr = mprService.find(material, partner); + if (mpr.getPartnerCXNumber() == null) { + mprService.triggerPartTypeRetrievalTask(partner); + mpr = mprService.find(material, partner); + } + var data = edcAdapterService.doSubmodelRequest(AssetType.DAYS_OF_SUPPLY, mpr, direction, 1); + var samm = objectMapper.treeToValue(data, DaysOfSupply.class); + if (direction == DirectionCharacteristic.INBOUND) { + var reportedCustomerSupplies = sammMapper.sammToReportedCustomerSupply(samm, partner); + for (var reportedCustomerSupply : reportedCustomerSupplies) { + 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; + } + } + var oldSupplies = customerSupplyService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl())); + for (var oldSupply : oldSupplies) { + customerSupplyService.deleteReportedSupply(oldSupply); + } + for (var newSupply : reportedCustomerSupplies) { + customerSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedCustomerSupply.class)); + } + } else { + var reportedSupplierSupplies = sammMapper.sammToReportedSupplierSupply(samm, partner); + for (var reportedSupplierSupply : reportedSupplierSupplies) { + 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; + } + } + var oldSupplies = supplierSupplyService.findAllByFilters(Optional.of(material.getOwnMaterialNumber()), Optional.of(partner.getBpnl())); + for (var oldSupply : oldSupplies) { + supplierSupplyService.deleteReportedSupply(oldSupply); + } + for (var newSupply : reportedSupplierSupplies) { + supplierSupplyService.createReportedSupply(modelMapper.map(newSupply, ReportedSupplierSupply.class)); + } + } + log.info("Updated ReportedSupply for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl()); + } catch (Exception e) { + log.error("Error in ReportedDaysOfSupply request for " + material.getOwnMaterialNumber() + " and partner " + partner.getBpnl(), e); + } + } +} 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 ba2d83a5..956d8165 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 @@ -23,14 +23,16 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Stream; 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.logic.service.MaterialService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; import org.eclipse.tractusx.puris.backend.production.logic.service.OwnProductionService; +import org.eclipse.tractusx.puris.backend.stock.domain.model.ProductItemStock; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import org.eclipse.tractusx.puris.backend.stock.logic.service.ProductItemStockService; import org.eclipse.tractusx.puris.backend.supply.domain.model.OwnSupplierSupply; import org.eclipse.tractusx.puris.backend.supply.domain.model.ReportedSupplierSupply; import org.eclipse.tractusx.puris.backend.supply.domain.repository.ReportedSupplierSupplyRepository; @@ -38,11 +40,7 @@ import org.springframework.stereotype.Service; @Service -public class SupplierSupplyService extends SupplyService { - @Autowired - private ReportedSupplierSupplyRepository repository; - @Autowired - private PartnerService partnerService; +public class SupplierSupplyService extends SupplyService { @Autowired private OwnDeliveryService ownDeliveryService; @Autowired @@ -50,10 +48,8 @@ public class SupplierSupplyService extends SupplyService { @Autowired private OwnProductionService productionService; - protected final Function validator; - - public SupplierSupplyService() { - this.validator = this::validate; + public SupplierSupplyService(ReportedSupplierSupplyRepository repository, PartnerService partnerService, MaterialService materialService) { + super(repository, partnerService, materialService); } @Override @@ -89,8 +85,8 @@ public final List calculateSupplierDaysOfSupply(String materi return calculateDaysOfSupply(material, partnerBpnl, siteBpns, numberOfDays); } - public final List findAll() { - return repository.findAll(); + public final List findAllByMaterialNumberAndPartnerBpnl(String ownMaterialNumber, String partnerBpnl) { + return repository.findByMaterial_OwnMaterialNumberAndPartner_Bpnl(ownMaterialNumber, partnerBpnl); } public final ReportedSupplierSupply findById(UUID id) { @@ -108,9 +104,6 @@ public final List findAllByFilters(Optional ownM return stream.toList(); } - public final List findByPartnerBpnlAndOwnMaterialNumber(String partnerBpnl, String ownMaterialNumber) { - return repository.findByPartner_BpnlAndMaterial_OwnMaterialNumber(partnerBpnl, ownMaterialNumber); - } public boolean validate(ReportedSupplierSupply daysOfSupply) { return @@ -119,7 +112,6 @@ public boolean validate(ReportedSupplierSupply daysOfSupply) { daysOfSupply.getDate() != null && daysOfSupply.getStockLocationBPNS() != null && daysOfSupply.getPartner() != partnerService.getOwnPartnerEntity() && - daysOfSupply.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(daysOfSupply.getStockLocationBPNS())) && - (daysOfSupply.getStockLocationBPNA().equals(null) || daysOfSupply.getStockLocationBPNA().equals(daysOfSupply.getStockLocationBPNS())); + daysOfSupply.getPartner().getSites().stream().anyMatch(site -> site.getBpns().equals(daysOfSupply.getStockLocationBPNS())); } } 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 bccf067c..3be6a42f 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 @@ -25,21 +25,56 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.UUID; +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.MaterialService; -import org.eclipse.tractusx.puris.backend.stock.logic.service.MaterialItemStockService; +import org.eclipse.tractusx.puris.backend.masterdata.logic.service.PartnerService; +import org.eclipse.tractusx.puris.backend.stock.domain.model.ItemStock; +import org.eclipse.tractusx.puris.backend.stock.logic.service.ItemStockService; import org.eclipse.tractusx.puris.backend.supply.domain.model.Supply; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; -public abstract class SupplyService { +public abstract class SupplyService, TStock extends ItemStock, TStockService extends ItemStockService> { @Autowired - private MaterialItemStockService stockService; + private TStockService stockService; @Autowired private MaterialService materialService; + @Autowired + protected PartnerService partnerService; + protected final TRepository repository; protected abstract T createSupplyInstance(); protected abstract List getAddedValues(String material, String partnerBpnl, String siteBpns, int numberOfDays); protected abstract List getConsumedValues(String material, String partnerBpnl, String siteBpns, int numberOfDays); + protected abstract boolean validate(TReported daysOfSupply); + + protected final Function validator; + + public SupplyService(TRepository repository, PartnerService partnerService, MaterialService materialService) { + this.repository = repository; + this.partnerService = partnerService; + this.materialService = materialService; + this.validator = this::validate; + } + + public final TReported createReportedSupply(TReported supply) { + if (!validator.apply(supply)) { + throw new IllegalArgumentException("Invalid days of supply"); + } + if (repository.findAll().stream().anyMatch(d -> d.equals(supply))) { + throw new KeyAlreadyExistsException("Supply already exists"); + } + return repository.save(supply); + } + + public final void deleteReportedSupply(TReported entity) { + repository.delete(entity); + } /** * Calculates the days of supply for a given material, partner, and site over a specified number of days. @@ -54,6 +89,7 @@ public abstract class SupplyService { public final List calculateDaysOfSupply(String material, String partnerBpnl, String siteBpns, int numberOfDays) { List supplyList = new ArrayList<>(); LocalDate localDate = LocalDate.now(); + Partner partner = partnerService.findByBpnl(partnerBpnl); List addedValues = getAddedValues(material, partnerBpnl, siteBpns, numberOfDays); List consumedValues = getConsumedValues(material, partnerBpnl, siteBpns, numberOfDays); @@ -70,6 +106,7 @@ public final List calculateDaysOfSupply(String material, String partnerBpnl, supply.setMaterial(materialService.findByOwnMaterialNumber(material)); supply.setDate(date); supply.setDaysOfSupply(daysOfSupply); + supply.setPartner(partner); supplyList.add(supply); stockQuantity = stockQuantity - consumedValues.get(i) + addedValues.get(i); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 841b89f2..a7266a65 100755 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -9,6 +9,7 @@ puris.itemstocksubmodel.apiassetid=${PURIS_ITEMSTOCKSUBMODEL_APIASSETID:itemstoc puris.productionsubmodel.apiassetid=${PURIS_PRODUCTIONSUBMODEL_APIASSETID:productionsubmodel-api-asset} puris.demandsubmodel.apiassetid=${PURIS_DEMANDSUBMODEL_APIASSETID:demandsubmodel-api-asset} puris.deliverysubmodel.apiassetid=${PURIS_DELIVERYSUBMODEL_APIASSETID:deliverysubmodel-api-asset} +puris.daysofsupplysubmodel.apiassetid=${PURIS_DAYSOFSUPPLYSUBMODEL_APIASSETID:daysofsupplysubmodel-api-asset} puris.notification.apiassetid=${PURIS_NOTIFICATION_APIASSETID:notification-api-asset} puris.frameworkagreement.credential=${PURIS_FRAMEWORKAGREEMENT_CREDENTIAL:Puris} puris.frameworkagreement.version=${PURIS_FRAMEWORKAGREEMENT_VERSION:1.0} diff --git a/local/tractus-x-edc/config/customer/puris-backend.properties b/local/tractus-x-edc/config/customer/puris-backend.properties index 0731a0be..23891101 100644 --- a/local/tractus-x-edc/config/customer/puris-backend.properties +++ b/local/tractus-x-edc/config/customer/puris-backend.properties @@ -6,6 +6,7 @@ puris.itemstocksubmodel.apiassetid=itemstocksubmodel-api-asset puris.productionsubmodel.apiassetid=productionsubmodel-api-asset puris.demandsubmodel.apiassetid=demandsubmodel-api-asset puris.deliverysubmodel.apiassetid=deliverysubmodel-api-asset +puris.daysofsupplysubmodel.apiassetid=daysofsupplysubmodel-api-asset puris.notification.apiassetid=notification-api-asset puris.frameworkagreement.credential=Puris puris.frameworkagreement.version=1.0 diff --git a/local/tractus-x-edc/config/supplier/puris-backend.properties b/local/tractus-x-edc/config/supplier/puris-backend.properties index 7f672909..ecf3b2f1 100644 --- a/local/tractus-x-edc/config/supplier/puris-backend.properties +++ b/local/tractus-x-edc/config/supplier/puris-backend.properties @@ -6,6 +6,7 @@ puris.itemstocksubmodel.apiassetid=itemstocksubmodel-api-asset puris.productionsubmodel.apiassetid=productionsubmodel-api-asset puris.demandsubmodel.apiassetid=demandsubmodel-api-asset puris.deliverysubmodel.apiassetid=deliverysubmodel-api-asset +puris.daysofsupplysubmodel.apiassetid=daysofsupplysubmodel-api-asset puris.notification.apiassetid=notification-api-asset puris.frameworkagreement.credential=Puris puris.frameworkagreement.version=1.0