Remove RoutingService2 and implement premise creation logic

Deleted the unused `RoutingService2` class to simplify the codebase. Added the logic for creating and duplicating premises in `PremiseCreationService`, including filtering and handling combinations of material and supplier IDs.
This commit is contained in:
Jan 2025-05-02 12:17:14 +02:00
parent a468e3d187
commit fd6e4ea435
20 changed files with 1406 additions and 1375 deletions

View file

@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/calculation")
@ -70,7 +71,7 @@ public class CalculationController {
// triggers a calculation
@PutMapping("/done")
public ResponseEntity<Integer> completePremises(@RequestBody List<Integer> premiseIds) {
return ResponseEntity.ok(premisesServices.startCalculation(premiseIds));
return ResponseEntity.ok(premisesServices.startCalculation(premiseIds)); //TODO
}
@PutMapping("/packaging")
@ -89,7 +90,7 @@ public class CalculationController {
}
@PostMapping("/destination")
public ResponseEntity<HashMap<Integer, DestinationDTO>> createDestination(@RequestBody DestinationCreateDTO destinationCreateDTO) {
public ResponseEntity<Map<Integer, DestinationDTO>> createDestination(@RequestBody DestinationCreateDTO destinationCreateDTO) {
return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO));
}

View file

@ -12,7 +12,7 @@ public class SetDataDTO {
@JsonProperty("update_master_data")
boolean updateMasterData;
@JsonProperty("supplier_node_id", required = false)
@JsonProperty(defaultValue = "supplier_node_id", required = false)
Integer supplierNodeId;
@JsonProperty("is_user_supplier_node")

View file

@ -7,20 +7,22 @@ public class RouteInformation {
private Route route;
private List<RouteSection> sections;
private List<RouteSectionInformation> sections;
private List<RouteNode> nodes;
public Route getRoute() {
return route;
}
public void setRoute(Route route) {
this.route = route;
}
public void setRouteNodes(List<RouteNode> nodes) {
this.nodes = nodes;
public List<RouteSectionInformation> getSections() {
return sections;
}
public void setRouteSections(List<RouteSection> sections) {
public void setSections(List<RouteSectionInformation> sections) {
this.sections = sections;
}
}

View file

@ -0,0 +1,39 @@
package de.avatic.lcc.model.premises.route;
public class RouteSectionInformation {
private RouteNode fromNode;
private RouteNode toNode;
private RouteSection section;
public RouteSectionInformation(RouteSection routeSection, RouteNode fromNode, RouteNode toNode) {
this.section = routeSection;
this.fromNode = fromNode;
this.toNode = toNode;
}
public RouteNode getFromNode() {
return fromNode;
}
public void setFromNode(RouteNode fromNode) {
this.fromNode = fromNode;
}
public RouteNode getToNode() {
return toNode;
}
public void setToNode(RouteNode toNode) {
this.toNode = toNode;
}
public RouteSection getSection() {
return section;
}
public void setSection(RouteSection section) {
this.section = section;
}
}

View file

@ -15,9 +15,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
@Repository
public class MaterialRepository {
@ -29,28 +27,6 @@ public class MaterialRepository {
this.jdbcTemplate = jdbcTemplate;
}
public List<Material> listAllMaterials() {
String query = "SELECT * FROM material ORDER BY normalized_part_number";
return jdbcTemplate.query(query, new MaterialMapper());
}
private static class MaterialMapper implements RowMapper<Material> {
@Override
public Material mapRow(ResultSet rs, int rowNum) throws SQLException {
Material data = new Material();
data.setId(rs.getInt("id"));
data.setName(rs.getString("name"));
data.setPartNumber(rs.getString("part_number"));
data.setNormalizedPartNumber(rs.getString("normalized_part_number"));
data.setHsCode(rs.getString("hs_code"));
data.setDeprecated(rs.getBoolean("is_deprecated"));
return data;
}
}
private static String buildCountQuery(String filter, boolean excludeDeprecated) {
StringBuilder queryBuilder = new StringBuilder("""
SELECT count(*)
@ -66,7 +42,6 @@ public class MaterialRepository {
return queryBuilder.toString();
}
private static String buildQuery(String filter, boolean excludeDeprecated) {
StringBuilder queryBuilder = new StringBuilder("""
SELECT id, name, part_number, normalized_part_number, hs_code, is_deprecated
@ -82,8 +57,25 @@ public class MaterialRepository {
return queryBuilder.toString();
}
public List<Material> listAllMaterials() {
String query = "SELECT * FROM material ORDER BY normalized_part_number";
return jdbcTemplate.query(query, new MaterialMapper());
}
public List<Material> getByPartNumbers(Collection<String> partNumbers) {
// Handle null or empty case
if (partNumbers == null || partNumbers.isEmpty()) {
return Collections.emptyList();
}
String placeholders = String.join(",", Collections.nCopies(partNumbers.size(), "?"));
String query = "SELECT * FROM material WHERE part_number IN (" + placeholders + ")";
return jdbcTemplate.query(query, new MaterialMapper(), partNumbers.toArray());
}
@Transactional
public Optional<Integer> setDeprecatedById(Integer id) {
public Optional<Integer> setDeprecatedById(Integer id) {
String query = "UPDATE material SET is_deprecated = TRUE WHERE id = ?";
return Optional.ofNullable(jdbcTemplate.update(query, id) == 0 ? null : id);
}
@ -149,4 +141,19 @@ public class MaterialRepository {
}
private static class MaterialMapper implements RowMapper<Material> {
@Override
public Material mapRow(ResultSet rs, int rowNum) throws SQLException {
Material data = new Material();
data.setId(rs.getInt("id"));
data.setName(rs.getString("name"));
data.setPartNumber(rs.getString("part_number"));
data.setNormalizedPartNumber(rs.getString("normalized_part_number"));
data.setHsCode(rs.getString("hs_code"));
data.setDeprecated(rs.getBoolean("is_deprecated"));
return data;
}
}
}

View file

@ -1,5 +1,6 @@
package de.avatic.lcc.repositories;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.NodeType;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
@ -32,9 +33,21 @@ public class NodeRepository {
FROM node
WHERE node.id = ?""";
var chain = jdbcTemplate.queryForObject(query, new NodeMapper(), id);
var node = jdbcTemplate.queryForObject(query, new NodeMapper(), id);
return Optional.ofNullable(chain);
return Optional.ofNullable(node);
}
public List<Node> getByIds(List<Integer> nodeIds) {
String placeholders = String.join(",", Collections.nCopies(nodeIds.size(), "?"));
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
WHERE node.id IN (?)""";
return jdbcTemplate.query(query, new NodeMapper(), nodeIds.toArray());
}
private List<Map<Integer, Integer>> getPredecessorsOf(Integer id) {
@ -281,6 +294,7 @@ public class NodeRepository {
return jdbcTemplate.query(query, new NodeMapper(), countryId);
}
private class NodeMapper implements RowMapper<Node> {
@Override

View file

@ -3,11 +3,14 @@ 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.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -70,6 +73,38 @@ public class DestinationRepository {
return Optional.of(userId.getFirst());
}
public List<Destination> getByPremiseIdsAndNodeId(List<Integer> premiseId, Integer nodeId, Integer userId) {
String placeholder = String.join(",", Collections.nCopies(premiseId.size(), "?"));
String query = "SELECT * FROM premise_destination JOIN premise ON premise_destination.premise_id = premise.id WHERE premise_id IN ("+placeholder+") AND premise_destination.destination_node_id AND premise.user_id = ?";
return jdbcTemplate.query(query, new DestinationMapper(), premiseId, nodeId, userId);
}
public Integer insert(Destination destination) {
KeyHolder keyHolder = new GeneratedKeyHolder();
String query = "INSERT INTO premise_destination (annual_amount, premise_id, destination_node_id, rate_d2d, is_d2d, repacking_cost, handling_cost, disposal_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(connection -> {
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
ps.setBigDecimal(1, destination.getAnnualAmount());
ps.setInt(2, destination.getPremiseId());
ps.setInt(3, destination.getDestinationNodeId());
ps.setBigDecimal(4, destination.getRateD2d());
ps.setBoolean(5, destination.getD2d());
ps.setBigDecimal(6, destination.getRepackingCost());
ps.setBigDecimal(7, destination.getHandlingCost());
ps.setBigDecimal(8, destination.getDisposalCost());
return ps;
}, keyHolder);
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
public void duplicate(Integer fromId, Integer toId) {
}
private static class DestinationMapper implements RowMapper<Destination> {
@Override

View file

@ -13,12 +13,17 @@ 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.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -34,6 +39,7 @@ public class PremiseRepository {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
@Transactional
public SearchQueryResult<PremiseListEntry> listPremises(String filter, SearchQueryPagination pagination,
Integer userId, Boolean deleted, Boolean archived, Boolean done) {
@ -111,6 +117,7 @@ public class PremiseRepository {
}
@Transactional(readOnly = true)
public List<Premise> getPremisesById(List<Integer> premiseIds) {
@ -132,6 +139,7 @@ public class PremiseRepository {
);
}
@Transactional
public Optional<Premise> getPremiseById(Integer id) {
String query = """
@ -189,6 +197,7 @@ public class PremiseRepository {
}
@Transactional
public void updateMaterial(List<Integer> premiseIds, Integer userId, String hsCode, BigDecimal tariffRate) {
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
@ -214,6 +223,7 @@ public class PremiseRepository {
);
}
@Transactional
public void updatePrice(List<Integer> premiseIds, int userId, BigDecimal price, Boolean includeFcaFee, BigDecimal overseaShare) {
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
@ -255,6 +265,7 @@ public class PremiseRepository {
* @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.
*/
@Transactional(readOnly = true)
public List<Premise> getCollidingPremisesOnChange(Integer userId, Integer premiseId, Integer materialId, Integer supplierId) {
if( premiseId == null || materialId == null || supplierId == null )
@ -270,6 +281,7 @@ public class PremiseRepository {
return jdbcTemplate.query(sql, new PremiseMapper(), materialId, supplierId, premiseId, userId);
}
@Transactional
public void deletePremisesById(List<Integer> premiseIds) {
if(premiseIds == null || premiseIds.isEmpty()) return;
@ -281,12 +293,14 @@ public class PremiseRepository {
jdbcTemplate.update(query, premiseIds.toArray());
}
@Transactional
public void updateTariffRate(Integer premiseId, Number tariffRate) {
String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?";
jdbcTemplate.update(sql, tariffRate, premiseId);
}
@Transactional
public void setSupplierId(List<Integer> 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+")";
@ -298,6 +312,124 @@ public class PremiseRepository {
}
}
@Transactional
public void setMaterialId(List<Integer> premiseId, Integer materialId) {
String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
String sql = "UPDATE premise SET material_id = ? WHERE id IN ("+placeholders+")";
jdbcTemplate.update(sql, materialId, premiseId.toArray());
}
@Transactional
public List<Premise> getPremisesByMaterialIdsAndSupplierIds(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, Integer userId, boolean draftsOnly) {
if ((materialIds == null || materialIds.isEmpty()) &&
(supplierIds == null || supplierIds.isEmpty()) &&
(userSupplierIds == null || userSupplierIds.isEmpty())) {
return Collections.emptyList();
}
StringBuilder query = new StringBuilder("""
SELECT * FROM premise
WHERE 1=1
""");
if(draftsOnly)
query.append(" AND user_id = ? AND state = ?");
// Append conditions for IDs
if (materialIds != null && !materialIds.isEmpty()) {
String materialPlaceholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
query.append(" AND material_id IN (").append(materialPlaceholders).append(")");
}
if (supplierIds != null && !supplierIds.isEmpty()) {
String supplierPlaceholders = String.join(",", Collections.nCopies(supplierIds.size(), "?"));
query.append(" AND supplier_node_id IN (").append(supplierPlaceholders).append(")");
}
if (userSupplierIds != null && !userSupplierIds.isEmpty()) {
String userSupplierPlaceholders = String.join(",", Collections.nCopies(userSupplierIds.size(), "?"));
query.append(" AND user_supplier_node_id IN (").append(userSupplierPlaceholders).append(")");
}
// Combine parameters for prepared statement
var params = new ArrayList<>();
if(draftsOnly) {
params.add(userId);
params.add(PremiseState.DRAFT.name());
}
if (materialIds != null && !materialIds.isEmpty()) params.addAll(materialIds);
if (supplierIds != null && !supplierIds.isEmpty()) params.addAll(supplierIds);
if (userSupplierIds != null && !userSupplierIds.isEmpty()) params.addAll(userSupplierIds);
return jdbcTemplate.query(query.toString(), new PremiseMapper(), params.toArray());
}
@Transactional
public Integer insert(Integer materialId, Integer supplierId, Integer userSupplierId, Integer userId) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, createdAt, updatedAt)" +
" VALUES (?, ?, ?, ?, 'DRAFT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, materialId);
ps.setObject(2, supplierId);
ps.setObject(3, userSupplierId);
ps.setInt(4, userId);
return ps;
}, keyHolder);
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
@Transactional
public List<Integer> findAssociatedSuppliers(List<Integer> materialIds) {
if (materialIds == null || materialIds.isEmpty()) {
return Collections.emptyList();
}
String placeholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
String query = """
SELECT DISTINCT supplier_node_id
FROM premise
WHERE material_id IN (""" + placeholders + ")";
return jdbcTemplate.query(
query,
(rs, rowNum) -> rs.getInt("supplier_node_id"),
materialIds.toArray()
);
}
@Transactional
public List<Integer> findAssociatedUserSuppliers(List<Integer> materialIds) {
if (materialIds == null || materialIds.isEmpty()) {
return Collections.emptyList();
}
String placeholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
String query = """
SELECT DISTINCT user_supplier_node_id
FROM premise
WHERE material_id IN (""" + placeholders + ")";
return jdbcTemplate.query(
query,
(rs, rowNum) -> rs.getInt("user_supplier_node_id"),
materialIds.toArray()
);
}
/**
* Encapsulates SQL query building logic
*/

View file

@ -3,10 +3,14 @@ package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.premises.route.RouteNode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -43,6 +47,29 @@ public class RouteNodeRepository {
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
public Integer insert(RouteNode node) {
String sql = "INSERT INTO premise_route_node (name, address, geo_lat, geo_lng, is_destination, is_intermediate, " +
"is_source, node_id, user_node_id, is_outdated) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, node.getName());
ps.setString(2, node.getAddress());
ps.setBigDecimal(3, node.getGeoLat());
ps.setBigDecimal(4, node.getGeoLng());
ps.setBoolean(5, Boolean.TRUE.equals(node.getDestination()));
ps.setBoolean(6, Boolean.TRUE.equals(node.getIntermediate()));
ps.setBoolean(7, Boolean.TRUE.equals(node.getSource()));
ps.setObject(8, node.getNodeId(), java.sql.Types.INTEGER);
ps.setObject(9, node.getUserNodeId(), java.sql.Types.INTEGER);
ps.setBoolean(10, Boolean.TRUE.equals(node.getOutdated()));
return ps;
}, keyHolder);
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
private static class RouteNodeMapper implements RowMapper<RouteNode> {
@Override

View file

@ -3,12 +3,17 @@ package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.premises.route.Route;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Repository
public class RouteRepository {
@ -36,6 +41,26 @@ public class RouteRepository {
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
public Integer insert(Route premiseRoute) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO premise_route (is_cheapest, is_fastest, is_selected, premise_destination_id) " +
"VALUES (?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS);
ps.setBoolean(1, premiseRoute.getCheapest());
ps.setBoolean(2, premiseRoute.getFastest());
ps.setBoolean(3, premiseRoute.getSelected());
ps.setInt(4, premiseRoute.getDestinationId());
return ps;
}, keyHolder);
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
private static class RouteMapper implements RowMapper<Route> {
@Override
public Route mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -4,10 +4,14 @@ import de.avatic.lcc.dto.generic.RouteType;
import de.avatic.lcc.model.premises.route.RouteSection;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
@ -38,6 +42,28 @@ public class RouteSectionRepository {
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
public Integer insert(RouteSection premiseRouteSection) {
String sql = "INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, is_pre_run, is_main_run, is_post_run, is_outdated) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, premiseRouteSection.getRouteId());
ps.setInt(2, premiseRouteSection.getFromRouteNodeId());
ps.setInt(3, premiseRouteSection.getToRouteNodeId());
ps.setInt(4, premiseRouteSection.getListPosition());
ps.setString(5, premiseRouteSection.getTransportType().name());
ps.setBoolean(6, premiseRouteSection.getPreRun());
ps.setBoolean(7, premiseRouteSection.getMainRun());
ps.setBoolean(8, premiseRouteSection.getPostRun());
ps.setBoolean(9, premiseRouteSection.getOutdated());
return ps;
}, keyHolder);
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
private static class RouteSectionMapper implements RowMapper<RouteSection> {
@Override
public RouteSection mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -3,20 +3,20 @@ 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.model.nodes.Node;
import de.avatic.lcc.model.premises.route.*;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.*;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.calculation.RoutingService;
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.math.BigDecimal;
import java.sql.Array;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@ -28,25 +28,62 @@ public class DestinationService {
private final RouteRepository routeRepository;
private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository;
private final RoutingService routingService;;
private final NodeRepository nodeRepository;
private final PremiseRepository premiseRepository;
private final UserNodeRepository userNodeRepository;
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository) {
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository) {
this.destinationRepository = destinationRepository;
this.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository;
this.routeSectionRepository = routeSectionRepository;
this.routeNodeRepository = routeNodeRepository;
this.routingService = routingService;
this.nodeRepository = nodeRepository;
this.premiseRepository = premiseRepository;
this.userNodeRepository = userNodeRepository;
}
public HashMap<Integer, DestinationDTO> createDestination(DestinationCreateDTO destinationCreateDTO) {
@Transactional
public Map<Integer, DestinationDTO> createDestination(DestinationCreateDTO dto) {
// do some checks
// - no duplicates
Integer userId = 1; //TODO get user id.
// do routing.
var existingDestinations = destinationRepository.getByPremiseIdsAndNodeId(dto.getPremiseId(), dto.getDestinationNodeId(), userId);
// create database entries.
var premisesIdsToProcess = new ArrayList<Integer>();
for(var premiseId : dto.getPremiseId()) {
if(existingDestinations.stream().map(Destination::getPremiseId).noneMatch(id -> id.equals(premiseId))) {
premisesIdsToProcess.add(premiseId);
}
}
var premisesToProcess = premiseRepository.getPremisesById(premisesIdsToProcess);
Node destinationNode = nodeRepository.getById(dto.getDestinationNodeId()).orElseThrow();
var destinations = new ArrayList<Destination>();
for(var premise : premisesToProcess) {
var destination = new Destination();
destination.setDestinationNodeId(dto.getDestinationNodeId());
destination.setPremiseId(premise.getId());
destination.setAnnualAmount(BigDecimal.ZERO);
destination.setD2d(false);
destination.setDisposalCost(null);
destination.setHandlingCost(null);
destination.setRepackingCost(null);
destination.setRateD2d(BigDecimal.ZERO);
destination.setId(destinationRepository.insert(destination));
Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow();
findRouteAndSave(destination.getId(), destinationNode, source, premise.getSupplierNodeId() == null);
destinations.add(destination);
}
return destinations.stream().collect(Collectors.toMap(Destination::getPremiseId, destinationTransformer::toDestinationDTO));
return null;
}
public DestinationDTO getDestination(Integer id) {
@ -63,6 +100,44 @@ public class DestinationService {
destinationRepository.update(id, userId, destinationUpdateDTO.getRepackingCost(), destinationUpdateDTO.getDisposalCost(), destinationUpdateDTO.getHandlingCost());
}
@Transactional
public void findRouteAndSave(Integer destinationId, Node destination, Node supplier, boolean isUserSupplierNode) {
var routeObjs = routingService.findRoutes(destination, supplier, isUserSupplierNode);
for (var routeObj : routeObjs) {
boolean first = true;
Integer fromNodeId = null;
var premiseRoute = routeObj.getRoute();
premiseRoute.setDestinationId(destinationId);
int routeId = routeRepository.insert(premiseRoute);
for (RouteSectionInformation section : routeObj.getSections()) {
if (first) {
fromNodeId = routeNodeRepository.insert(section.getFromNode());
first = false;
}
var toNode = section.getToNode();
Integer toNodeId = routeNodeRepository.insert(toNode);
var premiseRouteSection = section.getSection();
premiseRouteSection.setRouteId(routeId);
premiseRouteSection.setFromRouteNodeId(fromNodeId);
premiseRouteSection.setToRouteNodeId(toNodeId);
routeSectionRepository.insert(premiseRouteSection);
fromNodeId = toNodeId;
}
}
}
@Transactional
public void deleteAllDestinationsByPremiseId(List<Integer> ids, boolean deleteRoutesOnly) {
ids.forEach(id -> deleteDestinationById(id, deleteRoutesOnly));
@ -96,5 +171,35 @@ public class DestinationService {
}
@Transactional
public void duplicate(Integer fromPremiseId, Integer toPremiseId) {
List<Destination> destinations = destinationRepository.getByPremiseId(fromPremiseId);
for(Destination destination : destinations) {
destination.setPremiseId(toPremiseId);
Integer destinationId = destinationRepository.insert(destination);
for(Route route : routeRepository.getByDestinationId(destination.getId())) {
route.setDestinationId(destinationId);
Integer routeId = routeRepository.insert(route);
for(RouteSection section : routeSectionRepository.getByRouteId(route.getId())) {
section.setRouteId(routeId);
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
RouteNode toNode = routeNodeRepository.getById(section.getToRouteNodeId()).orElseThrow();
Integer fromNodeId = routeNodeRepository.insert(fromNode);
Integer toNodeId = routeNodeRepository.insert(toNode);
section.setFromRouteNodeId(fromNodeId);
section.setToRouteNodeId(toNodeId);
routeSectionRepository.insert(section);
}
}
}
}
}

View file

@ -7,7 +7,7 @@ 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.repositories.premise.PremiseRepository;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import org.springframework.stereotype.Service;
@ -16,7 +16,6 @@ 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 {
@ -26,7 +25,7 @@ public class PremisesService {
private final DimensionTransformer dimensionTransformer;
private final DestinationService destinationService;
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, DestinationService destinationService) {
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService) {
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
this.dimensionTransformer = dimensionTransformer;
@ -53,7 +52,7 @@ public class PremisesService {
//TODO move to other service
public Integer startCalculation(List<Integer> premises) {
return null;
}
public HashMap<String, String> updatePackaging(PackagingUpdateDTO packagingDTO) {

View file

@ -2,26 +2,103 @@ 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.packaging.PackagingDimension;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.properties.PackagingProperty;
import de.avatic.lcc.model.properties.PackagingPropertyMappingId;
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.PremiseRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.CustomApiService;
import de.avatic.lcc.service.access.PremisesService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.*;
@Service
public class ChangeMaterialService {
private final PremiseRepository premiseRepository;
private final PremisesService premisesService;
private final CustomApiService customApiService;
private final NodeRepository nodeRepository;
private final UserNodeRepository userNodeRepository;
private final PackagingRepository packagingRepository;
private final PackagingDimensionRepository packagingDimensionRepository;
private final PackagingPropertiesRepository packagingPropertiesRepository;
public ChangeMaterialService(PremiseRepository premiseRepository, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository) {
this.premiseRepository = premiseRepository;
this.premisesService = premisesService;
this.customApiService = customApiService;
this.nodeRepository = nodeRepository;
this.userNodeRepository = userNodeRepository;
this.packagingRepository = packagingRepository;
this.packagingDimensionRepository = packagingDimensionRepository;
this.packagingPropertiesRepository = packagingPropertiesRepository;
}
@Transactional
public List<PremiseDetailDTO> setMaterial(SetDataDTO setMaterialDTO) {
//0. check if one of the given premises contains the same supplier id, and abort if any duplicate would emerge
public List<PremiseDetailDTO> setMaterial(SetDataDTO dto) {
Integer userId = 1; /* TODO get user id */
//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)
Integer materialId = dto.getMaterialId();
List<Integer> premiseIds = dto.getPremiseId();
//4. Deliver Premise Detail
if (materialId == null || premiseIds == null || premiseIds.isEmpty())
throw new IllegalArgumentException("No supplier supplierNodeId or premises given");
// get all premises first.
List<Premise> allPremises = premiseRepository.getPremisesById(premiseIds);
// find resulting duplicates, split into "keep" and "to be deleted".
Map<Integer, Premise> uniqueMap = new HashMap<>();
List<Premise> premisesToBeDeleted = new ArrayList<>();
allPremises.forEach(p -> {
if (null != uniqueMap.putIfAbsent(p.getMaterialId(), p)) premisesToBeDeleted.add(p);
});
Collection<Premise> 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(), materialId, p.getSupplierNodeId())).flatMap(List::stream).toList());
if(dto.isUpdateMasterData()) {
for (var premise : premisesToProcess) {
var countryId = dto.isUserSupplierNode() ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow().getCountryId() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow().getCountryId();
var tariffRate = customApiService.getTariffRate(premise.getHsCode(), countryId);
premiseRepository.updateTariffRate(premise.getId(), tariffRate);
if (!dto.isUserSupplierNode()) {
var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), dto.getSupplierNodeId());
Optional<PackagingDimension> dimension = packagingDimensionRepository.getById(packaging.getFirst().getHuId());
if (dimension.isPresent()) {
boolean stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), userId, dimension.get(), stackable, mixable);
}
}
}
}
// actually update materialId.
premiseRepository.setMaterialId(premisesToProcess.stream().map(Premise::getId).toList(), materialId);
return null;
//delete all conflicting premises:
premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
}
}

View file

@ -6,15 +6,14 @@ 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.premises.route.RouteSectionInformation;
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.premise.*;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.CustomApiService;
import de.avatic.lcc.service.access.DestinationService;
@ -34,13 +33,15 @@ public class ChangeSupplierService {
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;
private final RouteRepository routeRepository;
private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository;
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) {
public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository) {
this.premiseRepository = premiseRepository;
this.destinationService = destinationService;
this.routingService = routingService;
@ -48,11 +49,13 @@ public class ChangeSupplierService {
this.customApiService = customApiService;
this.nodeRepository = nodeRepository;
this.userNodeRepository = userNodeRepository;
this.materialRepository = materialRepository;
this.packagingRepository = packagingRepository;
this.packagingDimensionRepository = packagingDimensionRepository;
this.packagingPropertiesRepository = packagingPropertiesRepository;
this.destinationRepository = destinationRepository;
this.routeRepository = routeRepository;
this.routeSectionRepository = routeSectionRepository;
this.routeNodeRepository = routeNodeRepository;
}
@ -92,8 +95,9 @@ public class ChangeSupplierService {
//recalculate routes:
for (Premise premise : premisesToProcess) {
List<Destination> destination = destinationRepository.getByPremiseId(premise.getId());
for( Destination d : destination ) {
routingService.findRoutes(d.getId(), supplierNodeId);
for (Destination d : destination) {
Node destinationNode = nodeRepository.getById(d.getDestinationNodeId()).orElseThrow();
destinationService.findRouteAndSave(d.getId(), destinationNode, supplier, dto.isUserSupplierNode() );
}
}
@ -108,15 +112,11 @@ public class ChangeSupplierService {
Optional<PackagingDimension> dimension = packagingDimensionRepository.getById(packaging.getFirst().getHuId());
if (dimension.isPresent()) {
Optional<PackagingProperty> stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name());
Optional<PackagingProperty> 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()));
boolean stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), userId, dimension.get(), stackable, mixable);
}
}
premisesService.fillPackaging(premisesToProcess.stream().map(Premise::getId).toList());
}
}
@ -132,4 +132,6 @@ public class ChangeSupplierService {
}
}

View file

@ -1,13 +1,139 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.PremiseState;
import de.avatic.lcc.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.premise.RouteRepository;
import de.avatic.lcc.service.access.DestinationService;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@Service
public class PremiseCreationService {
private final PremiseRepository premiseRepository;
private final PremiseTransformer premiseTransformer;
private final DestinationService destinationService;
public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService) {
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
this.destinationService = destinationService;
}
public List<PremiseDetailDTO> createPremises(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, boolean createEmpty) {
return null;
Integer userId = 1; //TODO get user id
List<TemporaryPremise> foundPremises = new ArrayList<>(premiseRepository.getPremisesByMaterialIdsAndSupplierIds(materialIds, supplierIds, userSupplierIds, userId, createEmpty).stream().map(TemporaryPremise::new).toList());
List<TemporaryPremise> toBeCopied = foundPremises.stream().filter(p -> (!p.getPremise().getState().equals(PremiseState.DRAFT) || !Objects.equals(p.getPremise().getUserId(), userId))).toList();
List<TemporaryPremise> toBeCreated = materialIds.stream()
.flatMap(materialId -> {
Stream<TemporaryPremise> supplierCombinations = supplierIds.stream()
.map(supplierId -> new TemporaryPremise(materialId, supplierId, null, false));
Stream<TemporaryPremise> userSupplierCombinations =
(userSupplierIds != null && !userSupplierIds.isEmpty()) ?
userSupplierIds.stream()
.map(userSupplierId -> new TemporaryPremise(materialId, null, userSupplierId, true)) :
Stream.empty();
return Stream.concat(supplierCombinations, userSupplierCombinations);
})
// filter existing combinations.
.filter(p -> foundPremises.stream()
.noneMatch(found ->
Objects.equals(found.materialId, p.materialId) &&
Objects.equals(found.supplierId, p.supplierId) &&
Objects.equals(found.userSupplierId, p.userSupplierId)
)
)
.toList();
List<Integer> premiseIds = new ArrayList<>(toBeCreated.stream().map(p -> premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), userId)).toList());
for( TemporaryPremise p : toBeCopied ) {
Integer id = premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), userId);
destinationService.duplicate(p.getPremise().getId(), id);
premiseIds.add(id);
}
return premiseRepository.getPremisesById(premiseIds).stream().map(premiseTransformer::toPremiseDetailDTO).toList();
}
private static class TemporaryPremise {
private Integer materialId;
private Integer supplierId;
private Integer userSupplierId;
private boolean isUserSupplier;
private Premise premise;
public Integer getMaterialId() {
return materialId;
}
public void setMaterialId(Integer materialId) {
this.materialId = materialId;
}
public Integer getSupplierId() {
return supplierId;
}
public void setSupplierId(Integer supplierId) {
this.supplierId = supplierId;
}
public Integer getUserSupplierId() {
return userSupplierId;
}
public void setUserSupplierId(Integer userSupplierId) {
this.userSupplierId = userSupplierId;
}
public boolean isUserSupplier() {
return isUserSupplier;
}
public void setUserSupplier(boolean userSupplier) {
isUserSupplier = userSupplier;
}
public void setPremise(Premise premise) {
this.premise = premise;
}
public TemporaryPremise(Premise premise) {
this.premise = premise;
this.materialId = premise.getMaterialId();
this.supplierId = premise.getSupplierNodeId();
this.userSupplierId = premise.getUserSupplierNodeId();
this.isUserSupplier = premise.getUserSupplierNodeId() != null;
}
public TemporaryPremise(Integer materialId, Integer supplierId, Integer userSupplierId, boolean isUserSupplier) {
this.materialId = materialId;
this.supplierId = supplierId;
this.userSupplierId = userSupplierId;
this.isUserSupplier = isUserSupplier;
this.premise = null;
}
public Premise getPremise() {
return premise;
}
}
}

View file

@ -1,12 +1,101 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
import de.avatic.lcc.model.materials.Material;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.service.transformer.generic.MaterialTransformer;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Service for analyzing search strings and retrieving materials and their associated suppliers.
* This service uses repositories and transformers to process and map database entities
* to DTOs for the result.
*/
@Service
public class PremiseSearchStringAnalyzerService {
private static final String PART_NUMBER_REGEX = "([a-zA-Z0-9][a-zA-Z0-9\\-]{4,11})";
private static final Pattern PART_NUMBER_PATTERN = Pattern.compile(PART_NUMBER_REGEX);
private final MaterialRepository materialRepository;
private final NodeRepository nodeRepository;
private final PremiseRepository premiseRepository;
private final NodeTransformer nodeTransformer;
private final MaterialTransformer materialTransformer;
/**
* Constructor for the PremiseSearchStringAnalyzerService, initializing the required repositories
* and transformers for analyzing search strings and processing materials and suppliers.
*
* @param materialRepository Repository for accessing material entities from the database.
* @param nodeRepository Repository for accessing node entities (suppliers) from the database.
* @param premiseRepository Repository for accessing premise-related data, including suppliers associated with materials.
* @param nodeTransformer Transformer for mapping database node entities (suppliers) to DTOs.
* @param materialTransformer Transformer for mapping database material entities to DTOs.
*/
public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer) {
this.materialRepository = materialRepository;
this.nodeRepository = nodeRepository;
this.premiseRepository = premiseRepository;
this.nodeTransformer = nodeTransformer;
this.materialTransformer = materialTransformer;
}
/**
* Analyzes the provided search string to find materials and associated suppliers.
* Processes the search string to extract part numbers, retrieves the associated materials,
* and identifies the suppliers connected to the materials. These results are returned
* in the form of a DTO containing lists of materials and suppliers.
*
* @param search The search string potentially containing part numbers used to find
* materials and their associated suppliers.
*
* @return A {@code PremiseSearchResultDTO} containing lists of material DTOs, suppliers,
* and user-specific suppliers related to the identified materials.
*/
public PremiseSearchResultDTO findMaterialAndSuppliers(String search) {
return null;
List<Material> material = materialRepository.getByPartNumbers(findPartNumbers(search));
List<Integer> materialIds = material.stream().map(Material::getId).toList();
// find suppliers associated with this material.
List<Integer> supplierIds = premiseRepository.findAssociatedSuppliers(materialIds);
List<Integer> userSupplierIds = premiseRepository.findAssociatedUserSuppliers(materialIds);
var dto = new PremiseSearchResultDTO();
dto.setMaterials(material.stream().map(materialTransformer::toMaterialDTO).toList());
dto.setSupplier(nodeRepository.getByIds(supplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
dto.setUserSupplier(nodeRepository.getByIds(userSupplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
return dto;
}
/**
* Extracts part numbers from the provided search string based on a predefined pattern.
*
* @param search The input string that potentially contains part numbers to be extracted.
* @return A collection of unique part numbers found within the input string.
*/
public static Collection<String> findPartNumbers(String search) {
Set<String> partNumbers = new HashSet<>();
Matcher matcher = PART_NUMBER_PATTERN.matcher(search);
// Find all matches
while (matcher.find()) {
// Get the match from group 1 (inside the lookahead)
String partNumber = matcher.group(1);
partNumbers.add(partNumber);
}
return partNumbers;
}
}

View file

@ -1,796 +0,0 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.generic.RouteType;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.premises.route.Route;
import de.avatic.lcc.model.premises.route.RouteInformation;
import de.avatic.lcc.model.premises.route.RouteNode;
import de.avatic.lcc.model.premises.route.RouteSection;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.model.rates.ContainerRate;
import de.avatic.lcc.model.rates.ContainerRateType;
import de.avatic.lcc.model.rates.MatrixRate;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
import de.avatic.lcc.repositories.rates.MatrixRateRepository;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class RoutingService2 {
private final MatrixRateRepository matrixRateRepository;
private final ChainResolver chainResolver;
private final NodeRepository nodeRepository;
private final ContainerRateRepository containerRateRepository;
private final DistanceService distanceService;
private final PropertyRepository propertyRepository;
public RoutingService2(MatrixRateRepository matrixRateRepository, ChainResolver chainResolver, NodeRepository nodeRepository, ContainerRateRepository containerRateRepository, DistanceService distanceService, PropertyRepository propertyRepository) {
this.matrixRateRepository = matrixRateRepository;
this.chainResolver = chainResolver;
this.nodeRepository = nodeRepository;
this.containerRateRepository = containerRateRepository;
this.distanceService = distanceService;
this.propertyRepository = propertyRepository;
}
public List<RouteInformation> findRoute(Node destination, Node source, boolean sourceIsUserNode) {
List<RouteInformation> routeInformation = new ArrayList<>();
TemporaryContainer container = new TemporaryContainer(source, destination);
/*
* Get the source and destination node from database.
* Check if there is a matrix rate for the source country.
*/
matrixRateRepository.getByCountryIds(source.getCountryId(), source.getCountryId()).ifPresent(container::setSourceMatrixRate);
/*
* Generate recursive all chains starting with the destination node.
* This means all chains within the last node of a chain are connected to the
* existing chain.
*
* Furthermore, it is evaluated that all nodes within the chain do not have chains
* themselves that are in conflict with the chain.
*
* Then get all countries from the end of the destination chains.
*/
List<List<Node>> destinationChains = chainResolver.buildChains(destination.getId());
List<Integer> inboundCountries = destinationChains.stream().filter(chain -> !chain.isEmpty()).map(chain -> chain.getLast().getCountryId()).distinct().toList();
/*
* Get all outbound nodes for the country of the source node. In this first step this includes:
* - all intermediate nodes that have the same country id.
* - all nodes that are explicitly mapped as outbound node for this country id.
*/
List<Node> outboundNodes = nodeRepository.getAllOutboundFor(source.getCountryId());
/*
* Find main runs based on the outbound nodes and the inbound countries found before.
* Find post-runs for the main runs.
*
* Store all information in the TemporaryContainer object.
*/
container.setMainRuns(outboundNodes.stream().collect(Collectors.toMap(Node::getId, n -> containerRateRepository.findRoutesByStartNodeIdAndDestinationCountryId(n.getId(), inboundCountries))));
container.setPostRuns(container.getMainRuns().stream().collect(Collectors.toMap(ContainerRate::getId, containerRateRepository::getPostRunsFor)));
connectDestinationChainAndMainRun(container, outboundNodes, destinationChains);
connectSourceChainAndSource(container);
/*
* At this point all routes with a main run are created.
* We find now the best route per main run and throw away the rest.
*/
findCheapestPerMainRun(container);
/*
* Now we also create routes without main run (matrix rate)
*/
connectDestinationChainDirectly(container, destinationChains);
/*
* finally find and mark the fastest and the cheapest route.
*/
findAndMarkCheapestAndFastest(container);
/*
* Convert to Database model
*/
for (var route : container.getRoutes()) {
RouteInformation routeInformationObj = new RouteInformation();
routeInformationObj.setRoute(mapRoute(route));
routeInformationObj.setRouteSections(mapSections(route.getSections()));
routeInformationObj.setRouteNodes(route.getNodes().stream().map(n -> mapNode(n, n.getId().equals(source.getId()) && sourceIsUserNode)).toList());
routeInformation.add(routeInformationObj);
}
return routeInformation;
}
private RouteNode mapNode(Node node, boolean isUserNode) {
RouteNode routeNode = new RouteNode();
routeNode.setName(node.getName());
routeNode.setCountryId(node.getCountryId());
routeNode.setAddress(node.getAddress());
routeNode.setGeoLng(node.getGeoLng());
routeNode.setGeoLat(node.getGeoLat());
routeNode.setUserNodeId(isUserNode? node.getId() : null);
routeNode.setNodeId(isUserNode ? null : node.getId());
routeNode.setIntermediate(node.getIntermediate() != null ? node.getIntermediate() : false);
routeNode.setDestination(node.getDestination() != null ? node.getIntermediate() : false);
routeNode.setSource(node.getSource() != null ? node.getIntermediate() : false);
routeNode.setOutdated(node.getDeprecated());
return routeNode;
}
private Route mapRoute(TemporaryRouteObject route) {
Route routeObj = new Route();
routeObj.setCheapest(route.isCheapest());
routeObj.setFastest(route.isFastest());
return routeObj;
}
private List<RouteSection> mapSections(List<TemporaryRateObject> sections) {
int index = 1;
List<RouteSection> routeSections = new ArrayList<>();
boolean passedMainRun = false;
for(var section : sections) {
var routeSection = mapSection(section);
if (!section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN) && !section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN)) {
if (!passedMainRun)
routeSection.setPreRun(true);
if (passedMainRun)
routeSection.setPostRun(true);
}
if(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN) || section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN))
passedMainRun = true;
routeSection.setListPosition(index++);
routeSections.add(routeSection);
}
return routeSections;
}
private RouteSection mapSection(TemporaryRateObject section) {
RouteSection routeSection = new RouteSection();
routeSection.setTransportType(mapRouteType(section));
routeSection.setFromRouteNodeId(section.getFromNode().getId());
routeSection.setToRouteNodeId(section.getToNode().getId());
routeSection.setMainRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN));
routeSection.setOutdated(false);
routeSection.setPostRun(false);
routeSection.setPreRun(false);
return routeSection;
}
private RouteType mapRouteType(TemporaryRateObject rate) {
switch(rate.getType()) {
case MATRIX, CONTAINER -> {
return RouteType.ROAD;
}
case POST_RUN -> {
return RouteType.POST_RUN;
}
case MAIN_RUN -> {
return RouteType.valueOf(rate.getContainerRateTye().name());
}
}
return null;
}
private void findAndMarkCheapestAndFastest(TemporaryContainer container) {
var routes = container.getRoutes();
TemporaryRouteObject cheapest = null;
double cheapestCost = Double.MAX_VALUE;
TemporaryRouteObject fastest = null;
double fastestRoute = Double.MAX_VALUE;
for(var route : routes) {
double routeCost = route.getCost();
int leadTime = route.getLeadTime();
if(routeCost < cheapestCost) {
cheapestCost = routeCost;
cheapest = route;
}
if(leadTime < fastestRoute) {
fastestRoute = leadTime;
fastest = route;
}
}
if(cheapest != null) {
cheapest.setCheapest();
}
if(fastest != null) {
fastest.setFastest();
}
}
private void findCheapestPerMainRun(TemporaryContainer container) {
var routesByMainRun = container.getRoutes().stream().collect(Collectors.groupingBy(TemporaryRouteObject::getMainRun));
Collection<TemporaryRouteObject> cheapestRoutes = new ArrayList<>();
for(var mainRun : routesByMainRun.keySet()) {
List<TemporaryRouteObject> routes = routesByMainRun.get(mainRun);
TemporaryRouteObject cheapest = null;
double cheapestCost = Double.MAX_VALUE;
for(var route : routes) {
double routeCost = route.getCost();
if(routeCost < cheapestCost) {
cheapestCost = routeCost;
cheapest = route;
}
}
if(cheapest != null) {
cheapestRoutes.add(cheapest);
}
}
container.overrideRoutes(cheapestRoutes);
}
private void connectDestinationChainDirectly(TemporaryContainer container, List<List<Node>> chains) {
Collection<TemporaryRateObject> rates = container.getRates();
for (var chain : chains) {
var toNode = chain.isEmpty() ? container.getDestinationNode() : chain.getLast();
TemporaryRateObject finalSection = new TemporaryRateObject(container.getSourceNode(), toNode, TemporaryRateObject.TemporaryRateObjectType.MATRIX);
if (rates.contains(finalSection)) {
TemporaryRateObject finalMatrixRateObj = finalSection;
finalSection = container.getRates().stream().filter(r -> r.equals(finalMatrixRateObj)).findFirst().orElse(null);
} else {
var matrixRate = matrixRateRepository.getByCountryIds(container.getSourceNode().getCountryId(), toNode.getCountryId()).orElse(null);
// no matrix rate in database. skip this chain
if (matrixRate == null) {
continue;
}
finalSection.setRate(matrixRate);
finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, true));
rates.add(finalSection);
}
// could not create an temporary rate object -> so just skip here ... (should not happen)
if (finalSection == null) {
continue;
}
// create a route.
boolean routable = true;
TemporaryRouteObject routeObj = new TemporaryRouteObject();
for (int idx = 1; idx < chain.size(); idx++) {
Node startNode = chain.get(idx);
Node endNode = chain.get(idx - 1);
var rate = connectNodes(startNode, endNode, container);
if (rate != null) {
routeObj.addSection(rate);
} else {
// chain is not routable -> discard
routable = false;
break;
}
}
// if the chain is routable -> add the final rate and save the route.
if (routable) {
routeObj.addSection(finalSection);
container.addRoute(routeObj);
}
}
}
private TemporaryRateObject connectNearByNodes(Node chainEnd, List<Node> nearByNodes, TemporaryContainer container) {
for (var nearbyNode : nearByNodes) {
TemporaryRateObject nearByRate = connectNodes(nearbyNode, chainEnd, container);
if (null != nearByRate) {
return nearByRate;
}
}
return null;
}
private void connectSourceChainAndSource(TemporaryContainer container) {
/* get the near-by nodes if no country matrix rate present */
List<Node> nearByNodes = (container.hasSourceMatrixRate()) ? null : nodeRepository.getByDistance(container.getSourceNode(), getRegionRadius());
Collection<TemporaryRouteObject> routes = new ArrayList<>();
for (var route : container.getRoutes()) {
var mainRun = route.getMainRun();
var sourceChains = chainResolver.buildChains(mainRun.getFromNode().getId());
for (var chain : sourceChains) {
Node source = container.getSourceNode();
boolean chainEndIsSource = source.getId().equals(chain.getLast().getId());
// find final section: check if chain end and source node are identical, then check if chain end can be connected to
// source node, if this is not possible use a near-by node
TemporaryRateObject finalSection = (chainEndIsSource) ? null : connectNodes(source, chain.getLast(), container);
finalSection = ((finalSection == null && !chainEndIsSource && nearByNodes != null) ? connectNearByNodes(chain.getLast(), nearByNodes, container) : finalSection);
if (finalSection != null || chainEndIsSource) {
boolean routable = true;
TemporaryRouteObject duplicate = route.clone();
for (int idx = 1; idx < chain.size() - 1; idx++) {
Node startNode = chain.get(idx);
Node endNode = chain.get(idx - 1);
TemporaryRateObject rate = connectNodes(startNode, endNode, container);
if (rate != null) duplicate.addSection(rate);
else {
routable = false;
break;
}
}
if (routable) {
if (finalSection != null) {
// add final section if necessary,
// if last chain node == source node this can be skipped.
duplicate.addSection(finalSection);
if (!finalSection.getFromNode().getId().equals(source.getId())) duplicate.routeOverNearBy();
}
routes.add(duplicate);
}
}
}
}
container.overrideRoutes(routes);
}
private Integer getRegionRadius() {
var property = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.RADIUS_REGION);
return property.map(propertyDTO -> Integer.valueOf(propertyDTO.getCurrentValue())).orElseGet(SystemPropertyMappingId.RADIUS_REGION::getDefaultAsInteger);
}
private void connectDestinationChainAndMainRun(TemporaryContainer container, List<Node> outboundNodes, List<List<Node>> destinationChains) {
/*
* Try to connect everything together:
* - go trough all main runs and adjacent post-runs
* - find any compatible chain:
* - check if chain is routable
* - add post run and main run
*/
for (var mainRun : container.getMainRuns()) {
Node mainRunEndNode = nodeRepository.getById(mainRun.getToNodeId()).orElseThrow();
Node mainRunStartNode = outboundNodes.stream().filter(n -> n.getId().equals(mainRun.getFromNodeId())).findFirst().orElseThrow();
TemporaryRateObject mainRunObj = new TemporaryRateObject(mainRunStartNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN, mainRun);
for (var postRun : container.getPostRuns().get(mainRun.getId())) {
Node postRunEndNode = nodeRepository.getById(postRun.getToNodeId()).orElseThrow();
TemporaryRateObject postRunObj = new TemporaryRateObject(postRunEndNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
for (var chain : destinationChains) {
ChainQuality quality = getChainQuality(chain, postRun, mainRun);
/* if connection quality is bad, do not try to route this and continue. */
if (quality == ChainQuality.FALLBACK) continue;
boolean routable = true;
TemporaryRouteObject routeObj = new TemporaryRouteObject();
for (int idx = 1; idx < chain.size() - quality.getSizeOffset(); idx++) {
Node startNode = chain.get(idx);
Node endNode = chain.get(idx - 1);
var rate = connectNodes(startNode, endNode, container);
if (rate != null) {
routeObj.addSection(rate);
} else {
// chain is not routable -> discard
routable = false;
break;
}
}
if (routable) {
routeObj.addPostRunSection(postRunObj);
routeObj.addMainRunSection(mainRunObj);
container.addRoute(routeObj);
}
}
}
}
}
private ChainQuality getChainQuality(List<Node> chain, ContainerRate postRun, ContainerRate mainRun) {
if (chain.getLast().getId().equals(postRun.getToNodeId())) {
return ChainQuality.MEDIUM;
} else if (chain.getLast().getId().equals(postRun.getFromNodeId()) && chain.get(chain.size() - 2).getId().equals(postRun.getToNodeId())) {
return ChainQuality.HIGH;
} else if (chain.getLast().getCountryId().equals(postRun.getToCountryId())) {
return ChainQuality.LOW;
}
return ChainQuality.FALLBACK;
}
private TemporaryRateObject connectNodes(Node startNode, Node endNode, TemporaryContainer container) {
var containerRateObj = new TemporaryRateObject(startNode, endNode, TemporaryRateObject.TemporaryRateObjectType.CONTAINER);
var matrixRateObj = new TemporaryRateObject(startNode, endNode, TemporaryRateObject.TemporaryRateObjectType.MATRIX);
if (container.getRates().contains(containerRateObj))
return container.getRates().stream().filter(r -> r.equals(containerRateObj)).findFirst().orElseThrow();
if (container.getRates().contains(matrixRateObj))
return container.getRates().stream().filter(r -> r.equals(matrixRateObj)).findFirst().orElseThrow();
Optional<ContainerRate> containerRate = containerRateRepository.findRoute(startNode.getId(), endNode.getId(), ContainerRateType.ROAD);
if (containerRate.isPresent()) {
containerRateObj.setRate(containerRate.get());
container.getRates().add(containerRateObj);
return containerRateObj;
} else {
Optional<MatrixRate> matrixRate = matrixRateRepository.getByCountryIds(startNode.getCountryId(), endNode.getCountryId());
if (matrixRate.isPresent()) {
matrixRateObj.setRate(matrixRate.get());
matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, true));
container.getRates().add(matrixRateObj);
return matrixRateObj;
} else {
return null;
}
}
}
private enum ChainQuality {
HIGH(1), MEDIUM(0), LOW(0), FALLBACK(0);
private final int sizeOffset;
ChainQuality(int sizeOffset) {
this.sizeOffset = sizeOffset;
}
public int getSizeOffset() {
return sizeOffset;
}
}
private static class TemporaryContainer {
/*
* Set to lookup route sections. Generated from node pairs.
*/
private final Set<TemporaryRateObject> rates = new HashSet<>();
/*
* Routes that are build within the routing service.
*/
private final Collection<TemporaryRouteObject> routes = new ArrayList<>();
/*
* Source and destination node
*/
private final Node source;
private final Node destination;
/*
* mainRuns and postRuns retrieved from database.
*/
private Map<Integer, List<ContainerRate>> mainRuns;
private Map<Integer, List<ContainerRate>> postRuns;
private MatrixRate sourceMatrixRate;
public TemporaryContainer(Node source, Node destination) {
this.source = source;
this.destination = destination;
this.mainRuns = null;
this.postRuns = null;
this.sourceMatrixRate = null;
}
public Collection<TemporaryRateObject> getRates() {
return rates;
}
public List<ContainerRate> getMainRuns(Integer outboundNodeId) {
return mainRuns.get(outboundNodeId);
}
public List<ContainerRate> getMainRuns() {
return mainRuns.values().stream().flatMap(Collection::stream).toList();
}
public void setMainRuns(Map<Integer, List<ContainerRate>> mainRuns) {
this.mainRuns = mainRuns;
}
public Map<Integer, List<ContainerRate>> getPostRuns() {
return postRuns;
}
public void setPostRuns(Map<Integer, List<ContainerRate>> postRuns) {
this.postRuns = postRuns;
}
public void addRoute(TemporaryRouteObject route) {
this.routes.add(route);
}
public Collection<TemporaryRouteObject> getRoutes() {
return routes;
}
public Node getSourceNode() {
return source;
}
public Node getDestinationNode() {
return destination;
}
public void setSourceMatrixRate(MatrixRate sourceMatrixRate) {
this.sourceMatrixRate = sourceMatrixRate;
}
public boolean hasSourceMatrixRate() {
return sourceMatrixRate != null;
}
public void overrideRoutes(Collection<TemporaryRouteObject> routes) {
this.routes.clear();
this.routes.addAll(routes);
}
}
private static class TemporaryRouteObject {
private final List<TemporaryRateObject> sections;
private TemporaryRateObject mainRun;
private TemporaryRateObject postRun;
private boolean nearBy = false;
private boolean isCheapest = false;
private boolean isFastest = false;
public TemporaryRouteObject() {
sections = new ArrayList<>();
}
public List<TemporaryRateObject> getSections() {
return sections;
}
public void addSection(TemporaryRateObject section) {
this.sections.add(section);
}
public void addMainRunSection(TemporaryRateObject mainRun) {
addSection(mainRun);
this.mainRun = mainRun;
}
public void addPostRunSection(TemporaryRateObject postRun) {
addSection(postRun);
this.postRun = postRun;
}
public TemporaryRateObject getMainRun() {
return mainRun;
}
public void setMainRun(TemporaryRateObject mainRun) {
this.mainRun = mainRun;
}
public TemporaryRateObject getPostRun() {
return postRun;
}
@Override
public TemporaryRouteObject clone() {
TemporaryRouteObject clone = new TemporaryRouteObject();
clone.sections.addAll(sections);
clone.mainRun = mainRun;
clone.postRun = postRun;
clone.nearBy = nearBy;
clone.isCheapest = isCheapest;
clone.isFastest = isFastest;
return clone;
}
public void routeOverNearBy() {
this.nearBy = true;
}
public double getCost() {
return sections.stream().mapToDouble(TemporaryRateObject::getCost).sum();
}
public void setCheapest() {
this.isCheapest = true;
}
public void setFastest() {
this.isFastest = true;
}
public int getLeadTime() {
return sections.stream().mapToInt(TemporaryRateObject::getLeadTime).sum();
}
public List<Node> getNodes() {
List<Node> nodes = new ArrayList<>();
for(var section : sections.reversed()) {
if(sections.getFirst().equals(section))
nodes.add(section.getFromNode());
nodes.add(section.getToNode());
}
return nodes;
}
public Boolean isCheapest() {
return isCheapest;
}
public Boolean isFastest() {
return isFastest;
}
}
private static class TemporaryRateObject {
private final Node fromNode;
private final Node toNode;
private MatrixRate matrixRate;
private double approxDistance;
private ContainerRate containerRate;
private TemporaryRateObjectType type;
public TemporaryRateObject(Node fromNode, Node toNode, TemporaryRateObjectType type) {
this.fromNode = fromNode;
this.toNode = toNode;
this.type = type;
}
public TemporaryRateObject(Node fromNode, Node toNode, TemporaryRateObjectType type, ContainerRate rate) {
this.fromNode = fromNode;
this.toNode = toNode;
this.type = type;
this.containerRate = rate;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
TemporaryRateObject that = (TemporaryRateObject) o;
if (this.type.equals(that.type)) {
if (this.type.equals(TemporaryRateObjectType.MATRIX)) {
return Objects.equals(this.fromNode.getCountryId(), that.fromNode.getCountryId()) && Objects.equals(this.toNode.getCountryId(), that.toNode.getCountryId());
} else if (this.type.equals(TemporaryRateObjectType.CONTAINER) || this.type.equals(TemporaryRateObjectType.MAIN_RUN) || this.type.equals(TemporaryRateObjectType.POST_RUN)) {
return Objects.equals(this.fromNode.getId(), that.fromNode.getId()) && Objects.equals(this.toNode.getId(), that.toNode.getId());
}
}
return false;
}
@Override
public int hashCode() {
if (matrixRate != null) return Objects.hash(matrixRate.getFromCountry(), matrixRate.getToCountry());
if (containerRate != null) return Objects.hash(containerRate.getFromNodeId(), containerRate.getToNodeId());
return Objects.hash(null, null);
}
public void setRate(ContainerRate containerRate) {
this.containerRate = containerRate;
this.type = TemporaryRateObjectType.CONTAINER;
}
public void setRate(MatrixRate matrixRate) {
this.matrixRate = matrixRate;
this.type = TemporaryRateObjectType.MATRIX;
}
public double getApproxDistance() {
return approxDistance;
}
public void setApproxDistance(double distance) {
this.approxDistance = distance;
}
public Node getFromNode() {
return fromNode;
}
public Node getToNode() {
return toNode;
}
public double getCost() {
if(type.equals(TemporaryRateObjectType.MATRIX)) {
return matrixRate.getRate().doubleValue() * approxDistance;
} else {
return containerRate.getRateFeu().doubleValue();
}
}
public int getLeadTime() {
if (type.equals(TemporaryRateObjectType.MATRIX)) {
return 3;
}
return containerRate.getLeadTime();
}
public TemporaryRateObjectType getType() {
return type;
}
public ContainerRateType getContainerRateTye() {
return containerRate.getType();
}
private enum TemporaryRateObjectType {
MATRIX, CONTAINER, POST_RUN, MAIN_RUN;
}
}
}

View file

@ -373,6 +373,8 @@ CREATE TABLE IF NOT EXISTS premise_destination
premise_id INT NOT NULL,
annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces',
destination_node_id INT NOT NULL,
is_d2d BOOLEAN DEFAULT FALSE,
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
repacking_cost DECIMAL(15, 2) DEFAULT NULL,
handling_cost DECIMAL(15, 2) DEFAULT NULL,
disposal_cost DECIMAL(15, 2) DEFAULT NULL,