Refactor packaging and material update logic in ChangeSupplierService and PremiseCreationService, optimize repository method calls, and simplify PremiseControllerIntegrationTest by utilizing PremiseTestsHelper.

This commit is contained in:
Jan 2025-07-25 12:39:08 +02:00
parent c03cbfb774
commit eab6ed2e1b
19 changed files with 1067 additions and 609 deletions

View file

@ -136,18 +136,18 @@ public class PremiseController {
return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO));
}
@GetMapping({"/destination({id}", "/destination({id}/"})
@GetMapping({"/destination/{id}", "/destination/{id}/"})
public ResponseEntity<DestinationDTO> getDestination(@PathVariable Integer id) {
return ResponseEntity.ok(destinationService.getDestination(id));
}
@PutMapping({"/destination({id}", "/destination({id}/"})
public ResponseEntity<Void> updateDestination(@PathVariable Integer id, @RequestParam DestinationUpdateDTO destinationUpdateDTO) {
@PutMapping({"/destination/{id}", "/destination/{id}/"})
public ResponseEntity<Void> updateDestination(@PathVariable @Min(1) Integer id, @RequestBody @Valid DestinationUpdateDTO destinationUpdateDTO) {
destinationService.updateDestination(id, destinationUpdateDTO);
return ResponseEntity.ok().build();
}
@DeleteMapping({"/destination({id}", "/destination({id}/"})
@DeleteMapping({"/destination/{id}", "/destination/{id}/"})
public ResponseEntity<Void> deleteDestination(@PathVariable Integer id) {
destinationService.deleteDestinationById(id, false);
return ResponseEntity.ok().build();

View file

@ -1,5 +1,6 @@
package de.avatic.lcc.dto.calculation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.TransportType;
@ -40,6 +41,8 @@ public class RouteDTO {
this.type = type;
}
@JsonIgnore
public Boolean getSelected() {
return isSelected;
}
@ -48,6 +51,7 @@ public class RouteDTO {
isSelected = selected;
}
@JsonIgnore
public Boolean getCheapest() {
return isCheapest;
}
@ -56,6 +60,7 @@ public class RouteDTO {
isCheapest = cheapest;
}
@JsonIgnore
public Boolean getFastest() {
return isFastest;
}

View file

@ -1,16 +1,29 @@
package de.avatic.lcc.dto.calculation.edit.destination;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Min;
public class DestinationUpdateDTO {
@JsonProperty("repacking_cost")
@DecimalMin(value = "0.00", message = "Amount must be greater than or equal 0")
@Digits(integer = 13, fraction = 2, message = "Amount must have at most 2 decimal places")
private Number repackingCost;
@JsonProperty("handling_cost")
@DecimalMin(value = "0.00", message = "Amount must be greater than or equal 0")
@Digits(integer = 13, fraction = 2, message = "Amount must have at most 2 decimal places")
private Number handlingCost;
@JsonProperty("disposal_cost")
@DecimalMin(value = "0.00", message = "Amount must be greater than or equal 0")
@Digits(integer = 13, fraction = 2, message = "Amount must have at most 2 decimal places")
private Number disposalCost;
@JsonProperty("route_selected_id")
@Min(1)
private Integer routeSelectedId;
public Number getRepackingCost() {

View file

@ -107,7 +107,7 @@ public class RouteNode {
}
public void setNodeId(Integer nodeId) {
if (nodeId == 0) this.nodeId = null;
if (nodeId == null || nodeId == 0) this.nodeId = null;
else this.nodeId = nodeId;
}
@ -116,7 +116,7 @@ public class RouteNode {
}
public void setUserNodeId(Integer userNodeId) {
if (userNodeId == 0) this.userNodeId = null;
if (userNodeId == null || userNodeId == 0) this.userNodeId = null;
else this.userNodeId = userNodeId;
}

View file

@ -1,28 +1,34 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
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 org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
@Service
public class DestinationRepository {
private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public DestinationRepository(JdbcTemplate jdbcTemplate) {
public DestinationRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
@Transactional
public Optional<Destination> getById(Integer id) {
@ -45,9 +51,46 @@ public class DestinationRepository {
}
@Transactional
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 update(Integer id, BigDecimal repackingCost, BigDecimal disposalCost, BigDecimal handlingCost) {
if (id == null) {
throw new InvalidArgumentException("ID cannot be null");
}
StringBuilder sql = new StringBuilder("UPDATE premise_destination SET ");
Map<String, Object> parameters = new HashMap<>();
List<String> setClauses = new ArrayList<>();
// Build dynamic SET clauses based on non-null parameters
if (repackingCost != null) {
setClauses.add("repacking_cost = :repackingCost");
parameters.put("repackingCost", repackingCost);
}
if (disposalCost != null) {
setClauses.add("disposal_cost = :disposalCost");
parameters.put("disposalCost", disposalCost);
}
if (handlingCost != null) {
setClauses.add("handling_cost = :handlingCost");
parameters.put("handlingCost", handlingCost);
}
// If no parameters to update, return early
if (setClauses.isEmpty()) {
return;
}
// Complete the SQL statement
sql.append(String.join(", ", setClauses));
sql.append(" WHERE id = :id");
parameters.put("id", id);
int rowsAffected = namedParameterJdbcTemplate.update(sql.toString(), parameters);
if (rowsAffected == 0) {
throw new DatabaseException("No premise_destination found with id: " + id);
}
}
@ -70,7 +113,7 @@ public class DestinationRepository {
WHERE pd.id = ?""";
List<Integer> userId = jdbcTemplate.query(query, (rs, rowNum) -> {
return rs.getInt("userId");
return rs.getInt("user_id");
}, id);
if (userId.isEmpty())
@ -127,6 +170,15 @@ public class DestinationRepository {
}
@Transactional
public void checkOwner(Integer id, Integer userId) {
var ownerId = getOwnerIdById(id);
if (ownerId.isEmpty() || !ownerId.get().equals(userId)) {
throw new ForbiddenException("Access violation. Accessing destination with id = " + id);
}
}
private static class DestinationMapper implements RowMapper<Destination> {
@Override

View file

@ -11,6 +11,7 @@ import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@ -160,9 +161,9 @@ public class PremiseRepository {
}
@Transactional
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension hu, PackagingDimension shu, Boolean stackable, Boolean mixable) {
public void updatePackaging(List<Integer> premiseIds, PackagingDimension hu, PackagingDimension shu, Boolean stackable, Boolean mixable) {
if (premiseIds == null || premiseIds.isEmpty() || userId == null || hu == null) {
if (premiseIds == null || premiseIds.isEmpty() || hu == null) {
return;
}
@ -179,7 +180,6 @@ public class PremiseRepository {
params.addValue("unitCount", hu.getContentUnitCount()*shu.getContentUnitCount());
params.addValue("stackable", isStackable);
params.addValue("mixable", isMixable);
params.addValue("userId", userId);
params.addValue("premiseIds", premiseIds);
String sql = """
@ -193,7 +193,7 @@ public class PremiseRepository {
hu_unit_count = :unitCount,
hu_stackable = :stackable,
hu_mixable = :mixable
WHERE user_id = :userId AND id IN (:premiseIds)
WHERE id IN (:premiseIds)
""";
namedParameterJdbcTemplate.update(sql, params);
@ -201,15 +201,14 @@ public class PremiseRepository {
}
@Transactional
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension hu, Boolean stackable, Boolean mixable) {
public void updatePackaging(List<Integer> premiseIds, PackagingDimension hu, Boolean stackable, Boolean mixable) {
if (premiseIds == null || premiseIds.isEmpty() || userId == null) {
if (premiseIds == null || premiseIds.isEmpty()) {
return;
}
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("userId", userId);
params.addValue("premiseIds", premiseIds);
StringBuilder sqlBuilder = new StringBuilder("UPDATE premise SET ");
@ -272,7 +271,7 @@ public class PremiseRepository {
// Build the complete SQL
sqlBuilder.append(String.join(", ", setClauses));
sqlBuilder.append(" WHERE user_id = :userId AND id IN (:premiseIds)");
sqlBuilder.append(" WHERE id IN (:premiseIds)");
String sql = sqlBuilder.toString();
var affectedRows = namedParameterJdbcTemplate.update(sql, params);
@ -283,7 +282,7 @@ public class PremiseRepository {
}
@Transactional
public void updateMaterial(List<Integer> premiseIds, Integer userId, String hsCode, BigDecimal tariffRate) {
public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate) {
// Build the SET clause dynamically based on non-null parameters
List<String> setClauses = new ArrayList<>();
@ -304,9 +303,6 @@ public class PremiseRepository {
return;
}
// Add userId to parameters
parameters.add(userId);
// Add premiseIds to parameters
parameters.addAll(premiseIds);
@ -314,7 +310,7 @@ public class PremiseRepository {
String setClause = String.join(", ", setClauses);
String query = "UPDATE premise SET " + setClause +
" WHERE user_id = ? AND id IN (" + placeholders + ")";
" WHERE id IN (" + placeholders + ")";
var affectedRows = jdbcTemplate.update(
query,
@ -337,7 +333,7 @@ public class PremiseRepository {
}
@Transactional
public void updatePrice(List<Integer> premiseIds, int userId, BigDecimal price, Boolean includeFcaFee, BigDecimal overseaShare) {
public void updatePrice(List<Integer> premiseIds, BigDecimal price, Boolean includeFcaFee, BigDecimal overseaShare) {
// Build dynamic SET clause based on non-null parameters
List<String> setClauses = new ArrayList<>();
if (price != null) {
@ -358,7 +354,7 @@ public class PremiseRepository {
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
String setClause = String.join(", ", setClauses);
String query = "UPDATE premise SET " + setClause + " WHERE user_id = ? AND id IN (" + placeholders + ")";
String query = "UPDATE premise SET " + setClause + " WHERE id IN (" + placeholders + ")";
var affectedRows = jdbcTemplate.update(
query,
@ -376,9 +372,6 @@ public class PremiseRepository {
ps.setBigDecimal(parameterIndex++, overseaShare);
}
// Set user_id parameter
ps.setInt(parameterIndex++, userId);
// Set premise ID parameters
for (Integer premiseId : premiseIds) {
ps.setInt(parameterIndex++, premiseId);
@ -608,6 +601,19 @@ public class PremiseRepository {
throw new DatabaseException("No premise found with id " + id);
}
@Transactional
public void checkOwner(List<Integer> premiseIds, int userId) {
String query = """
SELECT id FROM premise WHERE premise.id IN (?) AND user_id <> ?
""";
var otherIds = jdbcTemplate.queryForList(query, Integer.class, premiseIds, userId);
if(!otherIds.isEmpty()) {
throw new ForbiddenException("Access violation. Cannot open premise with ids = " + otherIds);
}
}
/**
* Encapsulates SQL query building logic

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.premises.route.Route;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
@ -78,6 +79,23 @@ public class RouteRepository {
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
public void updateSelectedByDestinationId(Integer destinationId, Integer selectedRouteId) {
String deselectQuery = """
UPDATE premise_route SET is_selected = FALSE WHERE is_selected = TRUE AND premise_destination_id = ?
""";
String selectQuery = """
UPDATE premise_route SET is_selected = TRUE WHERE id = ?
""";
jdbcTemplate.update(deselectQuery, destinationId);
var affectedRowsSelect = jdbcTemplate.update(selectQuery, selectedRouteId);
if (1 != affectedRowsSelect) {
throw new DatabaseException("Unable to select route with id " + selectedRouteId);
}
}
private static class RouteMapper implements RowMapper<Route> {
@Override
public Route mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -2,10 +2,12 @@ package de.avatic.lcc.repositories.users;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -110,6 +112,19 @@ public class UserNodeRepository {
return Optional.of(ids.getFirst());
}
@Transactional
public void checkOwner(List<Integer> userNodeIds, Integer userId) {
String query = """
SELECT id FROM sys_user_node WHERE id IN (?) AND user_id <> ?
""";
var otherIds = jdbcTemplate.queryForList(query, Integer.class, userNodeIds, userId);
if(!otherIds.isEmpty()) {
throw new ForbiddenException("Access violation. Cannot open user nodes with ids = " + otherIds);
}
}
private static class NodeMapper implements RowMapper<Node> {
@Override

View file

@ -1,21 +1,26 @@
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.DestinationCreateDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.premises.route.*;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.*;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.calculation.RoutingService;
import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.Array;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -28,12 +33,15 @@ public class DestinationService {
private final RouteRepository routeRepository;
private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository;
private final RoutingService routingService;;
private final RoutingService routingService;
;
private final NodeRepository nodeRepository;
private final PremiseRepository premiseRepository;
private final UserNodeRepository userNodeRepository;
private final PropertyRepository propertyRepository;
private final PropertyService propertyService;
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository) {
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository, PropertyRepository propertyRepository, PropertyService propertyService) {
this.destinationRepository = destinationRepository;
this.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository;
@ -43,6 +51,8 @@ public class DestinationService {
this.nodeRepository = nodeRepository;
this.premiseRepository = premiseRepository;
this.userNodeRepository = userNodeRepository;
this.propertyRepository = propertyRepository;
this.propertyService = propertyService;
}
@Transactional
@ -53,8 +63,8 @@ public class DestinationService {
var existingDestinations = destinationRepository.getByPremiseIdsAndNodeId(dto.getPremiseId(), dto.getDestinationNodeId(), userId);
var premisesIdsToProcess = new ArrayList<Integer>();
for(var premiseId : dto.getPremiseId()) {
if(existingDestinations.stream().map(Destination::getPremiseId).noneMatch(id -> id.equals(premiseId))) {
for (var premiseId : dto.getPremiseId()) {
if (existingDestinations.stream().map(Destination::getPremiseId).noneMatch(id -> id.equals(premiseId))) {
premisesIdsToProcess.add(premiseId);
}
}
@ -65,7 +75,7 @@ public class DestinationService {
var destinations = new ArrayList<Destination>();
for(var premise : premisesToProcess) {
for (var premise : premisesToProcess) {
var destination = new Destination();
destination.setDestinationNodeId(dto.getDestinationNodeId());
destination.setPremiseId(premise.getId());
@ -96,12 +106,24 @@ public class DestinationService {
return destinationTransformer.toDestinationDTO(destinationRepository.getById(id).orElseThrow());
}
@Transactional
public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) {
//todo check authorization
Integer userId = 1;
destinationRepository.checkOwner(id, userId);
destinationRepository.update(id, userId, destinationUpdateDTO.getRepackingCost(), destinationUpdateDTO.getDisposalCost(), destinationUpdateDTO.getHandlingCost());
var selectedRouteId = destinationUpdateDTO.getRouteSelectedId();
if (selectedRouteId != null) {
routeRepository.updateSelectedByDestinationId(id, selectedRouteId);
}
destinationRepository.update(id,
destinationUpdateDTO.getRepackingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getRepackingCost().doubleValue()),
destinationUpdateDTO.getDisposalCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getDisposalCost().doubleValue()),
destinationUpdateDTO.getHandlingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getHandlingCost().doubleValue()));
}
@ -154,10 +176,10 @@ public class DestinationService {
Integer userId = 1;
Optional<Integer> ownerId = destinationRepository.getOwnerIdById(id);
if(ownerId.isPresent() && ownerId.get().equals(userId)) {
if (ownerId.isPresent() && ownerId.get().equals(userId)) {
List<Route> routes = routeRepository.getByDestinationId(id);
for(var route : routes) {
for (var route : routes) {
List<RouteSection> 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());
@ -165,13 +187,13 @@ public class DestinationService {
routeRepository.deleteAllById(routes.stream().map(Route::getId).toList());
if(!deleteRoutesOnly)
if (!deleteRoutesOnly)
destinationRepository.deleteById(id);
return;
}
throw new RuntimeException("Not authorized to delete destination with id " + id);
throw new ForbiddenException("Not authorized to delete destination with id " + id);
}
@ -180,16 +202,16 @@ public class DestinationService {
List<Destination> destinations = destinationRepository.getByPremiseId(fromPremiseId);
for(Destination destination : destinations) {
for (Destination destination : destinations) {
destination.setPremiseId(toPremiseId);
Integer destinationId = destinationRepository.insert(destination);
for(Route route : routeRepository.getByDestinationId(destination.getId())) {
for (Route route : routeRepository.getByDestinationId(destination.getId())) {
route.setDestinationId(destinationId);
Integer routeId = routeRepository.insert(route);
for(RouteSection section : routeSectionRepository.getByRouteId(route.getId())) {
for (RouteSection section : routeSectionRepository.getByRouteId(route.getId())) {
section.setRouteId(routeId);
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();

View file

@ -15,6 +15,7 @@ import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.properties.PropertySetRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.calculation.execution.CalculationExecutionService;
import de.avatic.lcc.service.calculation.execution.CalculationStatusService;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
@ -39,8 +40,9 @@ public class PremisesService {
private final PropertySetRepository propertySetRepository;
private final ValidityPeriodRepository validityPeriodRepository;
private final CalculationStatusService calculationStatusService;
private final CalculationExecutionService calculationExecutionService;
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService) {
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService, CalculationExecutionService calculationExecutionService) {
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
this.dimensionTransformer = dimensionTransformer;
@ -50,6 +52,7 @@ public class PremisesService {
this.propertySetRepository = propertySetRepository;
this.validityPeriodRepository = validityPeriodRepository;
this.calculationStatusService = calculationStatusService;
this.calculationExecutionService = calculationExecutionService;
}
@Transactional(readOnly = true)
@ -67,8 +70,10 @@ public class PremisesService {
@Transactional(readOnly = true)
public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) {
//TODO check if user authorized
var userId = 0;
//TODO use real user
var userId = 1;
premiseRepository.checkOwner(premiseIds, userId);
return premiseRepository.getPremisesById(premiseIds).stream().map(premiseTransformer::toPremiseDetailDTO).toList();
}
@ -81,7 +86,7 @@ public class PremisesService {
var validSetId = propertySetRepository.getValidSetId();
var validPeriodId = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("no valid period found that is VALID"));
var calculationIds = new ArrayList<>();
var calculationIds = new ArrayList<Integer>();
premises.forEach(p -> {
CalculationJob job = new CalculationJob();
@ -96,6 +101,8 @@ public class PremisesService {
calculationIds.add(calculationJobRepository.insert(job));
});
calculationIds.forEach(calculationExecutionService::calculateJob);
return calculationStatusService.schedule(calculationIds);
}
@ -103,45 +110,46 @@ public class PremisesService {
return calculationStatusService.getCalculationStatus(processId);
}
public HashMap<String, String> updatePackaging(PackagingUpdateDTO packagingDTO) {
@Transactional
public void updatePackaging(PackagingUpdateDTO packagingDTO) {
//TODO check values. and return errors if needed
var userId = 1; // todo get id from current user.
premiseRepository.checkOwner(packagingDTO.getPremiseIds(), userId);
var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions());
premiseRepository.updatePackaging(packagingDTO.getPremiseIds(), userId, dimensions, packagingDTO.getStackable(), packagingDTO.getMixable());
premiseRepository.updatePackaging(packagingDTO.getPremiseIds(), dimensions, packagingDTO.getStackable(), packagingDTO.getMixable());
return null;
}
public HashMap<String, String> updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
public void updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
//TODO check values. and return errors if needed
var userId = 1; // todo get id from current user.
premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), userId, materialUpdateDTO.getHsCode(), tariffRate);
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), materialUpdateDTO.getHsCode(), tariffRate);
return null;
}
public HashMap<String, String> updatePrice(PriceUpdateDTO priceUpdateDTO) {
public void updatePrice(PriceUpdateDTO priceUpdateDTO) {
//TODO check values. and return errors if needed
var userId = 1; // todo get id from current user.
premiseRepository.checkOwner(priceUpdateDTO.getPremiseIds(), userId);
var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue());
var overseaShare = priceUpdateDTO.getOverseaShare() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue());
premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), userId, price, priceUpdateDTO.getIncludeFcaFee(),overseaShare);
return null;
premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), price, priceUpdateDTO.getIncludeFcaFee(),overseaShare);
}
@Transactional
public void delete(List<Integer> premiseIds) {
//TODO check authorization
var userId = 1;
premiseRepository.checkOwner(premiseIds, userId);
destinationService.deleteAllDestinationsByPremiseId(premiseIds, false);
premiseRepository.deletePremisesById(premiseIds);
}

View file

@ -73,7 +73,7 @@ public class ChainResolver {
}
if (validationObject.valid())
foundChains.add(validationObject.getChain());
foundChains.add(new ArrayList<>(validationObject.getChain()));
} else {
log.warn("Circular reference detected while building predecessor chain for node {}", nodeId);
}

View file

@ -83,7 +83,7 @@ public class ChangeMaterialService {
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);
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), dimension.get(), stackable, mixable);
}
}

View file

@ -113,7 +113,7 @@ public class ChangeSupplierService {
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);
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), dimension.get(), stackable, mixable);
}
}
}

View file

@ -58,6 +58,7 @@ public class PremiseCreationService {
@Transactional
public List<PremiseDetailDTO> createPremises(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, boolean createEmpty) {
Integer userId = 1; //TODO get user id
userNodeRepository.checkOwner(userSupplierIds, userId);
/* Build all resulting premises */
List<TemporaryPremise> premises = Stream.concat(
@ -96,9 +97,9 @@ public class PremiseCreationService {
private void copyPremise(TemporaryPremise p, Integer userId) {
var old = p.getPremise();
premiseRepository.updateMaterial(Collections.singletonList(p.getId()), userId, old.getHsCode(), old.getCustomRate());
premiseRepository.updatePrice(Collections.singletonList(p.getId()), userId, old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), userId, dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
premiseRepository.updateMaterial(Collections.singletonList(p.getId()), old.getHsCode(), old.getCustomRate());
premiseRepository.updatePrice(Collections.singletonList(p.getId()), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
premiseRepository.setPackagingId(p.getId(), old.getId());
}
@ -112,13 +113,13 @@ public class PremiseCreationService {
if (hu.isPresent() && shu.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(p.getId()), userId, hu.get(), shu.get(), stackable, mixable); //TODO clarify if the hu unit count in packaging data is total unit count or shu count (shu*hu or hu)
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), hu.get(), shu.get(), stackable, mixable); //TODO clarify if the hu unit count in packaging data is total unit count or shu count (shu*hu or hu)
premiseRepository.setPackagingId(p.getId(), packaging.getFirst().getId());
}
}
var material = materialRepository.getById(p.getMaterialId());
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), userId, value.getHsCode(), customApiService.getTariffRate(value.getHsCode(), getCountryId(p))));
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), value.getHsCode(), customApiService.getTariffRate(value.getHsCode(), getCountryId(p))));
}

View file

@ -60,6 +60,7 @@ public class RoutingService {
*/
List<List<Node>> destinationChains = chainResolver.buildChains(destination.getId());
destinationChains.forEach(chain -> chain.addFirst(destination));
List<Integer> inboundCountries = destinationChains.stream().filter(chain -> !chain.isEmpty()).map(chain -> chain.getLast().getCountryId()).distinct().toList();
/*
@ -86,7 +87,8 @@ public class RoutingService {
* 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);
// we cannot do this, because we need all the Intermediate Warehouses to show.
//findCheapestPerMainRun(container);
/*
* Now we also create routes without main run (matrix rate)
@ -122,11 +124,11 @@ public class RoutingService {
routeNode.setAddress(node.getAddress());
routeNode.setGeoLng(node.getGeoLng());
routeNode.setGeoLat(node.getGeoLat());
routeNode.setUserNodeId(isUserNode? node.getId() : null);
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.setDestination(node.getDestination() != null ? node.getDestination() : false);
routeNode.setSource(node.getSource() != null ? node.getSource() : false);
routeNode.setOutdated(node.getDeprecated());
return routeNode;
@ -137,6 +139,7 @@ public class RoutingService {
routeObj.setCheapest(route.isCheapest());
routeObj.setFastest(route.isFastest());
routeObj.setSelected(false);
return routeObj;
}
@ -149,11 +152,11 @@ public class RoutingService {
boolean firstSection = true;
RouteNode fromNode = null;
for(var section : sections.reversed()) {
for (var section : sections.reversed()) {
var routeSection = mapSection(section);
if(firstSection) {
fromNode = mapNode(section.getFromNode(),sourceIsUserNode);
if (firstSection) {
fromNode = mapNode(section.getFromNode(), sourceIsUserNode);
firstSection = false;
}
@ -168,7 +171,7 @@ public class RoutingService {
}
if(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN) || section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN))
if (section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN) || section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN))
passedMainRun = true;
routeSection.setListPosition(index++);
@ -189,7 +192,7 @@ public class RoutingService {
routeSection.setMainRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN));
routeSection.setOutdated(false);
routeSection.setPostRun(false);
routeSection.setPostRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN));
routeSection.setPreRun(false);
return routeSection;
@ -198,7 +201,8 @@ public class RoutingService {
private RateType mapRateType(TemporaryRateObject rate) {
if (Objects.requireNonNull(rate.getType()) == TemporaryRateObject.TemporaryRateObjectType.MATRIX) {
if (Objects.requireNonNull(rate.getType()) == TemporaryRateObject.TemporaryRateObjectType.MATRIX
|| Objects.requireNonNull(rate.getType()) == TemporaryRateObject.TemporaryRateObjectType.NEAR_BY) {
return RateType.MATRIX;
}
return RateType.CONTAINER;
@ -206,8 +210,8 @@ public class RoutingService {
private TransportType mapRouteType(TemporaryRateObject rate) {
switch(rate.getType()) {
case CONTAINER, MATRIX -> {
switch (rate.getType()) {
case CONTAINER, MATRIX, NEAR_BY -> {
return TransportType.ROAD;
}
case POST_RUN -> {
@ -230,26 +234,26 @@ public class RoutingService {
TemporaryRouteObject fastest = null;
double fastestRoute = Double.MAX_VALUE;
for(var route : routes) {
for (var route : routes) {
double routeCost = route.getCost();
int leadTime = route.getLeadTime();
if(routeCost < cheapestCost) {
if (routeCost < cheapestCost) {
cheapestCost = routeCost;
cheapest = route;
}
if(leadTime < fastestRoute) {
if (leadTime < fastestRoute) {
fastestRoute = leadTime;
fastest = route;
}
}
if(cheapest != null) {
if (cheapest != null) {
cheapest.setCheapest();
}
if(fastest != null) {
if (fastest != null) {
fastest.setFastest();
}
}
@ -259,21 +263,21 @@ public class RoutingService {
var routesByMainRun = container.getRoutes().stream().collect(Collectors.groupingBy(TemporaryRouteObject::getMainRun));
Collection<TemporaryRouteObject> cheapestRoutes = new ArrayList<>();
for(var mainRun : routesByMainRun.keySet()) {
for (var mainRun : routesByMainRun.keySet()) {
List<TemporaryRouteObject> routes = routesByMainRun.get(mainRun);
TemporaryRouteObject cheapest = null;
double cheapestCost = Double.MAX_VALUE;
for(var route : routes) {
for (var route : routes) {
double routeCost = route.getCost();
if(routeCost < cheapestCost) {
if (routeCost < cheapestCost) {
cheapestCost = routeCost;
cheapest = route;
}
}
if(cheapest != null) {
if (cheapest != null) {
cheapestRoutes.add(cheapest);
}
}
@ -361,22 +365,42 @@ public class RoutingService {
for (var route : container.getRoutes()) {
var mainRun = route.getMainRun();
var sourceChains = chainResolver.buildChains(mainRun.getFromNode().getId());
sourceChains.forEach(chain -> chain.addFirst(mainRun.getFromNode()));
for (var chain : sourceChains) {
Node source = container.getSourceNode();
boolean chainEndIsSource = source.getId().equals(chain.getLast().getId());
Node nearByNode = null;
TemporaryRateObject finalSection = null;
SourceConnectionType connectionType = SourceConnectionType.NONE;
// 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 (source.getId().equals(chain.getLast().getId())) {
connectionType = SourceConnectionType.CHAIN_END_IS_SOURCE_NODE;
} else if (nearByNodes != null) {
nearByNode = nearByNodes.stream().filter(n -> n.getId().equals(chain.getLast().getId())).findFirst().orElse(null);
if (nearByNode != null) {
connectionType = SourceConnectionType.CHAIN_END_IS_NEAR_BY_NODE;
}
}
if (connectionType == SourceConnectionType.NONE) {
// 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
finalSection = connectNodes(source, chain.getLast(), container);
if (finalSection != null)
connectionType = SourceConnectionType.FINAL_SECTION_WITH_SOURCE_NODE;
}
if (connectionType == SourceConnectionType.NONE) {
finalSection = connectNearByNodes(chain.getLast(), nearByNodes, container);
if (finalSection != null) {
connectionType = SourceConnectionType.FINAL_SECTION_WITH_NEAR_BY_NODE;
nearByNode = finalSection.getToNode();
}
}
if (finalSection != null || chainEndIsSource) {
if (connectionType != SourceConnectionType.NONE) {
boolean routable = true;
TemporaryRouteObject duplicate = route.clone();
for (int idx = 1; idx < chain.size() - 1; idx++) {
for (int idx = 1; idx < chain.size(); idx++) {
Node startNode = chain.get(idx);
Node endNode = chain.get(idx - 1);
@ -390,20 +414,68 @@ public class RoutingService {
}
if (routable) {
if (finalSection != null) {
// add final section if necessary,
// if last chain node == source node this can be skipped.
if (connectionType == SourceConnectionType.FINAL_SECTION_WITH_SOURCE_NODE
|| connectionType == SourceConnectionType.FINAL_SECTION_WITH_NEAR_BY_NODE) {
duplicate.addSection(finalSection);
if (!finalSection.getFromNode().getId().equals(source.getId())) duplicate.routeOverNearBy();
}
if (connectionType == SourceConnectionType.FINAL_SECTION_WITH_NEAR_BY_NODE || connectionType == SourceConnectionType.CHAIN_END_IS_NEAR_BY_NODE) {
duplicate.routeOverNearBy(source, nearByNode);
}
routes.add(duplicate);
}
}
}
}
/*
* There might be duplicate routes now, because the same route can be created by "nearby-routing" and container rates
*/
removeDuplicateRoutes(routes);
container.overrideRoutes(routes);
}
private void removeDuplicateRoutes(Collection<TemporaryRouteObject> routes) {
var toRemove = new ArrayList<TemporaryRouteObject>();
for (var route : routes) {
var sections = route.getSections();
if(sections.getLast().getType().equals(TemporaryRateObject.TemporaryRateObjectType.NEAR_BY)) {
for (var other : routes) {
//skip same instance.
if(other.equals(route)) continue;
var otherIter = other.getSections().iterator();
var iter = sections.iterator();
boolean same = true;
while (otherIter.hasNext() && iter.hasNext()) {
var otherSection = otherIter.next();
var section = iter.next();
if (!otherSection.hasSameNodes(section)) {
same = false;
break;
}
}
if (same && !otherIter.hasNext() && !iter.hasNext()) {
toRemove.add(route);
break;
}
}
}
}
routes.removeAll(toRemove);
}
private Integer getRegionRadius() {
var property = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.RADIUS_REGION);
return property.map(propertyDTO -> Integer.valueOf(propertyDTO.getCurrentValue())).orElseGet(SystemPropertyMappingId.RADIUS_REGION::getDefaultAsInteger);
@ -426,28 +498,25 @@ public class RoutingService {
TemporaryRateObject mainRunObj = new TemporaryRateObject(mainRunStartNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN, mainRun);
// var postRuns = container.getPostRuns().get(mainRun.getId());
//
// var sortedChains = sortByQuality(destinationChains)
var postRuns = container.getPostRuns().get(mainRun.getId());
var sortedChainMap = sortByQuality(destinationChains, postRuns, mainRun);
for (ChainQuality quality : ChainQuality.values()) {
for (var postRun : container.getPostRuns().get(mainRun.getId())) {
/* if connection quality is bad, do not try to route this and continue. */
if (quality == ChainQuality.FALLBACK) continue;
if (sortedChainMap.get(quality) == null) continue;
boolean qualityRoutable = false;
Node postRunEndNode = nodeRepository.getById(postRun.getToNodeId()).orElseThrow();
TemporaryRateObject postRunObj = new TemporaryRateObject(mainRunEndNode,postRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
for (var postRun : container.getPostRuns().get(mainRun.getId())) {
var sortedChains = sortByQuality(destinationChains, postRun, mainRun);
Node postRunEndNode = nodeRepository.getById(postRun.getToNodeId()).orElseThrow();
TemporaryRateObject postRunObj = new TemporaryRateObject(mainRunEndNode, postRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
for(ChainQuality quality : ChainQuality.values())
{
boolean qualityRoutable = false;
var sortedChains = sortedChainMap.get(quality).get(postRun.getId());
if (sortedChains == null || sortedChains.isEmpty()) continue;
/* if connection quality is bad, do not try to route this and continue. */
if (quality == ChainQuality.FALLBACK) continue;
if(sortedChains.get(quality) == null) continue;
for(var chain : sortedChains.get(quality)) {
for (var chain : sortedChains) {
boolean routable = true;
TemporaryRouteObject routeObj = new TemporaryRouteObject();
@ -466,7 +535,7 @@ public class RoutingService {
}
}
if(routable && quality == ChainQuality.LOW) {
if (routable && quality == ChainQuality.LOW) {
var rate = connectNodes(postRunEndNode, chain.getLast(), container);
if (rate != null) {
@ -484,30 +553,32 @@ public class RoutingService {
container.addRoute(routeObj);
}
}
/* if higher quality is routable, do not calculate lower qualities. */
if(qualityRoutable)
break;
}
/* if higher quality is routable, do not calculate lower qualities. */
if (qualityRoutable)
break;
}
}
}
private Map<ChainQuality, List<List<Node>>> sortByQuality(List<List<Node>> chains, ContainerRate postRun, ContainerRate mainRun) {
private Map<ChainQuality, Map<Integer, List<List<Node>>>> sortByQuality(List<List<Node>> chains, List<ContainerRate> postRuns, ContainerRate mainRun) {
Map<ChainQuality, List<List<Node>>> sortedChains = new HashMap<>();
Map<ChainQuality, Map<Integer, List<List<Node>>>> sortedChains = new HashMap<>();
for(var chain : chains) {
ChainQuality chainQuality = getChainQuality(chain, postRun, mainRun);
sortedChains.putIfAbsent(chainQuality, new ArrayList<>());
sortedChains.get(chainQuality).add(chain);
for (var postRun : postRuns) {
for (var chain : chains) {
ChainQuality chainQuality = getChainQuality(chain, postRun, mainRun);
sortedChains.putIfAbsent(chainQuality, new HashMap<>());
sortedChains.get(chainQuality).putIfAbsent(postRun.getId(), new ArrayList<>());
sortedChains.get(chainQuality).get(postRun.getId()).add(chain);
}
}
return sortedChains;
}
private ChainQuality getChainQuality(List<Node> chain, ContainerRate postRun, ContainerRate mainRun) {
if (chain.getLast().getId().equals(postRun.getToNodeId())) {
return ChainQuality.MEDIUM;
@ -556,6 +627,10 @@ public class RoutingService {
}
}
private enum SourceConnectionType {
NONE, CHAIN_END_IS_SOURCE_NODE, CHAIN_END_IS_NEAR_BY_NODE, FINAL_SECTION_WITH_SOURCE_NODE, FINAL_SECTION_WITH_NEAR_BY_NODE
}
private enum ChainQuality {
SUPERIOR(2), HIGH(1), MEDIUM(0), LOW(0), FALLBACK(0);
@ -715,10 +790,12 @@ public class RoutingService {
clone.nearBy = nearBy;
clone.isCheapest = isCheapest;
clone.isFastest = isFastest;
clone.quality = quality;
return clone;
}
public void routeOverNearBy() {
public void routeOverNearBy(Node source, Node nearByNode) {
this.sections.add(new TemporaryRateObject(source, nearByNode, TemporaryRateObject.TemporaryRateObjectType.NEAR_BY));
this.nearBy = true;
}
@ -751,7 +828,8 @@ public class RoutingService {
StringBuilder sb = new StringBuilder();
if(!sections.isEmpty()) {
if (!sections.isEmpty()) {
sb.append(sections.getLast().getFromNode().getExternalMappingId());
for (var section : sections.reversed()) {
sb.append(" -");
@ -759,15 +837,17 @@ public class RoutingService {
sb.append("-> ");
sb.append(section.getToNode().getExternalMappingId());
}
} else sb.append("Empty");
return String.format("Route: [%s][%s]", quality.name(), sb.toString());
return String.format("Route: [%s, %s]", quality.name().charAt(0) + (isFastest ? "\uD83D\uDE80" : "") + (isCheapest ? "\uD83D\uDCB2":""), sb);
}
public void setQuality(ChainQuality quality) {
this.quality = quality;
}
}
@ -805,7 +885,7 @@ public class RoutingService {
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)) {
} else if (this.type.equals(TemporaryRateObjectType.CONTAINER) || this.type.equals(TemporaryRateObjectType.MAIN_RUN) || this.type.equals(TemporaryRateObjectType.POST_RUN) || this.type.equals(TemporaryRateObjectType.NEAR_BY)) {
return Objects.equals(this.fromNode.getId(), that.fromNode.getId()) && Objects.equals(this.toNode.getId(), that.toNode.getId());
}
}
@ -849,7 +929,12 @@ public class RoutingService {
}
public double getCost() {
if(type.equals(TemporaryRateObjectType.MATRIX)) {
if (type.equals(TemporaryRateObjectType.NEAR_BY)) {
return 0.0;
}
if (type.equals(TemporaryRateObjectType.MATRIX)) {
return matrixRate.getRate().doubleValue() * approxDistance;
} else {
return containerRate.getRateFeu().doubleValue();
@ -857,9 +942,12 @@ public class RoutingService {
}
public int getLeadTime() {
if (type.equals(TemporaryRateObjectType.MATRIX)) {
if (type.equals(TemporaryRateObjectType.MATRIX))
return 3;
}
if (type.equals(TemporaryRateObjectType.NEAR_BY))
return 0;
return containerRate.getLeadTime();
}
@ -871,8 +959,19 @@ public class RoutingService {
return containerRate.getType();
}
public String getFullDebugText() {
if (type.equals(TemporaryRateObjectType.MATRIX)) {
return String.format("Rate [%s -\uD83D\uDDA9-> %s, %.2f km]", fromNode.getExternalMappingId(), toNode.getExternalMappingId(), approxDistance);
}
return String.format("Rate [%s -\uD83D\uDCE6-> %s]", fromNode.getExternalMappingId(), toNode.getExternalMappingId());
}
public boolean hasSameNodes(TemporaryRateObject section) {
return fromNode.getId().equals(section.getFromNode().getId()) && toNode.getId().equals(section.getToNode().getId());
}
private enum TemporaryRateObjectType {
MATRIX("\uD83D\uDDA9"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
NEAR_BY("\uD83D\uDCCD"), MATRIX("\uD83D\uDDA9"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
private final String debugText;
@ -884,12 +983,5 @@ public class RoutingService {
return debugText;
}
}
public String getFullDebugText() {
if(type.equals(TemporaryRateObjectType.MATRIX)) {
return String.format("Rate [%s --> %s [%s, %.2f km]", fromNode.getExternalMappingId(), toNode.getExternalMappingId(), type, approxDistance);
}
return String.format("Rate [%s --> %s [%s]", fromNode.getExternalMappingId(), toNode.getExternalMappingId(), type);
}
}
}

View file

@ -32,7 +32,7 @@ public class CalculationStatusService {
}
}
public Integer schedule(ArrayList<Object> calculationIds) {
public Integer schedule(ArrayList<Integer> calculationIds) {
//TODO
return null;
}

View file

@ -0,0 +1,366 @@
package de.avatic.lcc.controller.calculation;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@Import({PremiseControllerTestData.class, PremiseTestsHelper.class})
public class DestinationIntegrationTest {
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private PremiseControllerTestData premiseTestData;
@Autowired
private PremiseTestsHelper testHelper;
@Nested
@Sql(scripts = {"classpath:master_data/alldata.sql", "classpath:master_data/users.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/users-cleanup.sql", "classpath:master_data/alldata-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/premises.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/premises-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class CreateDestinationTests {
@Test
@DisplayName("POST /api/calculation/destination/ - happy path (create destination)")
public void createDestinationCase1() throws Exception {
var nodeId = testHelper.getNodeIdByExternalMappingId("AB");
var premisesBeforeUpdate = testHelper.getPremisesFromDb();
var premise1 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
var premise3 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 3).findFirst().orElseThrow();
var dto = new DestinationCreateDTO();
dto.setPremiseId(Arrays.asList(premise1.getId(), premise3.getId()));
dto.setDestinationNodeId(nodeId);
mockMvc.perform(post("/api/calculation/destination")
.content(objectMapper.writeValueAsString(dto))
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andDo(print());
// TODO check ...
}
}
@Nested
@Sql(scripts = {"classpath:master_data/alldata.sql", "classpath:master_data/users.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/users-cleanup.sql", "classpath:master_data/alldata-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/premises.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/premises-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class GetDestinationTests {
@BeforeEach
void setUp() {
premiseTestData.generateData();
}
@AfterEach
void cleanUp() {
premiseTestData.deleteAll();
}
@Test
@DisplayName("GET /api/calculation/destination - happy path (get destination)")
public void getDestinationCase1() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
var destIds = testHelper.getDestinationIdsByPremiseId(premise1.getId());
mockMvc.perform(get("/api/calculation/destination/" + destIds.getFirst()))
.andExpect(status().isOk()).andDo(print());
// TODO check ...
}
}
@Nested
@Sql(scripts = {"classpath:master_data/alldata.sql", "classpath:master_data/users.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/users-cleanup.sql", "classpath:master_data/alldata-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/premises.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/premises-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class UpdateDestinationTests {
@BeforeEach
void setUp() {
premiseTestData.generateData();
}
@AfterEach
void cleanUp() {
premiseTestData.deleteAll();
}
@Test
@DisplayName("PUT /api/calculation/destination - happy path (update destination)")
public void getDestinationCase1() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destId = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destId).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty();
var destinationBefore = testHelper.getDestinationById(destId.getFirst());
var routesBefore = testHelper.getRoutesByDestinationId(destId.getFirst());
var dto = new DestinationUpdateDTO();
dto.setDisposalCost(666.6);
dto.setHandlingCost(777.7);
dto.setRepackingCost(888.8);
dto.setRouteSelectedId(routesBefore.getFirst().getId());
mockMvc.perform(put("/api/calculation/destination/" + destId.getFirst())
.content(objectMapper.writeValueAsString(dto)).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print());
var destinationAfter = testHelper.getDestinationById(destId.getFirst());
var routesAfter = testHelper.getRoutesByDestinationId(destId.getFirst());
assertThat(destinationAfter).isNotEmpty();
assertThat(destinationAfter.get().getDisposalCost()).isEqualByComparingTo(BigDecimal.valueOf(666.6));
assertThat(destinationAfter.get().getHandlingCost()).isEqualByComparingTo(BigDecimal.valueOf(777.7));
assertThat(destinationAfter.get().getRepackingCost()).isEqualByComparingTo(BigDecimal.valueOf(888.8));
assertThat(routesAfter.getFirst().getSelected()).isEqualTo(true);
assertThat(routesAfter.getLast().getSelected()).isEqualTo(false);
}
@Test
@DisplayName("PUT /api/calculation/destination - happy path (partial update destination)")
public void getDestinationCase2() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destId = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destId).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty();
var destinationBefore = testHelper.getDestinationById(destId.getFirst()).orElseThrow();
var routesBefore = testHelper.getRoutesByDestinationId(destId.getFirst());
var dto = new DestinationUpdateDTO();
dto.setDisposalCost(666.6);
dto.setHandlingCost(null);
dto.setRepackingCost(null);
dto.setRouteSelectedId(routesBefore.getLast().getId());
mockMvc.perform(put("/api/calculation/destination/" + destId.getFirst())
.content(objectMapper.writeValueAsString(dto)).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print());
var destinationAfter = testHelper.getDestinationById(destId.getFirst());
var routesAfter = testHelper.getRoutesByDestinationId(destId.getFirst());
assertThat(destinationAfter).isNotEmpty();
assertThat(destinationAfter.get().getDisposalCost()).isEqualByComparingTo(BigDecimal.valueOf(666.6));
assertThat(destinationAfter.get().getHandlingCost()).isEqualByComparingTo(destinationBefore.getHandlingCost());
assertThat(destinationAfter.get().getRepackingCost()).isEqualByComparingTo(destinationBefore.getHandlingCost());
assertThat(routesAfter.getFirst().getSelected()).isEqualTo(false);
assertThat(routesAfter.getLast().getSelected()).isEqualTo(true);
}
@Test
@DisplayName("PUT /api/calculation/destination - error case (negative value)")
public void getDestinationCase3() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destId = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destId).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty();
var destinationBefore = testHelper.getDestinationById(destId.getFirst()).orElseThrow();
var routesBefore = testHelper.getRoutesByDestinationId(destId.getFirst());
var dto = new DestinationUpdateDTO();
dto.setDisposalCost(-666.6);
dto.setHandlingCost(null);
dto.setRepackingCost(null);
dto.setRouteSelectedId(routesBefore.getLast().getId());
mockMvc.perform(put("/api/calculation/destination/" + destId.getFirst())
.content(objectMapper.writeValueAsString(dto)).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andDo(print());
var destinationAfter = testHelper.getDestinationById(destId.getFirst());
var routesAfter = testHelper.getRoutesByDestinationId(destId.getFirst());
assertThat(destinationAfter).isNotEmpty();
assertThat(destinationAfter.get().getDisposalCost()).isEqualByComparingTo(destinationBefore.getHandlingCost());
assertThat(destinationAfter.get().getHandlingCost()).isEqualByComparingTo(destinationBefore.getHandlingCost());
assertThat(destinationAfter.get().getRepackingCost()).isEqualByComparingTo(destinationBefore.getHandlingCost());
assertThat(routesAfter.getFirst().getSelected()).isEqualTo(routesBefore.getFirst().getSelected());
assertThat(routesAfter.getLast().getSelected()).isEqualTo(routesBefore.getLast().getSelected());
}
}
@Nested
@Sql(scripts = {"classpath:master_data/alldata.sql", "classpath:master_data/users.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/users-cleanup.sql", "classpath:master_data/alldata-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/premises.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/premises-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class DeleteDestinationTests {
@BeforeEach
void setUp() {
premiseTestData.generateData();
}
@AfterEach
void cleanUp() {
premiseTestData.deleteAll();
}
@Test
@DisplayName("DELETE /api/calculation/destination - happy path (delete single destination)")
public void deleteDestinationCase1() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destIdsBefore = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsBefore).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(2);
mockMvc.perform(delete("/api/calculation/destination/" + destIdsBefore.getFirst()))
.andExpect(status().isOk())
.andDo(print());
List<Integer> destIdsAfter = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsAfter).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1);
var destinationAfter = testHelper.getDestinationById(destIdsBefore.getFirst());
assertThat(destinationAfter).isEmpty();
}
@Test
@DisplayName("DELETE /api/calculation/destination - happy path (delete all destination)")
public void deleteDestinationCase2() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destIdsBefore = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsBefore).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(2);
assertThat(testHelper.getRouteCount()).isNotEqualTo(0);
assertThat(testHelper.getRouteSectionCount()).isNotEqualTo(0);
assertThat(testHelper.getRouteNodeCount()).isNotEqualTo(0);
mockMvc.perform(delete("/api/calculation/destination/" + destIdsBefore.getFirst()))
.andExpect(status().isOk())
.andDo(print());
mockMvc.perform(delete("/api/calculation/destination/" + destIdsBefore.getLast()))
.andExpect(status().isOk())
.andDo(print());
List<Integer> destIdsAfter = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsAfter).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(0);
assertThat(testHelper.getDestinationById(destIdsBefore.getFirst())).isEmpty();
assertThat(testHelper.getDestinationById(destIdsBefore.getLast())).isEmpty();
assertThat(testHelper.getRouteCount()).isEqualTo(0);
assertThat(testHelper.getRouteSectionCount()).isEqualTo(0);
assertThat(testHelper.getRouteNodeCount()).isEqualTo(0);
}
@Test
@DisplayName("DELETE /api/calculation/destination - error case (unknown id)")
public void deleteDestinationCase3() throws Exception {
var premisesFromDb = testHelper.getPremisesFromDb();
var premise1 = premisesFromDb.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
List<Integer> destIdsBefore = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsBefore).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(2);
mockMvc.perform(delete("/api/calculation/destination/" + 9999))
.andExpect(status().isForbidden())
.andDo(print());
List<Integer> destIdsAfter = testHelper.getDestinationIdsByPremiseId(premise1.getId());
assertThat(destIdsAfter).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(2);
}
}
@Nested
@Sql(scripts = {"classpath:master_data/alldata.sql", "classpath:master_data/users.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/users-cleanup.sql", "classpath:master_data/alldata-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
@Sql(scripts = {"classpath:master_data/premises.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/premises-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@DisplayName("GET /api/calculation/start - Start Calculations Tests")
class StartCalculationTests {
@Test
@DisplayName("PUT /api/calculation/start - happy path")
public void startCalculationCase1() throws Exception {
var nodeId = testHelper.getNodeIdByExternalMappingId("AB");
var premisesBeforeUpdate = testHelper.getPremisesFromDb();
var premise1 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 2).findFirst().orElseThrow();
var premise3 = premisesBeforeUpdate.stream().filter(p -> p.getHuUnitCount() == 3).findFirst().orElseThrow();
var dto = new DestinationCreateDTO();
dto.setPremiseId(Arrays.asList(premise1.getId(), premise3.getId()));
dto.setDestinationNodeId(nodeId);
mockMvc.perform(post("/api/calculation/destination")
.content(objectMapper.writeValueAsString(dto))
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andDo(print());
mockMvc.perform(put("/api/calculation/start")
.content(objectMapper.writeValueAsString(Arrays.asList(premise1, premise3)))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(print());
}
}
}

View file

@ -0,0 +1,198 @@
package de.avatic.lcc.controller.calculation;
import de.avatic.lcc.model.nodes.Location;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.PremiseState;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.model.premises.route.Route;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.Optional;
@TestComponent
public class PremiseTestsHelper {
private final JdbcTemplate jdbcTemplate;
public PremiseTestsHelper(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<Premise> getPremisesFromDb() {
String query = "SELECT * FROM premise";
return jdbcTemplate.query(query, (rs, rowNum) -> {
var entity = new Premise();
entity.setId(rs.getInt("id"));
entity.setPackagingId(rs.getInt("packaging_id"));
if (rs.wasNull())
entity.setPackagingId(null);
entity.setMaterialId(rs.getInt("material_id"));
if (rs.wasNull())
entity.setMaterialId(null);
entity.setSupplierNodeId(rs.getInt("supplier_node_id"));
if (rs.wasNull())
entity.setSupplierNodeId(null);
entity.setUserSupplierNodeId(rs.getInt("user_supplier_node_id"));
if (rs.wasNull())
entity.setUserSupplierNodeId(null);
entity.setLocation(new Location(rs.getBigDecimal("geo_lng").doubleValue(), rs.getBigDecimal("geo_lat").doubleValue()));
entity.setCountryId(rs.getInt("country_id"));
if (rs.wasNull())
entity.setCountryId(null);
entity.setUserId(rs.getInt("user_id"));
if (rs.wasNull())
entity.setUserId(null);
entity.setMaterialCost(rs.getBigDecimal("material_cost"));
entity.setHsCode(rs.getString("hs_code"));
entity.setCustomRate(rs.getBigDecimal("tariff_rate"));
entity.setFcaEnabled(rs.getBoolean("is_fca_enabled"));
if (rs.wasNull())
entity.setFcaEnabled(null);
entity.setOverseaShare(rs.getBigDecimal("oversea_share"));
entity.setIndividualHuHeight(rs.getInt("individual_hu_height"));
if (rs.wasNull())
entity.setIndividualHuHeight(null);
entity.setIndividualHuWidth(rs.getInt("individual_hu_width"));
if (rs.wasNull())
entity.setIndividualHuWidth(null);
entity.setIndividualHuLength(rs.getInt("individual_hu_length"));
if (rs.wasNull())
entity.setIndividualHuLength(null);
entity.setIndividualHuWeight(rs.getInt("individual_hu_weight"));
if (rs.wasNull())
entity.setIndividualHuWeight(null);
entity.setHuDisplayedDimensionUnit(DimensionUnit.valueOf(rs.getString("hu_displayed_dimension_unit")));
entity.setHuDisplayedWeightUnit(WeightUnit.valueOf(rs.getString("hu_displayed_weight_unit")));
entity.setHuStackable(rs.getBoolean("hu_stackable"));
if (rs.wasNull())
entity.setHuStackable(null);
entity.setHuMixable(rs.getBoolean("hu_mixable"));
if (rs.wasNull())
entity.setHuMixable(null);
entity.setHuUnitCount(rs.getInt("hu_unit_count"));
if (rs.wasNull())
entity.setHuUnitCount(null);
entity.setState(PremiseState.valueOf(rs.getString("state")));
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
entity.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
return entity;
});
}
public Integer getNodeIdByExternalMappingId(String mappingId) {
String query = "SELECT id FROM node WHERE external_mapping_id = ? LIMIT 1";
return jdbcTemplate.queryForObject(query, Integer.class, mappingId);
}
public Integer getUserNodeIdByName(String name) {
String query = "SELECT id FROM sys_user_node WHERE name = ? LIMIT 1";
return jdbcTemplate.queryForObject(query, Integer.class, name);
}
public List<Integer> getDestinationIdsByPremiseId(Integer premiseId) {
String query = "SELECT id FROM premise_destination WHERE premise_id = ?";
return jdbcTemplate.query(query, (rs, rowNum) -> rs.getInt("id"), premiseId);
}
public Integer getRouteCount() {
String query = """
SELECT COUNT(*) FROM premise_route
""";
return jdbcTemplate.queryForObject(query, Integer.class);
}
public Integer getRouteSectionCount() {
String query = """
SELECT COUNT(*) FROM premise_route_section
""";
return jdbcTemplate.queryForObject(query, Integer.class);
}
public Integer getRouteNodeCount() {
String query = """
SELECT COUNT(*) FROM premise_route_node
""";
return jdbcTemplate.queryForObject(query, Integer.class);
}
public List<Route> getRoutesByDestinationId(Integer destinationId) {
String query = """
SELECT * FROM premise_route WHERE premise_destination_id = ?
""";
return jdbcTemplate.query(query, (rs, rowNum) -> {
Route entity = new Route();
entity.setId(rs.getInt("id"));
entity.setCheapest(rs.getBoolean("is_cheapest"));
entity.setFastest(rs.getBoolean("is_fastest"));
entity.setSelected(rs.getBoolean("is_selected"));
entity.setDestinationId(rs.getInt("premise_destination_id"));
return entity;
}, destinationId);
}
public Optional<Destination> getDestinationById(Integer id) {
String query = """
SELECT * FROM premise_destination WHERE id = ?
""";
var destinations = jdbcTemplate.query(query,(rs, row) -> {
Destination entity = new Destination();
entity.setId(rs.getInt("id"));
entity.setAnnualAmount(rs.getInt("annual_amount"));
entity.setPremiseId(rs.getInt("premise_id"));
entity.setDestinationNodeId(rs.getInt("destination_node_id"));
entity.setRateD2d(rs.getBigDecimal("rate_d2d"));
entity.setD2d(rs.getBoolean("is_d2d"));
entity.setLeadTimeD2d(rs.getInt("lead_time_d2d"));
entity.setRepackingCost(rs.getBigDecimal("repacking_cost"));
entity.setHandlingCost(rs.getBigDecimal("handling_cost"));
entity.setDisposalCost(rs.getBigDecimal("disposal_cost"));
entity.setGeoLat(rs.getBigDecimal("geo_lat"));
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
entity.setCountryId(rs.getInt("country_id"));
return entity;
}, id);
if(destinations.isEmpty())
return Optional.empty();
return Optional.ofNullable(destinations.getFirst());
}
}