diff --git a/src/main/java/de/avatic/lcc/controller/calculation/CalculationController.java b/src/main/java/de/avatic/lcc/controller/calculation/CalculationController.java index b4afcc9..447ff56 100644 --- a/src/main/java/de/avatic/lcc/controller/calculation/CalculationController.java +++ b/src/main/java/de/avatic/lcc/controller/calculation/CalculationController.java @@ -9,10 +9,12 @@ import de.avatic.lcc.dto.calculation.DestinationDTO; import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO; import de.avatic.lcc.dto.calculation.edit.masterData.*; import de.avatic.lcc.dto.calculation.PremiseDTO; -import de.avatic.lcc.service.calculation.DestinationService; +import de.avatic.lcc.service.access.DestinationService; +import de.avatic.lcc.service.calculation.ChangeMaterialService; +import de.avatic.lcc.service.calculation.ChangeSupplierService; import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService; import de.avatic.lcc.service.calculation.PremiseCreationService; -import de.avatic.lcc.service.calculation.PremisesService; +import de.avatic.lcc.service.access.PremisesService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -27,12 +29,16 @@ public class CalculationController { private final PremisesService premisesServices; private final PremiseCreationService premiseCreationService; private final DestinationService destinationService; + private final ChangeSupplierService changeSupplierService; + private final ChangeMaterialService changeMaterialService; - public CalculationController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService) { + public CalculationController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService, ChangeSupplierService changeSupplierService, ChangeMaterialService changeMaterialService) { this.premiseSearchStringAnalyzerService = premiseSearchStringAnalyzerService; this.premisesServices = premisesServices; this.premiseCreationService = premiseCreationService; this.destinationService = destinationService; + this.changeSupplierService = changeSupplierService; + this.changeMaterialService = changeMaterialService; } @GetMapping("/view") @@ -68,22 +74,22 @@ public class CalculationController { } @PutMapping("/packaging") - public ResponseEntity> updatePackaging(PackagingUpdateDTO packagingDTO) { + public ResponseEntity> updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) { return ResponseEntity.ok(premisesServices.updatePackaging(packagingDTO)); } @PutMapping("/material") - public ResponseEntity> updateMaterial(MaterialUpdateDTO materialUpdateDTO) { + public ResponseEntity> updateMaterial(@RequestBody MaterialUpdateDTO materialUpdateDTO) { return ResponseEntity.ok(premisesServices.updateMaterial(materialUpdateDTO)); } @PutMapping("/price") - public ResponseEntity> updatePrice(PriceUpdateDTO priceUpdateDTO) { + public ResponseEntity> updatePrice(@RequestBody PriceUpdateDTO priceUpdateDTO) { return ResponseEntity.ok(premisesServices.updatePrice(priceUpdateDTO)); } @PostMapping("/destination") - public ResponseEntity> createDestination(DestinationCreateDTO destinationCreateDTO) { + public ResponseEntity> createDestination(@RequestBody DestinationCreateDTO destinationCreateDTO) { return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO)); } @@ -100,18 +106,18 @@ public class CalculationController { @DeleteMapping("/destination({id}") public ResponseEntity deleteDestination(@PathVariable Integer id) { - destinationService.deleteDestination(id); + destinationService.deleteDestinationById(id, false); return ResponseEntity.ok().build(); } @PutMapping("/supplier") - public ResponseEntity> setSupplier(SetDataDTO setSupplierDTO) { - return ResponseEntity.ok(premisesServices.setSupplier(setSupplierDTO)); + public ResponseEntity> setSupplier(@RequestBody SetDataDTO setSupplierDTO) { + return ResponseEntity.ok(changeSupplierService.setSupplier(setSupplierDTO)); } @PutMapping("/material") public ResponseEntity> setMaterial(SetDataDTO setMaterialDTO) { - return ResponseEntity.ok(premisesServices.setMaterial(setMaterialDTO)); + return ResponseEntity.ok(changeMaterialService.setMaterial(setMaterialDTO)); } } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java b/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java index 2d65fb2..78aab07 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/CountryController.java @@ -3,7 +3,7 @@ package de.avatic.lcc.controller.configuration; import de.avatic.lcc.dto.configuration.countries.view.CountryDetailDTO; import de.avatic.lcc.dto.generic.CountryDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; -import de.avatic.lcc.service.configuration.CountryService; +import de.avatic.lcc.service.access.CountryService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java b/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java index ce25255..c566487 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/MaterialController.java @@ -1,11 +1,9 @@ package de.avatic.lcc.controller.configuration; import de.avatic.lcc.dto.configuration.material.view.MaterialDetailDTO; -import de.avatic.lcc.dto.configuration.material.update.MaterialUpdateDTO; import de.avatic.lcc.dto.generic.MaterialDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; -import de.avatic.lcc.service.configuration.MaterialService; -import de.avatic.lcc.util.Check; +import de.avatic.lcc.service.access.MaterialService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java index 1751b7c..ca2ea7b 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java @@ -1,16 +1,15 @@ package de.avatic.lcc.controller.configuration; import de.avatic.lcc.dto.configuration.nodes.userNodes.LocateNodeDTO; -import de.avatic.lcc.dto.generic.LocationDTO; import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.configuration.nodes.view.NodeDetailDTO; import de.avatic.lcc.dto.configuration.nodes.update.NodeUpdateDTO; import de.avatic.lcc.dto.generic.NodeType; import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; -import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.service.GeoApiService; -import de.avatic.lcc.service.configuration.NodeService; +import de.avatic.lcc.service.access.NodeService; +import de.avatic.lcc.service.access.UserNodeService; import de.avatic.lcc.util.Check; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -23,11 +22,12 @@ public class NodeController { private final NodeService nodeService; private final GeoApiService geoApiService; + private final UserNodeService userNodeService; - public NodeController(NodeService nodeService, GeoApiService geoApiService) { + public NodeController(NodeService nodeService, GeoApiService geoApiService, UserNodeService userNodeService) { this.nodeService = nodeService; this.geoApiService = geoApiService; - + this.userNodeService = userNodeService; } @GetMapping("/") @@ -71,7 +71,7 @@ public class NodeController { @PutMapping("/") public ResponseEntity addUserNode(@RequestParam AddUserNodeDTO node) { - nodeService.addUserNode(node); + userNodeService.addUserNode(node); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java b/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java index 860bd76..600ada4 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/PropertyController.java @@ -3,8 +3,8 @@ package de.avatic.lcc.controller.configuration; import de.avatic.lcc.dto.generic.ValidityPeriodDTO; import de.avatic.lcc.dto.generic.PropertyDTO; import de.avatic.lcc.model.country.IsoCode; -import de.avatic.lcc.service.configuration.CountryService; -import de.avatic.lcc.service.configuration.PropertyService; +import de.avatic.lcc.service.access.CountryService; +import de.avatic.lcc.service.access.PropertyService; import de.avatic.lcc.service.configuration.PropertyApprovalService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/de/avatic/lcc/controller/configuration/RateController.java b/src/main/java/de/avatic/lcc/controller/configuration/RateController.java index f968ba9..2397e86 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/RateController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/RateController.java @@ -4,10 +4,10 @@ import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO; import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO; import de.avatic.lcc.dto.generic.ValidityPeriodDTO; import de.avatic.lcc.repositories.pagination.SearchQueryResult; -import de.avatic.lcc.service.configuration.ContainerRateService; -import de.avatic.lcc.service.configuration.MatrixRateService; +import de.avatic.lcc.service.access.ContainerRateService; +import de.avatic.lcc.service.access.MatrixRateService; import de.avatic.lcc.service.configuration.RateApprovalService; -import de.avatic.lcc.service.configuration.ValidityPeriodService; +import de.avatic.lcc.service.access.ValidityPeriodService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/de/avatic/lcc/controller/custom/CustomController.java b/src/main/java/de/avatic/lcc/controller/custom/CustomController.java index 592aaaf..85e59ed 100644 --- a/src/main/java/de/avatic/lcc/controller/custom/CustomController.java +++ b/src/main/java/de/avatic/lcc/controller/custom/CustomController.java @@ -1,6 +1,6 @@ package de.avatic.lcc.controller.custom; -import de.avatic.lcc.service.calculation.CustomApiService; +import de.avatic.lcc.service.CustomApiService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/de/avatic/lcc/dto/calculation/edit/SetDataDTO.java b/src/main/java/de/avatic/lcc/dto/calculation/edit/SetDataDTO.java index 2fcc561..5c2e610 100644 --- a/src/main/java/de/avatic/lcc/dto/calculation/edit/SetDataDTO.java +++ b/src/main/java/de/avatic/lcc/dto/calculation/edit/SetDataDTO.java @@ -15,9 +15,19 @@ public class SetDataDTO { @JsonProperty("supplier_node_id", required = false) Integer supplierNodeId; + @JsonProperty("is_user_supplier_node") + boolean isUserSupplierNode; + @JsonProperty(value = "material_id", required = false) Integer materialId; + public boolean isUserSupplierNode() { + return isUserSupplierNode; + } + + public void setUserSupplierNode(boolean userSupplierNode) { + isUserSupplierNode = userSupplierNode; + } public Integer getMaterialId() { return materialId; diff --git a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationUpdateDTO.java b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationUpdateDTO.java index a25fd11..5f1e795 100644 --- a/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationUpdateDTO.java +++ b/src/main/java/de/avatic/lcc/dto/calculation/edit/destination/DestinationUpdateDTO.java @@ -1,4 +1,47 @@ package de.avatic.lcc.dto.calculation.edit.destination; +import com.fasterxml.jackson.annotation.JsonProperty; + public class DestinationUpdateDTO { + + @JsonProperty("repacking_cost") + private Number repackingCost; + @JsonProperty("handling_cost") + private Number handlingCost; + @JsonProperty("disposal_cost") + private Number disposalCost; + @JsonProperty("route_selected_id") + private Integer routeSelectedId; + + public Number getRepackingCost() { + return repackingCost; + } + + public void setRepackingCost(Number repackingCost) { + this.repackingCost = repackingCost; + } + + public Number getHandlingCost() { + return handlingCost; + } + + public void setHandlingCost(Number handlingCost) { + this.handlingCost = handlingCost; + } + + public Number getDisposalCost() { + return disposalCost; + } + + public void setDisposalCost(Number disposalCost) { + this.disposalCost = disposalCost; + } + + public Integer getRouteSelectedId() { + return routeSelectedId; + } + + public void setRouteSelectedId(Integer routeSelectedId) { + this.routeSelectedId = routeSelectedId; + } } diff --git a/src/main/java/de/avatic/lcc/model/properties/PackagingPropertyMappingId.java b/src/main/java/de/avatic/lcc/model/properties/PackagingPropertyMappingId.java new file mode 100644 index 0000000..65ac3eb --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/properties/PackagingPropertyMappingId.java @@ -0,0 +1,5 @@ +package de.avatic.lcc.model.properties; + +public enum PackagingPropertyMappingId { + STACKABLE, MIXABLE, RUST_PREVENTION +} diff --git a/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java b/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java new file mode 100644 index 0000000..f2a35ec --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java @@ -0,0 +1,87 @@ +package de.avatic.lcc.model.properties; + +public enum SystemPropertyMappingId { + + WORKDAYS("Annual working days", "210"), + INTEREST_RATE("Interest rate inventory [%]", "12%"), + FCA_FEE("FCA fee [%]", "0,20%"), + TARIFF_RATE("Default customs duty [%]", "3,0%"), + CUSTOM_FEE("Customs clearance fee per import & HS code", "35,00 €"), + REPORTING("Standard-Report Format", "MEK_B"), + + + FEU("40'", "Yes"), + TEU("20'", "Yes"), + FEU_HQ("40'HC", "Yes"), + TEU_HQ("20'HC", "No"), + + + CONTAINER_UTIL("Container utilization in mixed containers [%]", "70%"), + TRUCK_UTIL("Truck utilization road transport EMEA [%]", "70%"), + VALID_DAYS("Max validity of container freight rates [days]", "60"), + RADIUS_REGION("Metropolition region size (diameter) [km]", "20"), + FREQ_MIN("Min delivery frequency / year for containtrer transports", "3"), + FREQ_MAX("Max delivery frequency / year for containtrer transports", "50"), + TEU_LOAD("Max load of 20' container [kg]", "20000"), + FEU_LOAD("Max load of 40' container [kg]", "21000"), + + + AIR_PRECARRIAGE("Pre-carriage [EUR/kg]", "0,10 €"), + AIR_HANDLING("Pre-carriage handling [EUR]", "80,00 €"), + AIR_MAINCARRIAGE("Main carriage [EUR/kg]", "3,50 €"), + AIR_HANDOVER_FEE("Hand over fee [EUR]", "35,00 €"), + AIR_CUSTOM_FEE("Customs clearance fee [EUR]", "45,00 €"), + AIR_ONCARRIAGE("On-carriage [EUR/kg]", "0,20 €"), + AIR_TERMINAL_FEE("Terminal handling fee [EUR/kg]", "0,20 €"), + + + KLT_HANDLING("GR handling KLT [EUR/HU]", "0,71 €"), + GLT_HANDLING("GR handling GLT [EUR/HU]", "3,50 €"), + BOOKING("GLT/KLT booking & document handling [EUR/GR]", "3,50 €"), + GLT_RELEASE("GLT release from storage [EUR/GLT release]", "2,23 €"), + KLT_RELEASE("KLT release from storage [EUR/KLT release]", "1,12 €"), + GLT_DISPATCH("GLT dispatch [EUR/GLT dispatch]", "1,61 €"), + KLT_DISPATCH("KLT dispacth [EUR/KLT dispatch]", "0,33 €"), + KLT_REPACK_S("Repacking KLT, HU <15kg [EUR/HU]", "2,08 €"), + KLT_REPACK_M("Repacking KLT, HU >=15kg [EUR/HU]", "3,02 €"), + GLT_REPACK_S("Repacking GLT, HU <15kg [EUR/HU]", "3,02 €"), + GLT_REPACK_M("Repacking GLT, HU 15 - 2000kg [EUR/HU]", "7,76 €"), + GLT_REPACK_L("Repacking GLT, HU > 2000kg [EUR/HU]", "14,00 €"), + DISPOSAL("GLT disposal [EUR/GLT]", "6,00 €"), + + + SPACE_COST("Space costs / m3 per night [EUR/m3]", "0,26 €"), + + + UNION("Customs Union", ""), + SAFTY_STOCK("Safety Stock [working days]", "30"), + AIR_SHARE("Air freight share [%]", "2"), + WAGE("Wage factor [%]", "100%"); + + private final String description; + private final String defaultValue; + + + SystemPropertyMappingId(String description, String defaultValue) { + this.description = description; + this.defaultValue = defaultValue; + } + + + public String getDescription() { + return description; + } + + + public String getDefaultValue() { + return defaultValue; + } + + public Integer getDefaultAsInteger() { + try { + return Integer.parseInt(defaultValue); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java index d3d19df..b63ce0a 100644 --- a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java @@ -59,7 +59,9 @@ public class NodeRepository { return predecessors; }, id); }, id); - }; + } + + ; private Collection getOutboundCountriesOf(Integer id) { String query = """ @@ -107,7 +109,7 @@ public class NodeRepository { private String buildQuery(String filter, Boolean excludeDeprecated, SearchQueryPagination searchQueryPagination) { StringBuilder queryBuilder = new StringBuilder(""" SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source, - node.is_sink as is_sink, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required, + node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required, country.iso_code AS country_iso_code, country.region_code AS country_region_code, country.name AS country_name, country.is_deprecated AS country_is_deprecated FROM node LEFT JOIN country ON country.id = node.country_id @@ -182,6 +184,91 @@ public class NodeRepository { return Optional.ofNullable(node); } + + /** + * Resolves chains of predecessors for a specified destination node by its ID. + * If the destination node does not require predecessors, an empty list is returned. + * Otherwise, it constructs a list of chains, where each chain represents a sequence of predecessor nodes. + * + * @param destinationId The ID of the destination node whose predecessor chains need to be resolved. + * Must not be null and must correspond to an existing node. + * @return A list of chains, where each chain is a list of nodes. + * Each list represents a sequence of predecessor nodes for the given destination node. + * If the destination node does not require predecessors, a list containing an empty list is returned. + * @throws RuntimeException If a predecessor node is not found for a given sequence number in the chain. + */ + @Transactional + public List> resolveChainsById(Integer destinationId) { + List> resolvedChains = new ArrayList<>(); + + Node destination = getById(destinationId).orElseThrow(); + + if (!destination.getPredecessorRequired()) + resolvedChains.add(Collections.emptyList()); + + List> chains = destination.getNodePredecessors(); + + chains.forEach(chain -> { + + var currentChain = new ArrayList(); + resolvedChains.add(currentChain); + + chain.keySet().forEach(sequenceNumber -> { + var predecessor = getById(chain.get(sequenceNumber)); + if (predecessor.isEmpty()) { + throw new RuntimeException("Predecessor not found for chain " + chain + " and sequence number " + sequenceNumber); + } + currentChain.add(sequenceNumber, predecessor.get()); + }); + + }); + + return resolvedChains; + } + + public List getByDistance(Node node, Integer regionRadius) { + + String query = """ + SELECT * FROM node + WHERE is_deprecated = FALSE AND + ( + 6371 * acos( + cos(radians(?)) * + cos(radians(geo_lat)) * + cos(radians(geo_lng) - radians(?)) + + sin(radians(?)) * + sin(radians(geo_lat)) + ) + ) <= ? + """; + + return jdbcTemplate.query(query, new NodeMapper(), node.getGeoLat(), node.getGeoLng(), node.getGeoLat()); + } + + + + /** + * Retrieves a list of nodes that are outbound from a given country. + * The method considers nodes that are either explicitly mapped to the given country + * or are intermediate nodes within the same country. + * + * @param countryId The ID of the country for which outbound nodes are to be retrieved. + * Must not be null. + * @return A list of nodes that are outbound for the specified country. + * Returns an empty list if no outbound nodes are found. + */ + public List getAllOutboundFor(Integer countryId) { + String query = """ + SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source, + node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required + FROM node + LEFT JOIN outbound_country_mapping ON outbound_country_mapping.node_id = node.id + WHERE node.is_deprecated = FALSE AND (outbound_country_mapping.country_id = ? OR (node.is_intermediate = TRUE AND node.country_id = ?)) + """; + + return jdbcTemplate.query(query, new NodeMapper(), countryId); + } + private class NodeMapper implements RowMapper { @Override diff --git a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java index cbbfa0d..8063828 100644 --- a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java @@ -1,12 +1,17 @@ package de.avatic.lcc.repositories.packaging; +import com.azure.spring.cloud.core.implementation.properties.PropertyMapper; import de.avatic.lcc.model.properties.PackagingProperty; import de.avatic.lcc.model.properties.PropertyDataType; import de.avatic.lcc.model.properties.PropertyType; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; +import java.util.Optional; @Repository public class PackagingPropertiesRepository { @@ -25,8 +30,30 @@ public class PackagingPropertiesRepository { type.external_mapping_id AS type_external_mapping_id, type.validation_rule AS type_validation_rule FROM packaging_property property LEFT JOIN packaging_property_type type ON property.packaging_property_type_id = type.id WHERE packaging_id = ?"""; - return jdbcTemplate.query(query, (rs, rowNum) -> { + return jdbcTemplate.query(query, new PropertyMapper(), id); + } + + public Optional getByPackagingIdAndType(Integer id, String type) { + String query = """ + SELECT property.id as id, property.property_value AS value, property.packaging_id as packaging_id, + type.name AS type_name, type.data_type AS type_data_type, type.is_required AS type_is_required, + type.external_mapping_id AS type_external_mapping_id, type.validation_rule AS type_validation_rule + FROM packaging_property property LEFT JOIN packaging_property_type type ON property.packaging_property_type_id = type.id WHERE packaging_id = ? AND type.external_mapping_id = ?"""; + + var property = jdbcTemplate.query(query, new PropertyMapper(), id, type); + + if(property.isEmpty()) + return Optional.empty(); + + return Optional.of(property.getFirst()); + + } + + + private static class PropertyMapper implements RowMapper { + @Override + public PackagingProperty mapRow(ResultSet rs, int rowNum) throws SQLException { PackagingProperty property = new PackagingProperty(); PropertyType type = new PropertyType(); @@ -43,12 +70,10 @@ public class PackagingPropertiesRepository { property.setValue(rs.getString("value")); return property; - }, id); - + } } - public List listTypes() { String query = """ SELECT * FROM packaging_property_type diff --git a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java index 842be84..f93ce8c 100644 --- a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java @@ -180,4 +180,13 @@ public class PackagingRepository { return jdbcTemplate.query(query, new PackagingMapper(), materialId); } + + public List getByMaterialIdAndSupplierId(Integer materialId, Integer supplierId) { + String query = """ + SELECT id, supplier_node_id, material_id, hu_dimension_id, shu_dimension_id, is_deprecated + FROM packaging + WHERE packaging.material_id = ? AND packaging.supplier_node_id = ?"""; + + return jdbcTemplate.query(query, new PackagingMapper(), materialId, supplierId); + } } diff --git a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java index ce526da..a921c80 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java @@ -3,6 +3,7 @@ package de.avatic.lcc.repositories.premise; import de.avatic.lcc.model.premises.route.Destination; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; import java.sql.ResultSet; @@ -17,7 +18,7 @@ public class DestinationRepository { public DestinationRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; - } + } public Optional getById(Integer id) { String query = "SELECT * FROM premise_destination WHERE id = ?"; @@ -37,6 +38,39 @@ public class DestinationRepository { return jdbcTemplate.query(query, new DestinationMapper(), id); } + public void update(Integer id, Integer userId, Number repackingCost, Number disposalCost, Number handlingCost) { + String query = "UPDATE premise_destination SET repacking_cost = ?, disposal_cost = ?, handling_cost = ? WHERE id = ? AND user_id = ?"; + //TODO + } + + + public void deleteById(Integer id) { + if (id == null) { + return; + } + + String sql = "DELETE FROM premise_destination WHERE id = ?"; + jdbcTemplate.update(sql, id); + } + + public Optional getOwnerIdById(Integer id) { + String query = """ + SELECT p.user_id AS user_id + FROM premise_destination pd + JOIN premise p ON pd.premise_id = p.id + WHERE pd.id = ?"""; + + List userId = jdbcTemplate.query(query, (rs, rowNum) -> { + return rs.getInt("userId"); + }, id); + + if (userId.isEmpty()) + return Optional.empty(); + else + return Optional.of(userId.getFirst()); + } + + private static class DestinationMapper implements RowMapper { @Override public Destination mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/main/java/de/avatic/lcc/repositories/premise/PremiseRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/PremiseRepository.java index 483dda0..24ecc09 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/PremiseRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/PremiseRepository.java @@ -11,6 +11,8 @@ import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -25,89 +27,87 @@ import java.util.Optional; public class PremiseRepository { private final JdbcTemplate jdbcTemplate; + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - public PremiseRepository(JdbcTemplate jdbcTemplate) { + public PremiseRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; } - public SearchQueryResult listPremises(String filter, SearchQueryPagination pagination, Integer userId, Boolean deleted, Boolean archived, Boolean done) { - String query = buildQuery(filter, deleted, archived, done); - String countQuery = buildCountQuery(filter, deleted, archived, done); + public SearchQueryResult listPremises(String filter, SearchQueryPagination pagination, + Integer userId, Boolean deleted, Boolean archived, Boolean done) { - List entities = null; - Integer totalCount = 0; + QueryBuilder queryBuilder = new QueryBuilder() + .withFilter(filter) + .withDeleted(deleted) + .withArchived(archived) + .withDone(done); - if (filter == null || filter.isBlank()) { - entities = jdbcTemplate.query(query, new PremiseListEntryMapper(), userId, pagination.getLimit(), pagination.getOffset()); - totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class, userId); + String query = queryBuilder.buildSelectQuery(); + String countQuery = queryBuilder.buildCountQuery(); + + List entities; + Integer totalCount; + + if (isEmptyFilter(filter)) { + entities = executeQueryWithoutFilter(query, userId, pagination); + totalCount = executeCountQueryWithoutFilter(countQuery, userId); } else { - entities = jdbcTemplate.query(query, new PremiseListEntryMapper(), userId, "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", pagination.getLimit(), pagination.getOffset()); - totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class, userId, "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%"); + entities = executeQueryWithFilter(query, userId, filter, pagination); + totalCount = executeCountQueryWithFilter(countQuery, userId, filter); } return new SearchQueryResult<>(entities, pagination.getPage(), totalCount, pagination.getLimit()); } - private String buildCountQuery(String filter, Boolean deleted, Boolean archived, Boolean done) { - StringBuilder queryBuilder = new StringBuilder(); - queryBuilder.append(""" - SELECT COUNT(*) FROM premise AS p - LEFT JOIN material as m ON p.material_id = m.id - LEFT JOIN node as n ON p.supplier_node_id = n.id - LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id - WHERE p.userId = ?"""); - - if (filter != null && !filter.isBlank()) { - queryBuilder.append(" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)"); - } - - if (deleted != null && deleted) { - queryBuilder.append(" AND p.deleted = TRUE"); - } - - if (archived != null && archived) { - queryBuilder.append(" AND p.archived = TRUE"); - } - - if (done != null && done) { - queryBuilder.append(" AND p.done = TRUE"); - } - - return queryBuilder.toString(); + private boolean isEmptyFilter(String filter) { + return filter == null || filter.isBlank(); } + private List executeQueryWithoutFilter(String query, Integer userId, + SearchQueryPagination pagination) { + return jdbcTemplate.query( + query, + new PremiseListEntryMapper(), + userId, + pagination.getLimit(), + pagination.getOffset() + ); + } - private String buildQuery(String filter, Boolean deleted, Boolean archived, Boolean done) { + private Integer executeCountQueryWithoutFilter(String countQuery, Integer userId) { + return jdbcTemplate.queryForObject(countQuery, Integer.class, userId); + } - StringBuilder queryBuilder = new StringBuilder(); + private List executeQueryWithFilter(String query, Integer userId, + String filter, SearchQueryPagination pagination) { + String wildcardFilter = "%" + filter + "%"; + Object[] params = createFilterParams(userId, wildcardFilter, pagination); + return jdbcTemplate.query(query, new PremiseListEntryMapper(), params); + } - queryBuilder.append(""" - SELECT * FROM premise AS p - LEFT JOIN material as m ON p.material_id = m.id - LEFT JOIN node as n ON p.supplier_node_id = n.id - LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id - WHERE p.userId = ?"""); + private Integer executeCountQueryWithFilter(String countQuery, Integer userId, String filter) { + String wildcardFilter = "%" + filter + "%"; + Object[] params = createFilterParamsForCount(userId, wildcardFilter); + return jdbcTemplate.queryForObject(countQuery, Integer.class, params); + } - if (filter != null && !filter.isBlank()) { - queryBuilder.append(" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)"); - } - if (deleted != null && deleted) { - queryBuilder.append(" AND p.deleted = TRUE"); - } + private Object[] createFilterParams(Integer userId, String wildcardFilter, SearchQueryPagination pagination) { + return new Object[]{ + userId, + wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter, + wildcardFilter, wildcardFilter, wildcardFilter, + pagination.getLimit(), pagination.getOffset() + }; + } - if (archived != null && archived) { - queryBuilder.append(" AND p.archived = TRUE"); - } - - if (done != null && done) { - queryBuilder.append(" AND p.done = TRUE"); - } - - queryBuilder.append(" LIMIT ? OFFSET ?"); - queryBuilder.append(" ORDER BY p.updated_at DESC"); - - return queryBuilder.toString(); + private Object[] createFilterParamsForCount(Integer userId, String wildcardFilter) { + return new Object[]{ + userId, + wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter, + wildcardFilter, wildcardFilter, wildcardFilter + }; } @@ -148,53 +148,48 @@ public class PremiseRepository { } } - - @Transactional public void updatePackaging(List premiseIds, Integer userId, PackagingDimension dimensionEntity, Boolean stackable, Boolean mixable) { - String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?")); + if (premiseIds == null || premiseIds.isEmpty() || userId == null || dimensionEntity == null) { + return; + } - String query = """ + boolean isStackable = stackable != null ? stackable : false; + boolean isMixable = mixable != null ? mixable : false; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("length", dimensionEntity.getLength()); + params.addValue("height", dimensionEntity.getHeight()); + params.addValue("width", dimensionEntity.getWidth()); + params.addValue("weight", dimensionEntity.getWeight()); + params.addValue("dimensionUnit", dimensionEntity.getDimensionUnit().name()); + params.addValue("weightUnit", dimensionEntity.getWeightUnit().name()); + params.addValue("unitCount", dimensionEntity.getContentUnitCount()); + params.addValue("stackable", isStackable); + params.addValue("mixable", isMixable); + params.addValue("userId", userId); + params.addValue("premiseIds", premiseIds); + + String sql = """ UPDATE premise - SET individual_hu_length = ?, - individual_hu_height = ?, - individual_hu_width = ?, - individual_hu_weight = ?, - hu_displayed_dimension_unit = ?, - hu_displayed_weight_unit = ?, - hu_unit_count = ?, - hu_stackable = ?, - hu_mixable = ?, - WHERE user_id = ? AND id IN (""" + placeholders + ")" ; - - jdbcTemplate.update( - query, - ps -> { - ps.setInt(1, dimensionEntity.getLength()); - ps.setInt(2, dimensionEntity.getHeight()); - ps.setInt(3, dimensionEntity.getWidth()); - ps.setInt(4, dimensionEntity.getWeight()); - ps.setString(5, dimensionEntity.getDimensionUnit().name()); - ps.setString(6, dimensionEntity.getWeightUnit().name()); - ps.setInt(7, dimensionEntity.getContentUnitCount()); - - ps.setBoolean(8, stackable); - ps.setBoolean(9, mixable); - ps.setInt(10, userId); - - for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) { - ps.setInt(parameterIndex+10, premiseIds.get(parameterIndex)); - } - - - } - ); + SET individual_hu_length = :length, + individual_hu_height = :height, + individual_hu_width = :width, + individual_hu_weight = :weight, + hu_displayed_dimension_unit = :dimensionUnit, + hu_displayed_weight_unit = :weightUnit, + hu_unit_count = :unitCount, + hu_stackable = :stackable, + hu_mixable = :mixable + WHERE user_id = :userId AND id IN (:premiseIds) + """; + namedParameterJdbcTemplate.update(sql, params); } - public void updateMaterial(List premiseIds, Integer userId, String hsCode, BigDecimal tariffRate) { + public void updateMaterial(List premiseIds, Integer userId, String hsCode, BigDecimal tariffRate) { String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?")); @@ -202,7 +197,7 @@ public class PremiseRepository { UPDATE premise SET hs_code = ?, tariff_rate = ? - WHERE user_id = ? id IN ("""+placeholders+")"; + WHERE user_id = ? AND id IN (""" + placeholders + ")"; jdbcTemplate.update( query, @@ -212,8 +207,8 @@ public class PremiseRepository { ps.setBigDecimal(2, tariffRate); ps.setInt(3, userId); - for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) { - ps.setInt(parameterIndex+3, premiseIds.get(parameterIndex)); + for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) { + ps.setInt(parameterIndex + 4, premiseIds.get(parameterIndex)); } } ); @@ -227,7 +222,7 @@ public class PremiseRepository { SET price = ?, is_fca_enabled = ?, oversea_share = ? - WHERE user_id = ? AND id IN ("""+placeholders+")"; + WHERE user_id = ? AND id IN (""" + placeholders + ")"; jdbcTemplate.update( query, @@ -238,61 +233,171 @@ public class PremiseRepository { ps.setBigDecimal(3, overseaShare); ps.setInt(4, userId); - for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) { - ps.setInt(parameterIndex+4, premiseIds.get(parameterIndex)); + for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) { + ps.setInt(parameterIndex + 6, premiseIds.get(parameterIndex)); } } ); } + + /** + * Retrieves all draft premises that would conflict with updating the material or supplier for a given premise. + *

+ * A conflict arises when multiple premises share the same combination of material ID and supplier ID while + * being in the DRAFT state. This method enables the identification of such potential duplicates, excluding + * the premise being evaluated to avoid self-collision. + * + * @param userId The ID of the user who owns the premises. + * @param premiseId The ID of the premise being checked; this premise will be excluded from the results. + * @param materialId The material ID to verify for potential conflicts. + * @param supplierId The supplier node ID to verify for potential conflicts. + * @return A list of premises in the DRAFT state with the same material and supplier combination (excluding the specified premise). + * @throws IllegalArgumentException If any of the provided parameters are null. + */ + public List getCollidingPremisesOnChange(Integer userId, Integer premiseId, Integer materialId, Integer supplierId) { + + if( premiseId == null || materialId == null || supplierId == null ) + return Collections.emptyList(); + + String sql = "SELECT * FROM premise " + + "WHERE material_id = ? " + + "AND supplier_node_id = ? " + + "AND id != ? " + + "AND user_id = ? " + + "AND state = 'DRAFT'"; + + return jdbcTemplate.query(sql, new PremiseMapper(), materialId, supplierId, premiseId, userId); + } + + public void deletePremisesById(List premiseIds) { + if(premiseIds == null || premiseIds.isEmpty()) return; + + String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?")); + String query = """ + DELETE FROM premise + WHERE id IN (""" + placeholders + ") AND state = " + PremiseState.DRAFT.name(); + + jdbcTemplate.update(query, premiseIds.toArray()); + } + + public void updateTariffRate(Integer premiseId, Number tariffRate) { + String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?"; + + jdbcTemplate.update(sql, tariffRate, premiseId); + } + + public void setSupplierId(List premiseId, Integer supplierId, boolean userSupplierNode) { + String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?")); + String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ? WHERE id IN ("+placeholders+")"; + + if(userSupplierNode) { + jdbcTemplate.update(sql, 0, supplierId, premiseId.toArray()); + } else { + jdbcTemplate.update(sql, supplierId, 0, premiseId.toArray()); + } + } + + /** + * Encapsulates SQL query building logic + */ + private static class QueryBuilder { + private static final String BASE_JOIN_QUERY = """ + FROM premise AS p + LEFT JOIN material as m ON p.material_id = m.id + LEFT JOIN node as n ON p.supplier_node_id = n.id + LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id + WHERE p.userId = ?"""; + + private static final String FILTER_CONDITION = + " AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR " + + "user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)"; + + private String filter; + private Boolean deleted; + private Boolean archived; + private Boolean done; + + public QueryBuilder withFilter(String filter) { + this.filter = filter; + return this; + } + + public QueryBuilder withDeleted(Boolean deleted) { + this.deleted = deleted; + return this; + } + + public QueryBuilder withArchived(Boolean archived) { + this.archived = archived; + return this; + } + + public QueryBuilder withDone(Boolean done) { + this.done = done; + return this; + } + + public String buildCountQuery() { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("SELECT COUNT(*) ").append(BASE_JOIN_QUERY); + appendConditions(queryBuilder); + return queryBuilder.toString(); + } + + public String buildSelectQuery() { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("SELECT * ").append(BASE_JOIN_QUERY); + appendConditions(queryBuilder); + queryBuilder.append(" ORDER BY p.updated_at DESC"); + queryBuilder.append(" LIMIT ? OFFSET ?"); + return queryBuilder.toString(); + } + + private void appendConditions(StringBuilder queryBuilder) { + if (filter != null && !filter.isBlank()) { + queryBuilder.append(FILTER_CONDITION); + } + + appendBooleanCondition(queryBuilder, deleted, "p.deleted"); + appendBooleanCondition(queryBuilder, archived, "p.archived"); + appendBooleanCondition(queryBuilder, done, "p.done"); + } + + private void appendBooleanCondition(StringBuilder queryBuilder, Boolean condition, String field) { + if (condition != null && condition) { + queryBuilder.append(" AND ").append(field).append(" = TRUE"); + } + } + } + private static class PremiseListEntryMapper implements RowMapper { @Override public PremiseListEntry mapRow(ResultSet rs, int rowNum) throws SQLException { - PremiseListEntry entity = new PremiseListEntry(); - entity.setId(rs.getInt("p.id")); - entity.setState(de.avatic.lcc.dto.calculation.PremiseState.valueOf(rs.getString("p.state"))); - entity.setOwnerId(rs.getInt("p.user_id")); - entity.setCreatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); - entity.setUpdatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); + // Map base premise properties + mapBasePremiseProperties(entity, rs); + // Map material entity.setMaterial(mapMaterial(rs)); - if (rs.getInt("p.supplier_node_id") != 0) { - entity.setSupplierId(rs.getInt("n.id")); - entity.setSupplierName(rs.getString("n.name")); - entity.setSupplierAddress(rs.getString("n.address")); - entity.setSupplierCountryId(rs.getInt("n.country_id")); - entity.setUserSupplier(false); - - entity.setSupplierGeoLatitude(rs.getBigDecimal("n.geo_latitude")); - entity.setSupplierGeoLongitude(rs.getBigDecimal("n.geo_longitude")); - - entity.setSupplierIsDestination(rs.getBoolean("n.is_destination")); - entity.setSupplierIsIntermediate(rs.getBoolean("n.is_intermediate")); - entity.setSupplierIsSource(rs.getBoolean("n.is_source")); - - } else if (rs.getInt("p.user_supplier_node_id") != 0) { - entity.setSupplierId(rs.getInt("user_n.id")); - entity.setSupplierName(rs.getString("user_n.name")); - entity.setSupplierAddress(rs.getString("user_n.address")); - entity.setSupplierCountryId(rs.getInt("user_n.country_id")); - entity.setUserSupplier(false); - - entity.setSupplierGeoLatitude(rs.getBigDecimal("user_n.geo_latitude")); - entity.setSupplierGeoLongitude(rs.getBigDecimal("user_n.geo_longitude")); - - entity.setSupplierIsDestination(rs.getBoolean("user_n.is_destination")); - entity.setSupplierIsIntermediate(rs.getBoolean("user_n.is_intermediate")); - entity.setSupplierIsSource(rs.getBoolean("user_n.is_source")); - } + // Map supplier (either regular node or user node) + mapSupplierProperties(entity, rs); return entity; } + private void mapBasePremiseProperties(PremiseListEntry entity, ResultSet rs) throws SQLException { + entity.setId(rs.getInt("p.id")); + entity.setState(de.avatic.lcc.dto.calculation.PremiseState.valueOf(rs.getString("p.state"))); + entity.setOwnerId(rs.getInt("p.user_id")); + entity.setCreatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); + entity.setUpdatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); // Note: This looks like a bug, should be "p.updated_at" + } + private Material mapMaterial(ResultSet rs) throws SQLException { - var data = new Material(); + Material data = new Material(); data.setId(rs.getInt("m.id")); data.setName(rs.getString("m.name")); @@ -302,6 +407,29 @@ public class PremiseRepository { return data; } + + private void mapSupplierProperties(PremiseListEntry entity, ResultSet rs) throws SQLException { + if (rs.getInt("p.supplier_node_id") != 0) { + mapSupplier(entity, rs, "n"); + } else if (rs.getInt("p.user_supplier_node_id") != 0) { + mapSupplier(entity, rs, "user_n"); + entity.setUserSupplier(true); + } + } + + private void mapSupplier(PremiseListEntry entity, ResultSet rs, String tablePrefix) throws SQLException { + entity.setSupplierId(rs.getInt(tablePrefix + ".id")); + entity.setSupplierName(rs.getString(tablePrefix + ".name")); + entity.setSupplierAddress(rs.getString(tablePrefix + ".address")); + entity.setSupplierCountryId(rs.getInt(tablePrefix + ".country_id")); + + entity.setSupplierGeoLatitude(rs.getBigDecimal(tablePrefix + ".geo_latitude")); + entity.setSupplierGeoLongitude(rs.getBigDecimal(tablePrefix + ".geo_longitude")); + + entity.setSupplierIsDestination(rs.getBoolean(tablePrefix + ".is_destination")); + entity.setSupplierIsIntermediate(rs.getBoolean(tablePrefix + ".is_intermediate")); + entity.setSupplierIsSource(rs.getBoolean(tablePrefix + ".is_source")); + } } private static class PremiseMapper implements RowMapper { diff --git a/src/main/java/de/avatic/lcc/repositories/premise/RouteNodeRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/RouteNodeRepository.java index 37f7b13..9f2ac89 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/RouteNodeRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/RouteNodeRepository.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -31,6 +32,17 @@ public class RouteNodeRepository { return Optional.of(nodes.getFirst()); } + public void deleteAllById(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + + String placeholders = String.join(",", Collections.nCopies(ids.size(), "?")); + String sql = "DELETE FROM premise_route_node WHERE id IN (" + placeholders + ")"; + + jdbcTemplate.update(sql, ids.toArray(new Object[0])); + } + private static class RouteNodeMapper implements RowMapper { @Override diff --git a/src/main/java/de/avatic/lcc/repositories/premise/RouteRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/RouteRepository.java index 75a3445..fe8f5fd 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/RouteRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/RouteRepository.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.List; @Repository @@ -23,6 +24,18 @@ public class RouteRepository { return jdbcTemplate.query(query, new RouteMapper(), id); } + public void deleteAllById(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + + // Create a parameterized SQL statement with placeholders for each ID + String placeholders = String.join(",", Collections.nCopies(ids.size(), "?")); + String sql = "DELETE FROM premise_route WHERE id IN (" + placeholders + ")"; + + jdbcTemplate.update(sql, ids.toArray(new Object[0])); + } + private static class RouteMapper implements RowMapper { @Override public Route mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java index c5099d3..91d1c4f 100644 --- a/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java @@ -2,13 +2,13 @@ package de.avatic.lcc.repositories.premise; import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.model.premises.route.RouteSection; -import de.avatic.lcc.model.premises.route.RouteSectionType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.List; @Repository @@ -27,6 +27,17 @@ public class RouteSectionRepository { return jdbcTemplate.query(query, new RouteSectionMapper(), routeId); } + public void deleteAllById(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + + String placeholders = String.join(",", Collections.nCopies(ids.size(), "?")); + String sql = "DELETE FROM premise_route_section WHERE id IN (" + placeholders + ")"; + + jdbcTemplate.update(sql, ids.toArray(new Object[0])); + } + private static class RouteSectionMapper implements RowMapper { @Override public RouteSection mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java b/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java index 141a99d..fb8dc7e 100644 --- a/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java @@ -1,6 +1,7 @@ package de.avatic.lcc.repositories.properties; import de.avatic.lcc.dto.generic.PropertyDTO; +import de.avatic.lcc.model.properties.SystemPropertyMappingId; import de.avatic.lcc.model.rates.ValidityPeriodState; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -10,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import java.util.Optional; /** @@ -96,26 +98,25 @@ public class PropertyRepository { WHERE propertySet.state = ? AND propertySet.id = ? """; - return jdbcTemplate.query(query, new PropertyMapper(),ValidityPeriodState.EXPIRED.name(), propertySetId); - } - - private static class PropertyMapper implements RowMapper { - @Override - public PropertyDTO mapRow(ResultSet rs, int rowNum) throws SQLException { - var dto = new PropertyDTO(); - - dto.setName(rs.getString("name")); - dto.setDraftValue(rs.getString("draftValue")); - dto.setCurrentValue(rs.getString("validValue")); - dto.setValidationRule(rs.getString("validationRule")); - dto.setExternalMappingId(rs.getString("externalMappingId")); - dto.setRequired(true); - dto.setDataType(rs.getString("dataType")); - - return dto; - } + return jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.EXPIRED.name(), propertySetId); } + public Optional getPropertyByMappingId(SystemPropertyMappingId mappingId) { + String query = """ + SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, + property.property_value as draftValue, property.property_value as validValue + FROM system_property_type AS type + LEFT JOIN system_property AS property ON property.system_property_type_id = type.id + LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id + WHERE propertySet.state = ? AND type.external_mapping_id = ? + """; + + var property = jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.VALID.name(), mappingId.name()); + + if (property.isEmpty()) return Optional.empty(); + else return Optional.of(property.getFirst()); + + } /** * Fills the draft property set with values from a valid property set. @@ -137,4 +138,21 @@ public class PropertyRepository { }); } + + private static class PropertyMapper implements RowMapper { + @Override + public PropertyDTO mapRow(ResultSet rs, int rowNum) throws SQLException { + var dto = new PropertyDTO(); + + dto.setName(rs.getString("name")); + dto.setDraftValue(rs.getString("draftValue")); + dto.setCurrentValue(rs.getString("validValue")); + dto.setValidationRule(rs.getString("validationRule")); + dto.setExternalMappingId(rs.getString("externalMappingId")); + dto.setRequired(true); + dto.setDataType(rs.getString("dataType")); + + return dto; + } + } } diff --git a/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java b/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java index b454ca2..a659ec6 100644 --- a/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java @@ -11,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import java.util.Optional; /** * Repository for managing {@link MatrixRate} entities, including retrieval and mapping of MatrixRate rows @@ -76,6 +77,16 @@ public class MatrixRateRepository { return jdbcTemplate.query(query, new MatrixRateMapper()); } + public Optional getByCountryIds(Integer fromCountryId, Integer toCountryId) { + String query = "SELECT * FROM country_matrix_rate WHERE from_country_id = ? AND to_country_id = ?"; + var rates = jdbcTemplate.query(query, new MatrixRateMapper(), fromCountryId, toCountryId); + + if(rates.isEmpty()) + return Optional.empty(); + else + return Optional.of(rates.getFirst()); + } + /** * Maps rows of a {@link ResultSet} to {@link MatrixRate} objects as required by * the {@link JdbcTemplate}. diff --git a/src/main/java/de/avatic/lcc/service/calculation/CustomApiService.java b/src/main/java/de/avatic/lcc/service/CustomApiService.java similarity index 81% rename from src/main/java/de/avatic/lcc/service/calculation/CustomApiService.java rename to src/main/java/de/avatic/lcc/service/CustomApiService.java index 9a2eb97..40850dc 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/CustomApiService.java +++ b/src/main/java/de/avatic/lcc/service/CustomApiService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.calculation; +package de.avatic.lcc.service; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/avatic/lcc/service/configuration/ContainerRateService.java b/src/main/java/de/avatic/lcc/service/access/ContainerRateService.java similarity index 99% rename from src/main/java/de/avatic/lcc/service/configuration/ContainerRateService.java rename to src/main/java/de/avatic/lcc/service/access/ContainerRateService.java index 15dc556..b6235a3 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/ContainerRateService.java +++ b/src/main/java/de/avatic/lcc/service/access/ContainerRateService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO; import de.avatic.lcc.dto.generic.ContainerType; diff --git a/src/main/java/de/avatic/lcc/service/configuration/CountryService.java b/src/main/java/de/avatic/lcc/service/access/CountryService.java similarity index 99% rename from src/main/java/de/avatic/lcc/service/configuration/CountryService.java rename to src/main/java/de/avatic/lcc/service/access/CountryService.java index 95e1556..8ec2153 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/CountryService.java +++ b/src/main/java/de/avatic/lcc/service/access/CountryService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.configuration.countries.view.CountryDetailDTO; import de.avatic.lcc.dto.generic.CountryDTO; diff --git a/src/main/java/de/avatic/lcc/service/access/DestinationService.java b/src/main/java/de/avatic/lcc/service/access/DestinationService.java new file mode 100644 index 0000000..11b31c2 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/access/DestinationService.java @@ -0,0 +1,100 @@ +package de.avatic.lcc.service.access; + +import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO; +import de.avatic.lcc.dto.calculation.DestinationDTO; +import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO; +import de.avatic.lcc.model.premises.route.Route; +import de.avatic.lcc.model.premises.route.RouteSection; +import de.avatic.lcc.repositories.premise.DestinationRepository; +import de.avatic.lcc.repositories.premise.RouteNodeRepository; +import de.avatic.lcc.repositories.premise.RouteRepository; +import de.avatic.lcc.repositories.premise.RouteSectionRepository; +import de.avatic.lcc.service.transformer.premise.DestinationTransformer; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@Service +public class DestinationService { + + + private final DestinationRepository destinationRepository; + private final DestinationTransformer destinationTransformer; + private final RouteRepository routeRepository; + private final RouteSectionRepository routeSectionRepository; + private final RouteNodeRepository routeNodeRepository; + + public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository) { + this.destinationRepository = destinationRepository; + this.destinationTransformer = destinationTransformer; + this.routeRepository = routeRepository; + this.routeSectionRepository = routeSectionRepository; + this.routeNodeRepository = routeNodeRepository; + } + + public HashMap createDestination(DestinationCreateDTO destinationCreateDTO) { + + // do some checks + // - no duplicates + + // do routing. + + // create database entries. + + return null; + } + + public DestinationDTO getDestination(Integer id) { + //todo check authorization + + return destinationTransformer.toDestinationDTO(destinationRepository.getById(id).orElseThrow()); + } + + public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) { + + //todo check authorization + Integer userId = 1; + + destinationRepository.update(id, userId, destinationUpdateDTO.getRepackingCost(), destinationUpdateDTO.getDisposalCost(), destinationUpdateDTO.getHandlingCost()); + } + + @Transactional + public void deleteAllDestinationsByPremiseId(List ids, boolean deleteRoutesOnly) { + ids.forEach(id -> deleteDestinationById(id, deleteRoutesOnly)); + } + + + @Transactional + public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) { + //todo check authorization + Integer userId = 1; + Optional ownerId = destinationRepository.getOwnerIdById(id); + + if(ownerId.isPresent() && ownerId.get().equals(userId)) { + List routes = routeRepository.getByDestinationId(id); + + for(var route : routes) { + List sections = routeSectionRepository.getByRouteId(route.getId()); + routeSectionRepository.deleteAllById(sections.stream().map(RouteSection::getId).toList()); + routeNodeRepository.deleteAllById(sections.stream().flatMap(section -> Stream.of(section.getFromRouteNodeId(), section.getToRouteNodeId())).toList()); + } + + routeRepository.deleteAllById(routes.stream().map(Route::getId).toList()); + + if(!deleteRoutesOnly) + destinationRepository.deleteById(id); + + return; + } + + throw new RuntimeException("Not authorized to delete destination with id " + id); + + } + + +} diff --git a/src/main/java/de/avatic/lcc/service/configuration/MaterialService.java b/src/main/java/de/avatic/lcc/service/access/MaterialService.java similarity index 95% rename from src/main/java/de/avatic/lcc/service/configuration/MaterialService.java rename to src/main/java/de/avatic/lcc/service/access/MaterialService.java index 495f80d..98c3fe5 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/MaterialService.java +++ b/src/main/java/de/avatic/lcc/service/access/MaterialService.java @@ -1,6 +1,5 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; -import de.avatic.lcc.dto.configuration.material.update.MaterialUpdateDTO; import de.avatic.lcc.dto.configuration.material.view.MaterialDetailDTO; import de.avatic.lcc.dto.generic.MaterialDTO; import de.avatic.lcc.model.materials.Material; @@ -8,7 +7,6 @@ import de.avatic.lcc.repositories.MaterialRepository; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.service.transformer.generic.MaterialTransformer; -import de.avatic.lcc.service.transformer.material.MaterialUpdateDTOTransformer; import de.avatic.lcc.service.transformer.material.MaterialDetailTransformer; import de.avatic.lcc.util.exception.clienterror.MaterialNotFoundException; import org.springframework.stereotype.Service; diff --git a/src/main/java/de/avatic/lcc/service/configuration/MatrixRateService.java b/src/main/java/de/avatic/lcc/service/access/MatrixRateService.java similarity index 98% rename from src/main/java/de/avatic/lcc/service/configuration/MatrixRateService.java rename to src/main/java/de/avatic/lcc/service/access/MatrixRateService.java index db64d0e..f5d5cfb 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/MatrixRateService.java +++ b/src/main/java/de/avatic/lcc/service/access/MatrixRateService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO; import de.avatic.lcc.model.rates.MatrixRate; @@ -20,7 +20,7 @@ import java.util.Optional; * business logic for matrix rates. */ @Service -public class MatrixRateService { +public class MatrixRateService { private final ValidityPeriodRepository validityPeriodRepository; private final MatrixRateRepository matrixRateRepository; diff --git a/src/main/java/de/avatic/lcc/service/configuration/NodeService.java b/src/main/java/de/avatic/lcc/service/access/NodeService.java similarity index 63% rename from src/main/java/de/avatic/lcc/service/configuration/NodeService.java rename to src/main/java/de/avatic/lcc/service/access/NodeService.java index dba8c79..cb24241 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/NodeService.java +++ b/src/main/java/de/avatic/lcc/service/access/NodeService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO; import de.avatic.lcc.dto.generic.NodeDTO; @@ -12,7 +12,6 @@ import de.avatic.lcc.repositories.country.CountryRepository; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; import de.avatic.lcc.repositories.users.UserNodeRepository; -import de.avatic.lcc.service.GeoApiService; import de.avatic.lcc.service.transformer.nodes.NodeUpdateDTOTransformer; import de.avatic.lcc.service.transformer.nodes.NodeDetailTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer; @@ -23,6 +22,10 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +/** + * Service class for managing lifecycle operations for nodes and related entities. + * Provides methods to list, retrieve, update, delete, and create nodes in the system. + */ @Service public class NodeService { @@ -43,26 +46,72 @@ public class NodeService { } + /** + * Retrieves a paginated list of nodes filtered by the provided keyword. + * + * @param filter the filter keyword to search nodes. + * @param page the page number to retrieve. + * @param limit the number of nodes per page. + * @return a {@link SearchQueryResult} containing the paginated list of nodes. + */ public SearchQueryResult listNodes(String filter, int page, int limit) { return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeTransformer::toNodeDTO); } + /** + * Retrieves a paginated detailed view of nodes based on the provided filter. + * + * @param filter the filter keyword to search nodes. + * @param page the page number to retrieve. + * @param limit the number of nodes per page. + * @return a {@link SearchQueryResult} containing detailed information of nodes. + */ public SearchQueryResult listNodesView(String filter, int page, int limit) { return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeDetailTransformer::toNodeDetailDTO); } + /** + * Retrieves the detailed view of a specific node by its ID. + * + * @param id the ID of the node to retrieve. + * @return a {@link NodeDetailDTO} containing detailed information of the node. + * @throws NodeNotFoundException if no node is found with the specified ID. + */ public NodeDetailDTO getNode(Integer id) { return nodeDetailTransformer.toNodeDetailDTO(nodeRepository.getById(id).orElseThrow(() -> new NodeNotFoundException(id))); } + /** + * Marks a node as deprecated by its ID. + * + * @param id the ID of the node to delete. + * @return the ID of the deprecated node. + * @throws NodeNotFoundException if no node is found with the specified ID. + */ public Integer deleteNode(Integer id) { return nodeRepository.setDeprecatedById(id).orElseThrow(() -> new NodeNotFoundException(id)); } + /** + * Updates an existing node with the provided data. + * + * @param dto the {@link NodeUpdateDTO} containing the update data for the node. + * @return the ID of the updated node. + * @throws NodeNotFoundException if no node is found for the update. + */ public Integer updateNode(NodeUpdateDTO dto) { return nodeRepository.update(nodeUpdateDTOTransformer.fromNodeUpdateDTO(dto)).orElseThrow(() -> new NodeNotFoundException(dto.getId())); } + /** + * Searches for nodes by filter and type with an option to include user-specific nodes. + * + * @param filter the filter keyword to search nodes. + * @param limit the maximum number of nodes to retrieve. + * @param nodeType the type of node to restrict the search to. + * @param includeUserNode if true, includes user-specific nodes in the search results. + * @return a list of {@link NodeDTO} objects representing the search results. + */ public List searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) { List nodes = new ArrayList<>(); int userId = 1; //TODO get current user's id @@ -80,19 +129,5 @@ public class NodeService { return nodes; } - public void addUserNode(AddUserNodeDTO dto) { - Node node = new Node(); - node.setName(dto.getName()); - node.setAddress(dto.getAddress()); - node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude())); - node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude())); - node.setDestination(false); - node.setSource(true); - node.setIntermediate(false); - node.setDeprecated(false); - node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow().getId()); - - userNodeRepository.add(node); - } } diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremisesService.java b/src/main/java/de/avatic/lcc/service/access/PremisesService.java similarity index 73% rename from src/main/java/de/avatic/lcc/service/calculation/PremisesService.java rename to src/main/java/de/avatic/lcc/service/access/PremisesService.java index 56e40e2..9a8bb7d 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremisesService.java +++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java @@ -1,12 +1,13 @@ -package de.avatic.lcc.service.calculation; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.calculation.PremiseDTO; import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO; -import de.avatic.lcc.dto.calculation.edit.SetDataDTO; -import de.avatic.lcc.dto.calculation.edit.masterData.*; -import de.avatic.lcc.repositories.premise.PremiseRepository; +import de.avatic.lcc.dto.calculation.edit.masterData.MaterialUpdateDTO; +import de.avatic.lcc.dto.calculation.edit.masterData.PackagingUpdateDTO; +import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import de.avatic.lcc.repositories.premise.*; import de.avatic.lcc.service.transformer.generic.DimensionTransformer; import de.avatic.lcc.service.transformer.premise.PremiseTransformer; import org.springframework.stereotype.Service; @@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.HashMap; import java.util.List; +import java.util.stream.Stream; @Service public class PremisesService { @@ -22,11 +24,13 @@ public class PremisesService { private final PremiseRepository premiseRepository; private final PremiseTransformer premiseTransformer; private final DimensionTransformer dimensionTransformer; + private final DestinationService destinationService; - public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer) { + public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, DestinationService destinationService) { this.premiseRepository = premiseRepository; this.premiseTransformer = premiseTransformer; this.dimensionTransformer = dimensionTransformer; + this.destinationService = destinationService; } @Transactional(readOnly = true) @@ -39,7 +43,6 @@ public class PremisesService { } - @Transactional(readOnly = true) public List getPremises(List premiseIds) { //TODO check if user authorized @@ -47,42 +50,12 @@ public class PremisesService { return premiseRepository.getPremisesById(premiseIds).stream().map(premiseTransformer::toPremiseDetailDTO).toList(); } + + //TODO move to other service public Integer startCalculation(List premises) { } - - @Transactional - public List setSupplier(SetDataDTO setSupplierDTO) { - //0. check if one of the given premises contains the same part number, and abort if any duplicate would emerge - - //1. delete all routes. - - //2. recalculate routes - - //3 if updateMasterDate is set: - //3.1 copy packaging data (if exists) of new supplier to premise - //3.2 set new tariff rate (if supplier country changed) - - //4. Deliver Premise Detail - - return null; - } - - @Transactional - public List setMaterial(SetDataDTO setMaterialDTO) { - //0. check if one of the given premises contains the same supplier id, and abort if any duplicate would emerge - - //3 if updateMasterDate is set: - //3.1 copy packaging data (if exists) of new material to premise - //3.2 set new tariff rate and hs code (if supplier country changed) - - //4. Deliver Premise Detail - - - return null; - } - public HashMap updatePackaging(PackagingUpdateDTO packagingDTO) { //TODO check values. and return errors if needed @@ -111,7 +84,17 @@ public class PremisesService { premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), userId, BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue()), priceUpdateDTO.getIncludeFcaFee(), BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue())); - return null; } + + @Transactional + public void delete(List premiseIds) { + //TODO check authorization + destinationService.deleteAllDestinationsByPremiseId(premiseIds, false); + premiseRepository.deletePremisesById(premiseIds); + } + + public void fillPackaging(List premiseIds) { + + } } diff --git a/src/main/java/de/avatic/lcc/service/configuration/PropertyService.java b/src/main/java/de/avatic/lcc/service/access/PropertyService.java similarity index 61% rename from src/main/java/de/avatic/lcc/service/configuration/PropertyService.java rename to src/main/java/de/avatic/lcc/service/access/PropertyService.java index ed62055..b0f2292 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/PropertyService.java +++ b/src/main/java/de/avatic/lcc/service/access/PropertyService.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.configuration; +package de.avatic.lcc.service.access; import de.avatic.lcc.dto.generic.ValidityPeriodDTO; import de.avatic.lcc.dto.generic.PropertyDTO; @@ -10,6 +10,20 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +/** + * Service for managing properties and property sets within the application. + *

+ * This service provides functionality to: + *

    + *
  • Set property values for specific mappings
  • + *
  • List properties filtered by property set ID
  • + *
  • List all property sets with their validity periods
  • + *
  • Invalidate property sets
  • + *
+ *

+ * The service interacts with {@link PropertyRepository} and {@link PropertySetRepository} + * to perform CRUD operations on properties and property sets. + */ @Service public class PropertyService { @@ -21,8 +35,9 @@ public class PropertyService { /** * Constructs the PropertyService with the provided repositories. * - * @param propertyRepository the repository to manage properties - * @param propertySetRepository the repository to manage property sets + * @param propertyRepository the repository to manage properties + * @param propertySetRepository the repository to manage property sets + * @param validityPeriodTransformer the transformer to convert property sets to validity period DTOs */ public PropertyService(PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodTransformer validityPeriodTransformer) { this.propertyRepository = propertyRepository; @@ -32,6 +47,9 @@ public class PropertyService { /** * Sets a property value for a given mapping ID. + *

+ * This method updates the property value in the current draft set for the specified mapping ID. + * Note: Property value validation is pending implementation. * * @param mappingId the identifier of the property to be updated * @param value the new value to be set for the property @@ -45,12 +63,14 @@ public class PropertyService { } /** - * Retrieves a list of properties. If a specific property set ID is provided, only - * properties from that set are returned. Otherwise, the properties from the current - * valid set and current draft set are returned. + * Retrieves a list of properties based on the specified property set ID. + *

+ * If the property set ID is 0, all properties from both the current valid set + * and current draft set are returned. Otherwise, only properties from the + * specified property set are returned. * * @param propertySetId the ID of the property set (0 for all properties) - * @return a list of properties as {@link PropertyDTO} + * @return a list of properties as {@link PropertyDTO} objects */ public List listProperties(Integer propertySetId) { if (propertySetId == 0) @@ -61,8 +81,11 @@ public class PropertyService { /** * Retrieves a list of all property sets, mapping them to validity period data transfer objects. + *

+ * This method fetches all property sets from the repository and transforms each of them + * into a {@link ValidityPeriodDTO} using the injected transformer. * - * @return a list of property sets as {@link ValidityPeriodDTO} + * @return a list of property sets as {@link ValidityPeriodDTO} objects */ public List listPropertySets() { return propertySetRepository.listPropertySets().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList(); diff --git a/src/main/java/de/avatic/lcc/service/access/UserNodeService.java b/src/main/java/de/avatic/lcc/service/access/UserNodeService.java new file mode 100644 index 0000000..8b89a9e --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/access/UserNodeService.java @@ -0,0 +1,48 @@ +package de.avatic.lcc.service.access; + +import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO; +import de.avatic.lcc.model.country.IsoCode; +import de.avatic.lcc.model.nodes.Node; +import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.repositories.users.UserNodeRepository; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +public class UserNodeService { + private final CountryRepository countryRepository; + private final UserNodeRepository userNodeRepository; + + public UserNodeService(CountryRepository countryRepository, UserNodeRepository userNodeRepository) { + this.countryRepository = countryRepository; + this.userNodeRepository = userNodeRepository; + } + + public Node getUserNode(Integer id) { + return userNodeRepository.getById(id).orElseThrow(); + } + + /** + * Adds a user-specific node to the system based on the provided {@link AddUserNodeDTO}. + * + * @param dto the {@link AddUserNodeDTO} containing details about the user node to create, including name, + * address, geographic location, and country information. + * @throws IllegalArgumentException if any required information in the DTO is missing or invalid. + */ + public void addUserNode(AddUserNodeDTO dto) { + Node node = new Node(); + + node.setName(dto.getName()); + node.setAddress(dto.getAddress()); + node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude())); + node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude())); + node.setDestination(false); + node.setSource(true); + node.setIntermediate(false); + node.setDeprecated(false); + node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow().getId()); + + userNodeRepository.add(node); + } +} diff --git a/src/main/java/de/avatic/lcc/service/access/ValidityPeriodService.java b/src/main/java/de/avatic/lcc/service/access/ValidityPeriodService.java new file mode 100644 index 0000000..bb2ea4c --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/access/ValidityPeriodService.java @@ -0,0 +1,61 @@ +package de.avatic.lcc.service.access; + +import de.avatic.lcc.dto.generic.ValidityPeriodDTO; +import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; +import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Service class responsible for managing validity periods in the system. + *

+ * This service provides operations for retrieving and manipulating validity periods, + * acting as an intermediary between the repository layer and the application layer. + * It uses a transformer to convert between entity and DTO representations. + *

+ * + */ +@Service +public class ValidityPeriodService { + + private final ValidityPeriodRepository validityPeriodRepository; + private final ValidityPeriodTransformer validityPeriodTransformer; + + /** + * Constructs a new ValidityPeriodService with the required dependencies. + * + * @param validityPeriodRepository Repository for accessing validity period data + * @param validityPeriodTransformer Transformer for converting between entity and DTO representations + */ + public ValidityPeriodService(ValidityPeriodRepository validityPeriodRepository, ValidityPeriodTransformer validityPeriodTransformer) { + this.validityPeriodRepository = validityPeriodRepository; + this.validityPeriodTransformer = validityPeriodTransformer; + } + + + /** + * Retrieves all validity periods from the repository and transforms them to DTOs. + *

+ * Fetches the list of validity periods from the repository and transforms each entity + * to its corresponding DTO representation using the transformer. + *

+ * + * @return A list of {@link ValidityPeriodDTO} objects representing all validity periods + */ + public List listPeriods() { + return validityPeriodRepository.listPeriods().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList(); + } + + /** + * Invalidates a validity period with the specified ID. + *

+ * Marks the validity period as invalid in the repository based on its ID. + *

+ * + * @param id The unique identifier of the validity period to invalidate + */ + public void invalidate(Integer id) { + validityPeriodRepository.invalidateById(id); + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java index 460ab08..e29ee98 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java @@ -5,7 +5,7 @@ import de.avatic.lcc.model.bulk.BulkFileTypes; import de.avatic.lcc.model.bulk.HiddenTableType; import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; -import de.avatic.lcc.service.bulk.mapper.*; +import de.avatic.lcc.service.excelMapper.*; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.SheetVisibility; diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java index 325165e..9700f20 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java @@ -4,10 +4,7 @@ import de.avatic.lcc.dto.bulk.BulkFileType; import de.avatic.lcc.dto.bulk.BulkProcessingType; import de.avatic.lcc.dto.bulk.BulkStatus; import de.avatic.lcc.model.bulk.BulkFileTypes; -import de.avatic.lcc.model.materials.Material; -import de.avatic.lcc.model.rates.ContainerRate; -import de.avatic.lcc.model.rates.MatrixRate; -import de.avatic.lcc.service.bulk.mapper.*; +import de.avatic.lcc.service.excelMapper.*; import de.avatic.lcc.util.exception.clienterror.FileFormatNotSupportedException; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; @@ -16,7 +13,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; -import java.util.List; @Service public class BulkFileProcessingService { @@ -65,6 +61,7 @@ public class BulkFileProcessingService { break; case NODE: var nodes = nodeExcelMapper.extractSheet(sheet); + // check predecessors chains for loops or contradictions break; default: diff --git a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java index 82aa66d..2e5b2bc 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java @@ -4,10 +4,8 @@ import de.avatic.lcc.dto.bulk.BulkFileType; import de.avatic.lcc.model.bulk.*; import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; import de.avatic.lcc.service.bulk.helper.HeaderGenerator; -import de.avatic.lcc.service.bulk.mapper.*; -import org.apache.poi.ss.SpreadsheetVersion; +import de.avatic.lcc.service.excelMapper.*; import org.apache.poi.ss.usermodel.*; -import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; diff --git a/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java b/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java new file mode 100644 index 0000000..f601841 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/calculation/ChangeMaterialService.java @@ -0,0 +1,27 @@ +package de.avatic.lcc.service.calculation; + +import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO; +import de.avatic.lcc.dto.calculation.edit.SetDataDTO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class ChangeMaterialService { + + @Transactional + public List setMaterial(SetDataDTO setMaterialDTO) { + //0. check if one of the given premises contains the same supplier id, and abort if any duplicate would emerge + + //3 if updateMasterDate is set: + //3.1 copy packaging data (if exists) of new material to premise + //3.2 set new tariff rate and hs code (if supplier country changed) + + //4. Deliver Premise Detail + + + return null; + } + +} diff --git a/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java new file mode 100644 index 0000000..08b76d5 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java @@ -0,0 +1,135 @@ +package de.avatic.lcc.service.calculation; + +import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO; +import de.avatic.lcc.dto.calculation.edit.SetDataDTO; +import de.avatic.lcc.model.nodes.Node; +import de.avatic.lcc.model.packaging.PackagingDimension; +import de.avatic.lcc.model.premises.Premise; +import de.avatic.lcc.model.premises.route.Destination; +import de.avatic.lcc.model.properties.PackagingProperty; +import de.avatic.lcc.model.properties.PackagingPropertyMappingId; +import de.avatic.lcc.repositories.MaterialRepository; +import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository; +import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository; +import de.avatic.lcc.repositories.packaging.PackagingRepository; +import de.avatic.lcc.repositories.premise.DestinationRepository; +import de.avatic.lcc.repositories.premise.PremiseRepository; +import de.avatic.lcc.repositories.users.UserNodeRepository; +import de.avatic.lcc.service.CustomApiService; +import de.avatic.lcc.service.access.DestinationService; +import de.avatic.lcc.service.access.PremisesService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +@Service +public class ChangeSupplierService { + + private final PremiseRepository premiseRepository; + private final DestinationService destinationService; + private final RoutingService routingService; + private final PremisesService premisesService; + private final CustomApiService customApiService; + private final NodeRepository nodeRepository; + private final UserNodeRepository userNodeRepository; + private final MaterialRepository materialRepository; + private final PackagingRepository packagingRepository; + private final PackagingDimensionRepository packagingDimensionRepository; + private final PackagingPropertiesRepository packagingPropertiesRepository; + private final DestinationRepository destinationRepository; + + public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, MaterialRepository materialRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository) { + this.premiseRepository = premiseRepository; + this.destinationService = destinationService; + this.routingService = routingService; + this.premisesService = premisesService; + this.customApiService = customApiService; + this.nodeRepository = nodeRepository; + this.userNodeRepository = userNodeRepository; + this.materialRepository = materialRepository; + this.packagingRepository = packagingRepository; + this.packagingDimensionRepository = packagingDimensionRepository; + this.packagingPropertiesRepository = packagingPropertiesRepository; + this.destinationRepository = destinationRepository; + } + + + @Transactional + public List setSupplier(SetDataDTO dto) { + + Integer userId = 1; // todo get current user; + Integer supplierNodeId = dto.getSupplierNodeId(); + List premiseIds = dto.getPremiseId(); + + if (supplierNodeId == null || premiseIds == null || premiseIds.isEmpty()) + throw new IllegalArgumentException("No supplier supplierNodeId or premises given"); + + Node supplier = dto.isUserSupplierNode() ? userNodeRepository.getById(supplierNodeId).orElseThrow() : nodeRepository.getById(supplierNodeId).orElseThrow(); + + // get all premises first. + List allPremises = premiseRepository.getPremisesById(premiseIds); + + // find resulting duplicates, split into "keep" and "to be deleted". + Map uniqueMap = new HashMap<>(); + List premisesToBeDeleted = new ArrayList<>(); + allPremises.forEach(p -> { + if (null != uniqueMap.putIfAbsent(p.getMaterialId(), p)) premisesToBeDeleted.add(p); + }); + Collection premisesToProcess = uniqueMap.values(); + + // check if user owns all premises: + if (allPremises.stream().anyMatch(p -> !p.getUserId().equals(userId))) + throw new IllegalArgumentException("Not authorized to change suppliers of premises owned by other users"); + + // check for any other collisions, and mark as "to be deleted": + premisesToBeDeleted.addAll(premisesToProcess.stream().map(p -> premiseRepository.getCollidingPremisesOnChange(userId, p.getId(), p.getMaterialId(), supplierNodeId)).flatMap(List::stream).toList()); + + // delete all routes of all destinations + destinationService.deleteAllDestinationsByPremiseId(premisesToProcess.stream().map(Premise::getId).toList(), true); + + //recalculate routes: + for (Premise premise : premisesToProcess) { + List destination = destinationRepository.getByPremiseId(premise.getId()); + for( Destination d : destination ) { + routingService.findRoutes(d.getId(), supplierNodeId); + } + } + + //update master data: + if (dto.isUpdateMasterData()) { + for (var premise : premisesToProcess) { + var tariffRate = customApiService.getTariffRate(premise.getHsCode(), supplier.getCountryId()); + premiseRepository.updateTariffRate(premise.getId(), tariffRate); + + if (!dto.isUserSupplierNode()) { + var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), supplierNodeId); + Optional dimension = packagingDimensionRepository.getById(packaging.getFirst().getHuId()); + + if (dimension.isPresent()) { + Optional stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name()); + Optional mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.MIXABLE.name()); + //TODO check optional + premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), userId, dimension.get(), Boolean.valueOf(stackable.get().getValue()), Boolean.valueOf(mixable.get().getValue())); + } + + } + + premisesService.fillPackaging(premisesToProcess.stream().map(Premise::getId).toList()); + } + } + + // actually update supplier supplierNodeId. + premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplierNodeId, dto.isUserSupplierNode()); + + + //delete all conflicting premises: + premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList()); + + + return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList()); + + } + +} diff --git a/src/main/java/de/avatic/lcc/service/calculation/DestinationService.java b/src/main/java/de/avatic/lcc/service/calculation/DestinationService.java deleted file mode 100644 index a43f703..0000000 --- a/src/main/java/de/avatic/lcc/service/calculation/DestinationService.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.avatic.lcc.service.calculation; - -import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO; -import de.avatic.lcc.dto.calculation.DestinationDTO; -import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO; -import de.avatic.lcc.repositories.premise.DestinationRepository; -import org.springframework.stereotype.Service; - -import java.util.HashMap; - -@Service -public class DestinationService { - - - private final DestinationRepository destinationRepository; - - public DestinationService(DestinationRepository destinationRepository) { - this.destinationRepository = destinationRepository; - } - - public HashMap createDestination(DestinationCreateDTO destinationCreateDTO) { - - // do some checks - // - no duplicates - - // do routing. - - // create database entries. - - return null; - } - - public DestinationDTO getDestination(Integer id) { - return null; - } - - public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) { - //destinationRepository.update(id, destinationUpdateDTO.) - } - - public void deleteDestination(Integer id) { - } -} diff --git a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java new file mode 100644 index 0000000..939600f --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java @@ -0,0 +1,135 @@ +package de.avatic.lcc.service.calculation; + +import de.avatic.lcc.model.nodes.Node; +import de.avatic.lcc.model.properties.SystemPropertyMappingId; +import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.repositories.properties.PropertyRepository; +import de.avatic.lcc.repositories.rates.MatrixRateRepository; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class RoutingService { + + + private final NodeRepository nodeRepository; + private final CountryRepository countryRepository; + private final MatrixRateRepository matrixRateRepository; + private final PropertyRepository propertyRepository; + + public RoutingService(NodeRepository nodeRepository, CountryRepository countryRepository, MatrixRateRepository matrixRateRepository, PropertyRepository propertyRepository) { + this.nodeRepository = nodeRepository; + this.countryRepository = countryRepository; + this.matrixRateRepository = matrixRateRepository; + this.propertyRepository = propertyRepository; + } + + public void findRoutes(Integer destinationId, Integer sourceId) { + var foundRoutes = new ArrayList(); + + Node source = nodeRepository.getById(sourceId).orElseThrow(); + Node destination = nodeRepository.getById(destinationId).orElseThrow(); + List regionNodes = nodeRepository.getByDistance(source, getRegionRadius()); + List outboundNodes = nodeRepository.getAllOutboundFor(source.getCountryId()); + + List> destinationChains = resolveChains(destinationId); + Map>> outboundChains = getOutboundChains(outboundNodes); + + + + foundRoutes.addAll(constructRoutesWithChains(destinationChains, source, destination)); + + + + } + + private Map>> getOutboundChains(List outboundNodes) { + Map>> outboundChains = new HashMap<>(); + for(var outboundNode : outboundNodes) { + outboundChains.put(outboundNode.getId(), resolveChains(outboundNode.getId())); + } + return outboundChains; + } + + private Integer getRegionRadius() { + var property = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.RADIUS_REGION); + return property.map(propertyDTO -> Integer.valueOf(propertyDTO.getCurrentValue())).orElseGet(SystemPropertyMappingId.RADIUS_REGION::getDefaultAsInteger); + } + + private ArrayList constructRoutesWithChains(List> chains, Node source, Node destination) { + ArrayList foundRoutes = new ArrayList<>(); + + for (var chain : chains) { + if(!chain.isEmpty()) { + var matrixRate = matrixRateRepository.getByCountryIds(chain.getLast().getCountryId(), source.getCountryId()); + + if(matrixRate.isPresent()) { + foundRoutes.add(new FoundRoute(chain, destination, source)); + } + + } + } + return foundRoutes; + } + + + /** + * Resolves all possible chains of nodes starting from the specified node ID. + * This method recursively retrieves predecessor chains and appends successor chains + * to form complete resolution paths. + * + * @param nodeId The ID of the node from which the resolution process starts. + * Must not be null and must correspond to an existing node. + * @return A list of lists, where each inner list represents a fully resolved chain + * of nodes including predecessors and successors for the specified node ID. + */ + private List> resolveChains(Integer nodeId) { + var resolvedChains = new ArrayList>(); + + var predecessorChains = nodeRepository.resolveChainsById(nodeId); + + for (var predecessorChain : predecessorChains) { + if (!predecessorChain.isEmpty()) { + var successorChains = resolveChains(predecessorChain.getLast().getId()); + + successorChains.forEach(successorChain -> { + successorChain.addAll(0, predecessorChain); + resolvedChains.add(successorChain); + }); + + } + else { + resolvedChains.add(predecessorChain); + } + } + + return resolvedChains; + } + + private static class FoundRoute { + private List nodes; + + /** + * Constructs a FoundRoute object that represents a route consisting of a chain of nodes, + * starting with the source node, followed by the reversed chain of nodes, and ending with the destination node. + * + * @param chain The list of intermediary nodes in the route, in original order before reversing. + * Must not be null. + * @param destination The destination node in the route. Must not be null. + * @param source The source node in the route. Must not be null. + */ + public FoundRoute(List chain, Node destination, Node source) { + this.nodes = new ArrayList<>(); + this.nodes.add(source); + this.nodes.addAll(chain.reversed()); + this.nodes.add(destination); + } + } + + +} diff --git a/src/main/java/de/avatic/lcc/service/configuration/ValidityPeriodService.java b/src/main/java/de/avatic/lcc/service/configuration/ValidityPeriodService.java deleted file mode 100644 index 7c68eda..0000000 --- a/src/main/java/de/avatic/lcc/service/configuration/ValidityPeriodService.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.avatic.lcc.service.configuration; - -import de.avatic.lcc.dto.generic.ValidityPeriodDTO; -import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; -import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class ValidityPeriodService { - - private final ValidityPeriodRepository validityPeriodRepository; - private final ValidityPeriodTransformer validityPeriodTransformer; - - public ValidityPeriodService(ValidityPeriodRepository validityPeriodRepository, ValidityPeriodTransformer validityPeriodTransformer) { - this.validityPeriodRepository = validityPeriodRepository; - this.validityPeriodTransformer = validityPeriodTransformer; - } - - public List listPeriods() { - return validityPeriodRepository.listPeriods().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList(); - } - - public void invalidate(Integer id) { - validityPeriodRepository.invalidateById(id); - } -} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java similarity index 99% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java index b4b66c8..f5fc599 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.model.bulk.ContainerRateHeader; import de.avatic.lcc.model.bulk.HiddenNodeHeader; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java similarity index 96% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java index 280a3a3..523fdfa 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.model.bulk.HiddenCountryHeader; import de.avatic.lcc.model.country.Country; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java similarity index 96% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java index e2fb238..a1914cd 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.model.bulk.HiddenNodeHeader; import de.avatic.lcc.model.nodes.Node; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java similarity index 97% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java index 4e98b58..6ba95da 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java @@ -1,6 +1,5 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; -import de.avatic.lcc.model.bulk.ContainerRateHeader; import de.avatic.lcc.model.bulk.MaterialHeader; import de.avatic.lcc.model.materials.Material; import de.avatic.lcc.repositories.MaterialRepository; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java similarity index 97% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java index 8bf1f45..23cdb1d 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java @@ -1,8 +1,7 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.model.bulk.*; import de.avatic.lcc.model.country.IsoCode; -import de.avatic.lcc.model.rates.ContainerRateType; import de.avatic.lcc.model.rates.MatrixRate; import de.avatic.lcc.repositories.country.CountryRepository; import de.avatic.lcc.repositories.rates.MatrixRateRepository; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java similarity index 99% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java index c47a7f2..779fd74 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.excelmodel.ExcelNode; import de.avatic.lcc.model.bulk.HiddenCountryHeader; diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java similarity index 99% rename from src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java rename to src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java index 563046f..918f07b 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java @@ -1,4 +1,4 @@ -package de.avatic.lcc.service.bulk.mapper; +package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.excelmodel.ExcelPackaging;