diff --git a/src/frontend/src/components/UI/Toast.vue b/src/frontend/src/components/UI/Toast.vue
index 27967cc..3c646df 100644
--- a/src/frontend/src/components/UI/Toast.vue
+++ b/src/frontend/src/components/UI/Toast.vue
@@ -116,7 +116,7 @@ export default {
case 'warning':
return 'secondary'
case 'info':
- return 'primary'
+ return 'secondary'
case 'error':
return 'exception'
default:
diff --git a/src/frontend/src/components/layout/calculation/TheDashboard.vue b/src/frontend/src/components/layout/calculation/TheDashboard.vue
index 9415703..cf14520 100644
--- a/src/frontend/src/components/layout/calculation/TheDashboard.vue
+++ b/src/frontend/src/components/layout/calculation/TheDashboard.vue
@@ -13,9 +13,9 @@
-
{{ total }}
+
{{ completed }}
-
Total
+
Completed
@@ -94,8 +94,8 @@ export default {
},
computed: {
...mapStores(useDashboardStore),
- total() {
- return this.dashboardStore.total
+ completed() {
+ return this.dashboardStore.completed
},
drafts() {
return this.dashboardStore.drafts
diff --git a/src/frontend/src/store/bulkOperation.js b/src/frontend/src/store/bulkOperation.js
index 437a4bc..bfc5e18 100644
--- a/src/frontend/src/store/bulkOperation.js
+++ b/src/frontend/src/store/bulkOperation.js
@@ -43,7 +43,7 @@ export const useBulkOperationStore = defineStore('bulkOperation', {
useNotificationStore().addNotification({
title: 'Bulk operation',
message: 'All your bulk operations have been completed.',
- type: 'success',
+ variant: 'info',
icon: 'stack',
})
diff --git a/src/frontend/src/store/dashboard.js b/src/frontend/src/store/dashboard.js
index 0f0d0c8..9faed48 100644
--- a/src/frontend/src/store/dashboard.js
+++ b/src/frontend/src/store/dashboard.js
@@ -1,6 +1,7 @@
import {defineStore} from 'pinia'
import performRequest from "@/backend.js";
import {config} from '@/config'
+import {useNotificationStore} from "@/store/notification.js";
export const useDashboardStore = defineStore('dashboard', {
state: () => ({
@@ -9,9 +10,9 @@ export const useDashboardStore = defineStore('dashboard', {
pullTimer: null,
}),
getters: {
- total(state) {
+ completed(state) {
if (state.stats)
- return state.stats.total;
+ return state.stats.completed;
return null;
},
@@ -38,13 +39,23 @@ export const useDashboardStore = defineStore('dashboard', {
async load() {
const url = `${config.backendUrl}/dashboard`;
const resp = await performRequest(this, 'GET', url, null);
+
+ if(this.stats?.running && this.stats.running > 0 && resp.data.running === 0) {
+ useNotificationStore().addNotification({
+ title: 'Calculation',
+ message: 'All your calculations have been completed.',
+ variant: 'info',
+ icon: 'calculator',
+ })
+ }
+
this.stats = resp.data;
},
startPulling() {
if (this.pullTimer) return
- this.pullTimer = setTimeout(() => {
- this.pull()
+ this.pullTimer = setTimeout(async () => {
+ await this.pull()
}, this.pullInterval)
},
stopPulling() {
diff --git a/src/frontend/src/store/notification.js b/src/frontend/src/store/notification.js
index 018691c..b2fe61d 100644
--- a/src/frontend/src/store/notification.js
+++ b/src/frontend/src/store/notification.js
@@ -33,6 +33,9 @@ export const useNotificationStore = defineStore('notification', {
return this.notifications.pop();
},
addNotification(notification) {
+
+ console.log("add notification", notification, this.notifications.length)
+
this.notifications.push({
icon: notification.icon ?? null,
message: notification.message ?? 'Unknown notification',
diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java
index 079f381..0ce4c0d 100644
--- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java
+++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java
@@ -42,7 +42,7 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -111,7 +111,7 @@ public class SecurityConfig {
.exceptionHandling(ex -> ex
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
- new AntPathRequestMatcher("/api/**")
+ PathPatternRequestMatcher.withDefaults().matcher("/api/**")
)
)
.csrf(csrf -> csrf
diff --git a/src/main/java/de/avatic/lcc/dto/calculation/execution/CalculationProcessingOverviewDTO.java b/src/main/java/de/avatic/lcc/dto/calculation/execution/CalculationProcessingOverviewDTO.java
index 9f6f58d..c92e257 100644
--- a/src/main/java/de/avatic/lcc/dto/calculation/execution/CalculationProcessingOverviewDTO.java
+++ b/src/main/java/de/avatic/lcc/dto/calculation/execution/CalculationProcessingOverviewDTO.java
@@ -2,7 +2,7 @@ package de.avatic.lcc.dto.calculation.execution;
public class CalculationProcessingOverviewDTO {
- private Integer total;
+ private Integer completed;
private Integer running;
@@ -11,12 +11,12 @@ public class CalculationProcessingOverviewDTO {
private Integer failed;
- public Integer getTotal() {
- return total;
+ public Integer getCompleted() {
+ return completed;
}
- public void setTotal(Integer total) {
- this.total = total;
+ public void setCompleted(Integer completed) {
+ this.completed = completed;
}
public Integer getRunning() {
diff --git a/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java b/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java
index 27546d2..d6f2bda 100644
--- a/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java
+++ b/src/main/java/de/avatic/lcc/model/db/nodes/Distance.java
@@ -40,6 +40,7 @@ public class Distance {
private DistanceMatrixState state;
+ private Integer retries;
private Integer fromNodeId;
@@ -144,4 +145,12 @@ public class Distance {
public void setToUserNodeId(Integer toUserNodeId) {
this.toUserNodeId = toUserNodeId;
}
+
+ public Integer getRetries() {
+ return retries;
+ }
+
+ public void setRetries(Integer retries) {
+ this.retries = retries;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/model/db/premises/route/Destination.java b/src/main/java/de/avatic/lcc/model/db/premises/route/Destination.java
index 0aeb820..f9b82dd 100644
--- a/src/main/java/de/avatic/lcc/model/db/premises/route/Destination.java
+++ b/src/main/java/de/avatic/lcc/model/db/premises/route/Destination.java
@@ -31,6 +31,8 @@ public class Destination {
private Integer countryId;
+ private BigDecimal distanceD2d;
+
public Integer getLeadTimeD2d() {
return leadTimeD2d;
}
@@ -134,4 +136,12 @@ public class Destination {
public void setDisposalCost(BigDecimal disposalCost) {
this.disposalCost = disposalCost;
}
+
+ public BigDecimal getDistanceD2d() {
+ return distanceD2d;
+ }
+
+ public void setDistanceD2d(BigDecimal distanceD2d) {
+ this.distanceD2d = distanceD2d;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/model/db/premises/route/RouteSection.java b/src/main/java/de/avatic/lcc/model/db/premises/route/RouteSection.java
index 36610c2..d6219da 100644
--- a/src/main/java/de/avatic/lcc/model/db/premises/route/RouteSection.java
+++ b/src/main/java/de/avatic/lcc/model/db/premises/route/RouteSection.java
@@ -27,6 +27,8 @@ public class RouteSection {
private Integer toRouteNodeId;
+ private Double distance;
+
public RateType getRateType() {
return rateType;
}
@@ -114,4 +116,12 @@ public class RouteSection {
public void setToRouteNodeId(Integer toRouteNodeId) {
this.toRouteNodeId = toRouteNodeId;
}
+
+ public Double getDistance() {
+ return distance;
+ }
+
+ public void setDistance(Double distance) {
+ this.distance = distance;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java b/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java
index a8274fb..c25a2a8 100644
--- a/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/DistanceMatrixRepository.java
@@ -29,10 +29,10 @@ public class DistanceMatrixRepository {
String fromCol = isUsrFrom ? "from_user_node_id" : "from_node_id";
String toCol = isUsrTo ? "to_user_node_id" : "to_node_id";
- String query = "SELECT * FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ? AND state = ?";
+ String query = "SELECT * FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ?";
var distance = jdbcTemplate.query(query, new DistanceMapper(),
- src.getId(), dest.getId(), DistanceMatrixState.VALID.name());
+ src.getId(), dest.getId());
if (distance.isEmpty())
return Optional.empty();
@@ -40,6 +40,12 @@ public class DistanceMatrixRepository {
return Optional.of(distance.getFirst());
}
+ @Transactional
+ public void updateRetries(Integer id) {
+ String query = "UPDATE distance_matrix SET retries = retries + 1 WHERE id = ?";
+ jdbcTemplate.update(query, id);
+ }
+
@Transactional
public void saveDistance(Distance distance) {
try {
@@ -119,10 +125,20 @@ public class DistanceMatrixRepository {
public Distance mapRow(ResultSet rs, int rowNum) throws SQLException {
Distance entity = new Distance();
- entity.setFromNodeId(rs.getInt("from_node_id"));
- entity.setToNodeId(rs.getInt("to_node_id"));
- entity.setFromNodeId(rs.getInt("from_user_node_id"));
- entity.setToNodeId(rs.getInt("to_user_node_id"));
+ entity.setId(rs.getInt("id"));
+
+ var fromNodeId = rs.getInt("from_node_id");
+ entity.setFromNodeId(rs.wasNull() ? null : fromNodeId);
+
+ var toNodeId = rs.getInt("to_node_id");
+ entity.setToNodeId(rs.wasNull() ? null : toNodeId);
+
+ var fromUserNodeId = rs.getInt("from_user_node_id");
+ entity.setFromUserNodeId(rs.wasNull() ? null : fromUserNodeId);
+
+ var toUserNodeId = rs.getInt("to_user_node_id");
+ entity.setToUserNodeId(rs.wasNull() ? null : toUserNodeId);
+
entity.setDistance(rs.getBigDecimal("distance"));
entity.setFromGeoLng(rs.getBigDecimal("from_geo_lng"));
entity.setFromGeoLat(rs.getBigDecimal("from_geo_lat"));
@@ -131,6 +147,9 @@ public class DistanceMatrixRepository {
entity.setState(DistanceMatrixState.valueOf(rs.getString("state")));
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
+ var retries = rs.getInt("retries");
+ entity.setRetries(rs.wasNull() ? 0 : retries);
+
return entity;
}
}
diff --git a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java
index 3b47aa3..70dde7f 100644
--- a/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/premise/DestinationRepository.java
@@ -67,7 +67,7 @@ public class DestinationRepository {
}
@Transactional
- public void update(Integer id, Integer annualAmount, BigDecimal repackingCost, BigDecimal disposalCost, BigDecimal handlingCost, Boolean isD2d, BigDecimal d2dRate, BigDecimal d2dLeadTime) {
+ public void update(Integer id, Integer annualAmount, BigDecimal repackingCost, BigDecimal disposalCost, BigDecimal handlingCost, Boolean isD2d, BigDecimal d2dRate, BigDecimal d2dLeadTime, BigDecimal distanceD2d) {
if (id == null) {
throw new InvalidArgumentException("ID cannot be null");
}
@@ -99,6 +99,9 @@ public class DestinationRepository {
setClauses.add("lead_time_d2d = :d2dLeadTime");
parameters.put("d2dLeadTime", setD2d ? d2dLeadTime : null);
+ setClauses.add("distance_d2d = :distanceD2d");
+ parameters.put("distanceD2d", distanceD2d);
+
if (annualAmount != null) {
setClauses.add("annual_amount = :annualAmount");
@@ -268,7 +271,7 @@ public class DestinationRepository {
public Integer insert(Destination destination) {
KeyHolder keyHolder = new GeneratedKeyHolder();
- String query = "INSERT INTO premise_destination (annual_amount, premise_id, destination_node_id, country_id, rate_d2d, lead_time_d2d, is_d2d, repacking_cost, handling_cost, disposal_cost, geo_lat, geo_lng) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ String query = "INSERT INTO premise_destination (annual_amount, premise_id, destination_node_id, country_id, rate_d2d, lead_time_d2d, is_d2d, repacking_cost, handling_cost, disposal_cost, geo_lat, geo_lng, distance_d2d) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(connection -> {
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
@@ -297,6 +300,8 @@ public class DestinationRepository {
ps.setBigDecimal(11, destination.getGeoLat());
ps.setBigDecimal(12, destination.getGeoLng());
+ ps.setBigDecimal(13, destination.getDistanceD2d());
+
return ps;
}, keyHolder);
@@ -363,6 +368,8 @@ public class DestinationRepository {
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
entity.setCountryId(rs.getInt("country_id"));
+ entity.setDistanceD2d(rs.getBigDecimal("distance_d2d"));
+
return entity;
}
}
diff --git a/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java b/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java
index f47ac8d..eb8a373 100644
--- a/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/premise/RouteSectionRepository.java
@@ -9,6 +9,7 @@ import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
+import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -45,8 +46,8 @@ public class RouteSectionRepository {
}
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, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated) " +
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ String sql = "INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated, distance) " +
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
@@ -61,6 +62,7 @@ public class RouteSectionRepository {
ps.setBoolean(8, premiseRouteSection.getMainRun());
ps.setBoolean(9, premiseRouteSection.getPostRun());
ps.setBoolean(10, premiseRouteSection.getOutdated());
+ ps.setBigDecimal(11, premiseRouteSection.getDistance() == null ? null : BigDecimal.valueOf(premiseRouteSection.getDistance()));
return ps;
}, keyHolder);
@@ -92,6 +94,9 @@ public class RouteSectionRepository {
entity.setPostRun(rs.getBoolean("is_post_run"));
entity.setOutdated(rs.getBoolean("is_outdated"));
+ var distance = rs.getBigDecimal("distance");
+ entity.setDistance(rs.wasNull() ? null : distance.doubleValue());
+
return entity;
}
}
diff --git a/src/main/java/de/avatic/lcc/service/access/DestinationService.java b/src/main/java/de/avatic/lcc/service/access/DestinationService.java
index 283fd8d..d7b6297 100644
--- a/src/main/java/de/avatic/lcc/service/access/DestinationService.java
+++ b/src/main/java/de/avatic/lcc/service/access/DestinationService.java
@@ -10,6 +10,7 @@ import de.avatic.lcc.model.db.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.DistanceService;
import de.avatic.lcc.service.calculation.RoutingService;
import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
import de.avatic.lcc.service.users.AuthorizationService;
@@ -35,8 +36,9 @@ public class DestinationService {
private final PremiseRepository premiseRepository;
private final UserNodeRepository userNodeRepository;
private final AuthorizationService authorizationService;
+ private final DistanceService distanceService;
- public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) {
+ public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, RoutingService routingService, NodeRepository nodeRepository, PremiseRepository premiseRepository, UserNodeRepository userNodeRepository, AuthorizationService authorizationService, DistanceService distanceService) {
this.destinationRepository = destinationRepository;
this.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository;
@@ -47,6 +49,7 @@ public class DestinationService {
this.premiseRepository = premiseRepository;
this.userNodeRepository = userNodeRepository;
this.authorizationService = authorizationService;
+ this.distanceService = distanceService;
}
@@ -199,11 +202,30 @@ public class DestinationService {
destinationUpdateDTO.getDisposalCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getDisposalCost().doubleValue()),
destinationUpdateDTO.getHandlingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getHandlingCost().doubleValue()),
destinationUpdateDTO.getD2d(), destinationUpdateDTO.getRateD2d() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getRateD2d().doubleValue()),
- destinationUpdateDTO.getLeadtimeD2d() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getLeadtimeD2d())
+ destinationUpdateDTO.getLeadtimeD2d() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getLeadtimeD2d()),
+ destinationUpdateDTO.getD2d() ? getD2dDistance(id) : null
);
}
+ private BigDecimal getD2dDistance(Integer destinationId) {
+ var dest = destinationRepository.getById(destinationId);
+
+ if(dest.isPresent()) {
+ var premise = premiseRepository.getPremiseById(dest.get().getPremiseId());
+
+ if(premise.isPresent()) {
+ boolean isUserNode = premise.get().getSupplierNodeId() == null;
+
+ var from = isUserNode ? userNodeRepository.getById(premise.get().getUserSupplierNodeId()) : nodeRepository.getById(premise.get().getSupplierNodeId());
+ var to = nodeRepository.getById(dest.get().getDestinationNodeId());
+
+ return BigDecimal.valueOf(distanceService.getDistanceForNode(from.orElseThrow(), to.orElseThrow()));
+ }
+ }
+ return null;
+ }
+
private Map> findRoutes(List premisses, Map> routingRequest) {
Map> routes = new HashMap<>();
@@ -267,6 +289,7 @@ public class DestinationService {
premiseRouteSection.setFromRouteNodeId(fromNodeId);
premiseRouteSection.setToRouteNodeId(toNodeId);
+
routeSectionRepository.insert(premiseRouteSection);
fromNodeId = toNodeId;
diff --git a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java
index f8b6b38..1150ebd 100644
--- a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java
+++ b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java
@@ -1,5 +1,7 @@
package de.avatic.lcc.service.api;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import de.avatic.lcc.model.azuremaps.route.RouteDirectionsResponse;
import de.avatic.lcc.model.db.nodes.Distance;
import de.avatic.lcc.model.db.nodes.DistanceMatrixState;
@@ -10,6 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@@ -25,14 +28,18 @@ public class DistanceApiService {
private final DistanceMatrixRepository distanceMatrixRepository;
private final RestTemplate restTemplate;
+ private final ObjectMapper objectMapper;
+ private final GeoApiService geoApiService;
@Value("${azure.maps.subscription.key}")
private String subscriptionKey;
public DistanceApiService(DistanceMatrixRepository distanceMatrixRepository,
- RestTemplate restTemplate) {
+ RestTemplate restTemplate, ObjectMapper objectMapper, GeoApiService geoApiService) {
this.distanceMatrixRepository = distanceMatrixRepository;
this.restTemplate = restTemplate;
+ this.objectMapper = objectMapper;
+ this.geoApiService = geoApiService;
}
public Optional getDistance(Location from, Location to) {
@@ -70,22 +77,55 @@ public class DistanceApiService {
Optional cachedDistance = distanceMatrixRepository.getDistance(from, isUsrFrom, to, isUsrTo);
- if (cachedDistance.isPresent()) {
+ if (cachedDistance.isPresent() && cachedDistance.get().getState() == DistanceMatrixState.VALID) {
logger.info("Found cached distance from node {} to node {}", from.getExternalMappingId(), to.getExternalMappingId());
return cachedDistance;
}
- logger.info("Fetching distance from Azure Maps for nodes {} to {}", from.getExternalMappingId(), to.getExternalMappingId());
- Optional fetchedDistance = fetchDistanceFromAzureMaps(from, isUsrFrom, to, isUsrTo);
+ if (cachedDistance.isPresent() && cachedDistance.get().getState() == DistanceMatrixState.EXCEPTION) {
+ if (cachedDistance.get().getRetries() >= 3)
+ return Optional.empty();
- if (fetchedDistance.isPresent()) {
- distanceMatrixRepository.saveDistance(fetchedDistance.get());
- return fetchedDistance;
+ distanceMatrixRepository.updateRetries(cachedDistance.get().getId());
+ }
+
+ logger.info("Fetching distance from Azure Maps for nodes {} to {}", from.getExternalMappingId(), to.getExternalMappingId());
+ AzureMapResponse distanceResponse = fetchDistanceFromAzureMaps(from, isUsrFrom, to, isUsrTo, true);
+
+ if (distanceResponse.distance != null) {
+ distanceMatrixRepository.saveDistance(distanceResponse.distance);
+ return Optional.of(distanceResponse.distance);
+ }
+
+ if(distanceResponse.errorType != AzureMapsErrorType.NO_ERROR) {
+ distanceMatrixRepository.saveDistance(getErrorDistance(cachedDistance, from, isUsrFrom, to, isUsrTo));
}
return Optional.empty();
}
+ private Distance getErrorDistance(Optional cachedDistance, Node from, boolean isUsrFrom, Node to, boolean isUsrTo) {
+ var distance = cachedDistance.orElse(new Distance());
+
+ distance.setState(DistanceMatrixState.EXCEPTION);
+ distance.setUpdatedAt(LocalDateTime.now());
+ distance.setRetries(distance.getRetries() == null ? 0 : distance.getRetries() + 1);
+
+ if(cachedDistance.isEmpty()) {
+ distance.setFromUserNodeId(isUsrFrom ? from.getId() : null);
+ distance.setFromNodeId(isUsrFrom ? null : from.getId());
+ distance.setToUserNodeId(isUsrTo ? to.getId() : null);
+ distance.setToNodeId(isUsrTo ? null : to.getId());
+
+ distance.setFromGeoLat(from.getGeoLat());
+ distance.setFromGeoLng(from.getGeoLng());
+ distance.setToGeoLat(to.getGeoLat());
+ distance.setToGeoLng(to.getGeoLng());
+ }
+
+ return distance;
+ }
+
private RouteDirectionsResponse fetchDistanceFromAzureMaps(BigDecimal fromLat, BigDecimal fromLng, BigDecimal toLat, BigDecimal toLng) {
String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API)
.queryParam("api-version", "1.0")
@@ -99,89 +139,144 @@ public class DistanceApiService {
return restTemplate.getForObject(url, RouteDirectionsResponse.class);
}
- //TODO;
-// private Optional handleAzureMapsError(HttpClientErrorException e,
-// Node from, boolean isUsrFrom,
-// Node to, boolean isUsrTo) {
-// try {
-// String responseBody = e.getResponseBodyAsString();
-// JsonNode errorNode = objectMapper.readTree(responseBody);
-//
-// String errorCode = errorNode.path("error").path("code").asText();
-// String errorMessage = errorNode.path("error").path("message").asText();
-//
-// logger.warn("Azure Maps API Error for nodes {} to {}: {} - {}",
-// from.getExternalMappingId(), to.getExternalMappingId(),
-// errorCode, errorMessage);
-//
-// // Spezifische Fehlerbehandlung
-// if (errorMessage.contains("MAP_MATCHING_FAILURE") ||
-// errorMessage.contains("NO_ROUTE_FOUND")) {
-//
-// logger.info("Route calculation failed, falling back to straight line distance");
-//
-// // Fallback auf Luftlinie
-// double straightLineKm = calculateHaversineDistance(
-// from.getGeoLat(), from.getGeoLng(),
-// to.getGeoLat(), to.getGeoLng()
-// );
-//
-// return createStraightLineDistance(from, isUsrFrom, to, isUsrTo, straightLineKm);
-// }
-//
-// } catch (Exception parseException) {
-// logger.error("Failed to parse Azure Maps error response", parseException);
-// }
-//
-// return Optional.empty();
-// }
- private Optional fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo) {
+ private AzureMapResponse fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo, boolean allowFixing) {
try {
-
RouteDirectionsResponse response = fetchDistanceFromAzureMaps(from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng());
-
- if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) {
- Integer distanceInMeters = response.getRoutes().getFirst().getSummary().getLengthInMeters();
-
- Distance distance = new Distance();
-
- if (isUsrFrom) {
- distance.setFromUserNodeId(from.getId());
- distance.setFromNodeId(null);
- } else {
- distance.setFromUserNodeId(null);
- distance.setFromNodeId(from.getId());
- }
-
- if (isUsrTo) {
- distance.setToUserNodeId(to.getId());
- distance.setToNodeId(null);
- } else {
- distance.setToUserNodeId(null);
- distance.setToNodeId(to.getId());
- }
-
- distance.setFromGeoLat(from.getGeoLat());
- distance.setFromGeoLng(from.getGeoLng());
- distance.setToGeoLat(to.getGeoLat());
- distance.setToGeoLng(to.getGeoLng());
- distance.setDistance(BigDecimal.valueOf(distanceInMeters));
- distance.setState(DistanceMatrixState.VALID);
- distance.setUpdatedAt(LocalDateTime.now());
-
- logger.info("Successfully fetched distance: {} meters", distanceInMeters);
- return Optional.of(distance);
- } else {
- logger.warn("No routes found in Azure Maps response");
- }
+ return convertToDistance(response, from, isUsrFrom, to, isUsrTo);
} catch (Exception e) {
- //TODO parse 400 Bad Request on GET request for "https://atlas.microsoft.com/route/directions/json": "{ "error": { "code": "400 BadRequest", "message": "Engine error while executing route request: MAP_MATCHING_FAILURE: Destination (31.364, 121.598)" }}
- // "{ "error": { "code": "400 BadRequest", "message": "Engine error while executing route request: NO_ROUTE_FOUND: Origin and destination have different ProductId's." }}"
+ if (HttpClientErrorException.class.isAssignableFrom(e.getClass()))
+ return handleAzureMapsError((HttpClientErrorException) e, from, isUsrFrom, to, isUsrTo, allowFixing);
+
logger.error("Error fetching distance from Azure Maps", e);
+ return new AzureMapResponse(null, AzureMapsErrorType.OTHER_ERROR, null);
+ }
+ }
+
+ private AzureMapResponse convertToDistance(RouteDirectionsResponse response, Node from, boolean isUsrFrom, Node to, boolean isUsrTo) {
+ if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) {
+ Integer distanceInMeters = response.getRoutes().getFirst().getSummary().getLengthInMeters();
+
+ Distance distance = new Distance();
+
+ if (isUsrFrom) {
+ distance.setFromUserNodeId(from.getId());
+ distance.setFromNodeId(null);
+ } else {
+ distance.setFromUserNodeId(null);
+ distance.setFromNodeId(from.getId());
+ }
+
+ if (isUsrTo) {
+ distance.setToUserNodeId(to.getId());
+ distance.setToNodeId(null);
+ } else {
+ distance.setToUserNodeId(null);
+ distance.setToNodeId(to.getId());
+ }
+
+ distance.setFromGeoLat(from.getGeoLat());
+ distance.setFromGeoLng(from.getGeoLng());
+ distance.setToGeoLat(to.getGeoLat());
+ distance.setToGeoLng(to.getGeoLng());
+ distance.setDistance(BigDecimal.valueOf(distanceInMeters));
+ distance.setState(DistanceMatrixState.VALID);
+ distance.setUpdatedAt(LocalDateTime.now());
+
+ // reset to 0 if on success
+ distance.setRetries(0);
+
+ logger.info("Successfully fetched distance: {} meters", distanceInMeters);
+ return new AzureMapResponse(distance, AzureMapsErrorType.NO_ERROR, null);
+ } else {
+ logger.error("No routes found in Azure Maps response");
+ return new AzureMapResponse(null, AzureMapsErrorType.ROUTE_ERROR, null);
+ }
+ }
+
+ private AzureMapResponse handleAzureMapsError(HttpClientErrorException e,
+ Node from, boolean isUsrFrom,
+ Node to, boolean isUsrTo, boolean allowFixing) {
+
+ try {
+ String responseBody = e.getResponseBodyAsString();
+ JsonNode errorNode = objectMapper.readTree(responseBody);
+
+ String errorCode = errorNode.path("error").path("code").asText();
+ String errorMessage = errorNode.path("error").path("message").asText();
+
+ logger.warn("Azure Maps API Error for nodes {} ({}) to {} ({}): {} - {}",
+ from.getExternalMappingId(), from.getId(), to.getExternalMappingId(), to.getId(),
+ errorCode, errorMessage);
+
+ if (errorMessage.contains("NO_ROUTE_FOUND"))
+ return new AzureMapResponse(null, AzureMapsErrorType.ROUTE_ERROR, null);
+
+ if (errorMessage.contains("MAP_MATCHING_FAILURE")) {
+ if (errorMessage.contains("Destination")) {
+ if (allowFixing) {
+ var fixedNode = fixNode(to);
+
+ if (fixedNode != null)
+ return fetchDistanceFromAzureMaps(from, isUsrFrom, fixedNode, isUsrTo, false);
+ }
+
+ return new AzureMapResponse(null, AzureMapsErrorType.NODE_ERROR, AzureMapsDefectiveNode.FROM);
+ }
+
+ if (errorMessage.contains("Origin")) {
+ if (allowFixing) {
+ var fixedNode = fixNode(from);
+
+ if (fixedNode != null)
+ return fetchDistanceFromAzureMaps(fixedNode, isUsrFrom, to, isUsrTo, false);
+ }
+
+ return new AzureMapResponse(null, AzureMapsErrorType.NODE_ERROR, AzureMapsDefectiveNode.TO);
+ }
+
+ return new AzureMapResponse(null, AzureMapsErrorType.NODE_ERROR, AzureMapsDefectiveNode.UNKNOWN);
+ }
+
+ } catch (Exception parseException) {
+ logger.error("Failed to parse Azure Maps error response", parseException);
}
- return Optional.empty();
+ return new AzureMapResponse(null, AzureMapsErrorType.OTHER_ERROR, null);
+ }
+
+ private Node fixNode(Node node) {
+
+ logger.info("Try to fix node {} ({}) ", node.getExternalMappingId(), node.getId());
+
+ Location location = geoApiService.locate(node.getAddress() + ", " + node.getCountryId());
+
+ if (location != null && location.getLatitude() != null && location.getLongitude() != null) {
+ if (0 != BigDecimal.valueOf(location.getLatitude()).compareTo(node.getGeoLat()) || 0 != BigDecimal.valueOf(location.getLongitude()).compareTo(node.getGeoLng())) {
+ logger.info("Fixed node {} ({}) coordinates {}, {} -> {}, {}", node.getExternalMappingId(), node.getId(), node.getGeoLat(), node.getGeoLng(), location.getLatitude(), location.getLongitude());
+
+ node.setGeoLng(BigDecimal.valueOf(location.getLongitude()));
+ node.setGeoLat(BigDecimal.valueOf(location.getLatitude()));
+
+
+ return node;
+ }
+ }
+
+ return null;
+ }
+
+ private enum AzureMapsErrorType {
+ NO_ERROR, NODE_ERROR, ROUTE_ERROR, OTHER_ERROR
+ }
+
+ private enum AzureMapsDefectiveNode {
+ FROM, TO, UNKNOWN
+ }
+
+ private record AzureMapResponse(Distance distance, AzureMapsErrorType errorType,
+ AzureMapsDefectiveNode defectiveNode) {
+
}
diff --git a/src/main/java/de/avatic/lcc/service/api/GeoApiService.java b/src/main/java/de/avatic/lcc/service/api/GeoApiService.java
index 1bc63f2..0bae27b 100644
--- a/src/main/java/de/avatic/lcc/service/api/GeoApiService.java
+++ b/src/main/java/de/avatic/lcc/service/api/GeoApiService.java
@@ -43,6 +43,7 @@ public class GeoApiService {
}
public GeocodingResult geocode(String address) {
+
if (address == null || address.trim().isEmpty()) {
logger.warn("Address is null or empty");
return null;
diff --git a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java
index a606ec1..a09a3e7 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java
@@ -4,7 +4,6 @@ import de.avatic.lcc.model.db.country.DetourIndex;
import de.avatic.lcc.model.db.country.IsoCode;
import de.avatic.lcc.model.db.nodes.Location;
import de.avatic.lcc.model.db.nodes.Node;
-import de.avatic.lcc.repositories.DistanceMatrixRepository;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.service.api.DistanceApiService;
import org.springframework.stereotype.Service;
@@ -21,29 +20,36 @@ public class DistanceService {
this.distanceApiService = distanceApiService;
}
- public double getDistance(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) {
- if (fast) return getDistanceFast(srcCountryId, src, destCountryId, dest);
+ public double getDistanceForLocation(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) {
+ var fastDistance = getDistanceFast(srcCountryId, src, destCountryId, dest);
+
+ // use fast distance if more than 3000km
+ if (fast || fastDistance > 3000) return fastDistance;
var distance = distanceApiService.getDistance(src, dest);
- if (distance.isEmpty()) return getDistanceFast(srcCountryId, src, destCountryId, dest);
+ if (distance.isEmpty()) return fastDistance;
return distance.map(value -> value/1000).orElse(0);
}
- public double getDistance(Node src, Node dest, boolean fast) {
+ public double getDistanceForNode(Node src, Node dest) {
+ return getDistanceForNode(src, dest, false);
+ }
+
+ public double getDistanceForNode(Node src, Node dest, boolean fast) {
var fastDistance = getDistanceFast(src, dest);
// use fast distance if more than 3000km
if (fast || fastDistance > 3000) return fastDistance;
var distance = distanceApiService.getDistance(src, src.isUserNode(), dest, dest.isUserNode());
- if (distance.isEmpty()) return getDistanceFast(src, dest);
+ if (distance.isEmpty()) return fastDistance;
return distance.map(value -> value.getDistance().intValue()/1000).orElse(0);
}
- public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) {
+ private double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) {
double srcLatitudeRadians = Math.toRadians(src.getLatitude());
double srcLongitudeRad = Math.toRadians(src.getLongitude());
double destLatitudeRadians = Math.toRadians(dest.getLatitude());
diff --git a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
index 5f474f9..a23d92c 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
@@ -219,6 +219,8 @@ public class RoutingService {
routeSection.setPostRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN));
routeSection.setPreRun(false);
+ routeSection.setDistance(section.getApproxDistance());
+
return routeSection;
}
@@ -334,7 +336,7 @@ public class RoutingService {
}
finalSection.setRate(matrixRate);
- finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, true));
+ finalSection.setApproxDistance(distanceService.getDistanceForNode(container.getSourceNode(), toNode));
rates.add(finalSection);
}
@@ -699,7 +701,7 @@ public class RoutingService {
if (matrixRate.isPresent()) {
matrixRateObj.setRate(matrixRate.get());
- matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, true));
+ matrixRateObj.setApproxDistance(distanceService.getDistanceForNode(startNode, endNode));
container.getRates().add(matrixRateObj);
return matrixRateObj;
} else {
@@ -927,16 +929,16 @@ public class RoutingService {
sb.append(sections.getLast().getFromNode().getDebugText());
for (var section : sections.reversed()) {
- sb.append(" -");
+ sb.append(" --[");
sb.append(section.getType().getDebugText());
- sb.append("-> ");
+ sb.append("]--> ");
sb.append(section.getToNode().getDebugText());
}
} else sb.append("Empty");
- return String.format("Route: [%s, %s]", quality.name().charAt(0) + (isFastest ? "\uD83D\uDE80" : "") + (isCheapest ? "\uD83D\uDCB2" : ""), sb);
+ return String.format("Route: [%s, %s]", quality.name() + (isFastest ? " FAST" : "") + (isCheapest ? " CHEAP" : ""), sb);
}
public void setQuality(ChainQuality quality) {
@@ -1074,8 +1076,8 @@ public class RoutingService {
}
private enum TemporaryRateObjectType {
- NEAR_BY("\uD83D\uDCCD"), MATRIX("\uD83D\uDCCA"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
-
+// NEAR_BY("\uD83D\uDCCD"), MATRIX("\uD83D\uDCCA"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
+ NEAR_BY("NEAR"), MATRIX("KM"), CONTAINER("CON"), POST_RUN("POST"), MAIN_RUN("MAIN");
private final String debugText;
TemporaryRateObjectType(String debugText) {
diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationJobProcessorManagementService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationJobProcessorManagementService.java
index 2cf6d26..0fb6949 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationJobProcessorManagementService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationJobProcessorManagementService.java
@@ -2,7 +2,6 @@ package de.avatic.lcc.service.calculation.execution;
import de.avatic.lcc.dto.calculation.execution.*;
import de.avatic.lcc.model.db.calculations.CalculationJob;
-import de.avatic.lcc.model.db.calculations.CalculationJobPriority;
import de.avatic.lcc.model.db.calculations.CalculationJobState;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.properties.PropertySet;
@@ -126,7 +125,7 @@ public class CalculationJobProcessorManagementService {
dto.setFailed(failed);
dto.setDrafts(draft);
dto.setRunning(running);
- dto.setTotal(completed + draft);
+ dto.setCompleted(completed);
return dto;
}
diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java
index ae22e04..377c058 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/RouteSectionCostCalculationService.java
@@ -1,9 +1,9 @@
package de.avatic.lcc.service.calculation.execution.steps;
-import de.avatic.lcc.model.calculation.ContainerCalculationResult;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
+import de.avatic.lcc.model.calculation.ContainerCalculationResult;
import de.avatic.lcc.model.db.calculations.CalculationJobRouteSection;
import de.avatic.lcc.model.db.nodes.Location;
import de.avatic.lcc.model.db.nodes.Node;
@@ -70,7 +70,7 @@ public class RouteSectionCostCalculationService {
Node fromNode = premise.getSupplierNodeId() != null ? nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow() : userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow();
Node toNode = nodeRepository.getById(destination.getDestinationNodeId()).orElseThrow();
- double distance = distanceService.getDistance(fromNode, toNode, false);
+ double distance = destination.getDistanceD2d() == null ? distanceService.getDistanceForNode(fromNode, toNode) : destination.getDistanceD2d().doubleValue();
result.setDistance(BigDecimal.valueOf(distance));
// Get rate and transit time
@@ -130,8 +130,14 @@ public class RouteSectionCostCalculationService {
// Get nodes and distance
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
RouteNode toNode = routeNodeRepository.getById(section.getToRouteNodeId()).orElseThrow();
- double distance = getDistance(fromNode, toNode);
- result.setDistance(BigDecimal.valueOf(distance));
+
+ if (section.getDistance() == null) {
+ double distance = getDistance(fromNode, toNode);
+ result.setDistance(BigDecimal.valueOf(distance));
+ } else {
+ result.setDistance(BigDecimal.valueOf(section.getDistance()));
+ }
+
// Get rate and transit time
BigDecimal rate;
@@ -143,7 +149,7 @@ public class RouteSectionCostCalculationService {
transitTime = containerRate.getLeadTime();
} else if (RateType.MATRIX == section.getRateType()) {
MatrixRate matrixRate = findMatrixRate(fromNode, toNode, periodId);
- rate = matrixRate.getRate().multiply(BigDecimal.valueOf(distance));
+ rate = matrixRate.getRate().multiply(result.getDistance());
transitTime = 3; // Default transit time for matrix rate
} else if (RateType.NEAR_BY == section.getRateType()) {
rate = BigDecimal.ZERO;
@@ -183,8 +189,8 @@ public class RouteSectionCostCalculationService {
BigDecimal annualCost = (containerCalculation.isWeightExceeded() ? prices.weightPrice.multiply(annualWeight) : prices.volumePrice.multiply(annualVolume));
- BigDecimal annualRiskCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getRiskFactor()) : annualCost;
- BigDecimal annualChanceCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getChanceFactor()): annualCost;
+ BigDecimal annualRiskCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getRiskFactor()) : annualCost;
+ BigDecimal annualChanceCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getChanceFactor()) : annualCost;
result.setAnnualRiskCost(annualRiskCost);
result.setAnnualChanceCost(annualChanceCost);
@@ -278,8 +284,8 @@ public class RouteSectionCostCalculationService {
private double getDistance(RouteNode fromNode, RouteNode toNode) {
- if(fromNode.getOutdated() || toNode.getOutdated()) {
- return distanceService.getDistance(
+ if (fromNode.getOutdated() || toNode.getOutdated()) {
+ return distanceService.getDistanceForLocation(
fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()),
toNode.getCountryId(), new Location(toNode.getGeoLng().doubleValue(), toNode.getGeoLat().doubleValue()), false);
}
@@ -287,14 +293,14 @@ public class RouteSectionCostCalculationService {
var optSrcNode = fromNode.getNodeId() == null ? userNodeRepository.getById(fromNode.getUserNodeId()) : nodeRepository.getById(fromNode.getNodeId());
var optDestNode = toNode.getNodeId() == null ? userNodeRepository.getById(toNode.getUserNodeId()) : nodeRepository.getById(toNode.getNodeId());
- if(optSrcNode.isEmpty() ) {
+ if (optSrcNode.isEmpty()) {
throw new NoSuchElementException("Source node not found for route section " + fromNode.getName());
}
- if(optDestNode.isEmpty() ) {
+ if (optDestNode.isEmpty()) {
throw new NoSuchElementException("Destination node not found for route section" + toNode.getName());
}
- return distanceService.getDistance(optSrcNode.get(), optDestNode.get(), false);
+ return distanceService.getDistanceForNode(optSrcNode.get(), optDestNode.get(), false);
}
diff --git a/src/main/resources/db/migration/V11__Schedule_Priority.sql b/src/main/resources/db/migration/V11__Schedule_Priority.sql
index f404a71..45fee6f 100644
--- a/src/main/resources/db/migration/V11__Schedule_Priority.sql
+++ b/src/main/resources/db/migration/V11__Schedule_Priority.sql
@@ -7,5 +7,13 @@ ALTER TABLE calculation_job
ADD INDEX idx_priority (priority);
ALTER TABLE distance_matrix
+ ADD COLUMN retries INT NOT NULL DEFAULT 0,
DROP CONSTRAINT chk_distance_matrix_state,
- ADD CONSTRAINT chk_distance_matrix_state CHECK (`state` IN ('VALID', 'STALE', 'EXCEPTION'));
\ No newline at end of file
+ ADD CONSTRAINT chk_distance_matrix_state CHECK (`state` IN ('VALID', 'STALE', 'EXCEPTION'));
+
+
+ALTER TABLE premise_destination
+ ADD COLUMN distance_d2d DECIMAL(15, 2) DEFAULT NULL COMMENT 'travel distance between the two nodes in meters';
+
+ALTER TABLE premise_route_section
+ ADD COLUMN distance DECIMAL(15, 2) DEFAULT NULL COMMENT 'travel distance between the two nodes in meters';
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 24600ca..47c35f3 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -433,6 +433,7 @@ CREATE TABLE IF NOT EXISTS premise_destination
is_d2d BOOLEAN DEFAULT FALSE,
rate_d2d DECIMAL(15, 2) DEFAULT NULL CHECK (rate_d2d >= 0),
lead_time_d2d INT UNSIGNED DEFAULT NULL CHECK (lead_time_d2d >= 0),
+ distance_d2d DECIMAL(15, 2) DEFAULT NULL,
repacking_cost DECIMAL(15, 2) DEFAULT NULL CHECK (repacking_cost >= 0),
handling_cost DECIMAL(15, 2) DEFAULT NULL CHECK (handling_cost >= 0),
disposal_cost DECIMAL(15, 2) DEFAULT NULL CHECK (disposal_cost >= 0),
@@ -496,6 +497,7 @@ CREATE TABLE IF NOT EXISTS premise_route_section
is_main_run BOOLEAN DEFAULT FALSE,
is_post_run BOOLEAN DEFAULT FALSE,
is_outdated BOOLEAN DEFAULT FALSE,
+ distance DECIMAL(15, 2) DEFAULT NULL COMMENT 'travel distance between the two nodes in meters',
CONSTRAINT fk_premise_route_section_premise_route_id FOREIGN KEY (premise_route_id) REFERENCES premise_route (id),
FOREIGN KEY (from_route_node_id) REFERENCES premise_route_node (id),
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),