Enhance distance handling in routing logic: add new distance attributes, improve fallback logic, and refine Azure Maps API integration.
This commit is contained in:
parent
735d8a707b
commit
3aa86b4eea
22 changed files with 357 additions and 141 deletions
|
|
@ -116,7 +116,7 @@ export default {
|
||||||
case 'warning':
|
case 'warning':
|
||||||
return 'secondary'
|
return 'secondary'
|
||||||
case 'info':
|
case 'info':
|
||||||
return 'primary'
|
return 'secondary'
|
||||||
case 'error':
|
case 'error':
|
||||||
return 'exception'
|
return 'exception'
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dashboard-box-info">
|
<div class="dashboard-box-info">
|
||||||
<div v-if="total !== null" class="dashboard-box-number">{{ total }}</div>
|
<div v-if="completed !== null" class="dashboard-box-number">{{ completed }}</div>
|
||||||
<div v-else class="dashboard-box-number"><spinner size="s"/></div>
|
<div v-else class="dashboard-box-number"><spinner size="s"/></div>
|
||||||
<div class="dashboard-box-number-text">Total</div>
|
<div class="dashboard-box-number-text">Completed</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -94,8 +94,8 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useDashboardStore),
|
...mapStores(useDashboardStore),
|
||||||
total() {
|
completed() {
|
||||||
return this.dashboardStore.total
|
return this.dashboardStore.completed
|
||||||
},
|
},
|
||||||
drafts() {
|
drafts() {
|
||||||
return this.dashboardStore.drafts
|
return this.dashboardStore.drafts
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export const useBulkOperationStore = defineStore('bulkOperation', {
|
||||||
useNotificationStore().addNotification({
|
useNotificationStore().addNotification({
|
||||||
title: 'Bulk operation',
|
title: 'Bulk operation',
|
||||||
message: 'All your bulk operations have been completed.',
|
message: 'All your bulk operations have been completed.',
|
||||||
type: 'success',
|
variant: 'info',
|
||||||
icon: 'stack',
|
icon: 'stack',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import performRequest from "@/backend.js";
|
import performRequest from "@/backend.js";
|
||||||
import {config} from '@/config'
|
import {config} from '@/config'
|
||||||
|
import {useNotificationStore} from "@/store/notification.js";
|
||||||
|
|
||||||
export const useDashboardStore = defineStore('dashboard', {
|
export const useDashboardStore = defineStore('dashboard', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
|
@ -9,9 +10,9 @@ export const useDashboardStore = defineStore('dashboard', {
|
||||||
pullTimer: null,
|
pullTimer: null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
total(state) {
|
completed(state) {
|
||||||
if (state.stats)
|
if (state.stats)
|
||||||
return state.stats.total;
|
return state.stats.completed;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
@ -38,13 +39,23 @@ export const useDashboardStore = defineStore('dashboard', {
|
||||||
async load() {
|
async load() {
|
||||||
const url = `${config.backendUrl}/dashboard`;
|
const url = `${config.backendUrl}/dashboard`;
|
||||||
const resp = await performRequest(this, 'GET', url, null);
|
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;
|
this.stats = resp.data;
|
||||||
},
|
},
|
||||||
startPulling() {
|
startPulling() {
|
||||||
if (this.pullTimer) return
|
if (this.pullTimer) return
|
||||||
|
|
||||||
this.pullTimer = setTimeout(() => {
|
this.pullTimer = setTimeout(async () => {
|
||||||
this.pull()
|
await this.pull()
|
||||||
}, this.pullInterval)
|
}, this.pullInterval)
|
||||||
},
|
},
|
||||||
stopPulling() {
|
stopPulling() {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ export const useNotificationStore = defineStore('notification', {
|
||||||
return this.notifications.pop();
|
return this.notifications.pop();
|
||||||
},
|
},
|
||||||
addNotification(notification) {
|
addNotification(notification) {
|
||||||
|
|
||||||
|
console.log("add notification", notification, this.notifications.length)
|
||||||
|
|
||||||
this.notifications.push({
|
this.notifications.push({
|
||||||
icon: notification.icon ?? null,
|
icon: notification.icon ?? null,
|
||||||
message: notification.message ?? 'Unknown notification',
|
message: notification.message ?? 'Unknown notification',
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||||
import org.springframework.security.web.csrf.CsrfToken;
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
|
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.util.StringUtils;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
@ -111,7 +111,7 @@ public class SecurityConfig {
|
||||||
.exceptionHandling(ex -> ex
|
.exceptionHandling(ex -> ex
|
||||||
.defaultAuthenticationEntryPointFor(
|
.defaultAuthenticationEntryPointFor(
|
||||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||||
new AntPathRequestMatcher("/api/**")
|
PathPatternRequestMatcher.withDefaults().matcher("/api/**")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.csrf(csrf -> csrf
|
.csrf(csrf -> csrf
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package de.avatic.lcc.dto.calculation.execution;
|
||||||
|
|
||||||
public class CalculationProcessingOverviewDTO {
|
public class CalculationProcessingOverviewDTO {
|
||||||
|
|
||||||
private Integer total;
|
private Integer completed;
|
||||||
|
|
||||||
private Integer running;
|
private Integer running;
|
||||||
|
|
||||||
|
|
@ -11,12 +11,12 @@ public class CalculationProcessingOverviewDTO {
|
||||||
private Integer failed;
|
private Integer failed;
|
||||||
|
|
||||||
|
|
||||||
public Integer getTotal() {
|
public Integer getCompleted() {
|
||||||
return total;
|
return completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTotal(Integer total) {
|
public void setCompleted(Integer completed) {
|
||||||
this.total = total;
|
this.completed = completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getRunning() {
|
public Integer getRunning() {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ public class Distance {
|
||||||
|
|
||||||
private DistanceMatrixState state;
|
private DistanceMatrixState state;
|
||||||
|
|
||||||
|
private Integer retries;
|
||||||
|
|
||||||
private Integer fromNodeId;
|
private Integer fromNodeId;
|
||||||
|
|
||||||
|
|
@ -144,4 +145,12 @@ public class Distance {
|
||||||
public void setToUserNodeId(Integer toUserNodeId) {
|
public void setToUserNodeId(Integer toUserNodeId) {
|
||||||
this.toUserNodeId = toUserNodeId;
|
this.toUserNodeId = toUserNodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getRetries() {
|
||||||
|
return retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetries(Integer retries) {
|
||||||
|
this.retries = retries;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ public class Destination {
|
||||||
|
|
||||||
private Integer countryId;
|
private Integer countryId;
|
||||||
|
|
||||||
|
private BigDecimal distanceD2d;
|
||||||
|
|
||||||
public Integer getLeadTimeD2d() {
|
public Integer getLeadTimeD2d() {
|
||||||
return leadTimeD2d;
|
return leadTimeD2d;
|
||||||
}
|
}
|
||||||
|
|
@ -134,4 +136,12 @@ public class Destination {
|
||||||
public void setDisposalCost(BigDecimal disposalCost) {
|
public void setDisposalCost(BigDecimal disposalCost) {
|
||||||
this.disposalCost = disposalCost;
|
this.disposalCost = disposalCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getDistanceD2d() {
|
||||||
|
return distanceD2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistanceD2d(BigDecimal distanceD2d) {
|
||||||
|
this.distanceD2d = distanceD2d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ public class RouteSection {
|
||||||
|
|
||||||
private Integer toRouteNodeId;
|
private Integer toRouteNodeId;
|
||||||
|
|
||||||
|
private Double distance;
|
||||||
|
|
||||||
public RateType getRateType() {
|
public RateType getRateType() {
|
||||||
return rateType;
|
return rateType;
|
||||||
}
|
}
|
||||||
|
|
@ -114,4 +116,12 @@ public class RouteSection {
|
||||||
public void setToRouteNodeId(Integer toRouteNodeId) {
|
public void setToRouteNodeId(Integer toRouteNodeId) {
|
||||||
this.toRouteNodeId = toRouteNodeId;
|
this.toRouteNodeId = toRouteNodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Double getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistance(Double distance) {
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ public class DistanceMatrixRepository {
|
||||||
String fromCol = isUsrFrom ? "from_user_node_id" : "from_node_id";
|
String fromCol = isUsrFrom ? "from_user_node_id" : "from_node_id";
|
||||||
String toCol = isUsrTo ? "to_user_node_id" : "to_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(),
|
var distance = jdbcTemplate.query(query, new DistanceMapper(),
|
||||||
src.getId(), dest.getId(), DistanceMatrixState.VALID.name());
|
src.getId(), dest.getId());
|
||||||
|
|
||||||
if (distance.isEmpty())
|
if (distance.isEmpty())
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|
@ -40,6 +40,12 @@ public class DistanceMatrixRepository {
|
||||||
return Optional.of(distance.getFirst());
|
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
|
@Transactional
|
||||||
public void saveDistance(Distance distance) {
|
public void saveDistance(Distance distance) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -119,10 +125,20 @@ public class DistanceMatrixRepository {
|
||||||
public Distance mapRow(ResultSet rs, int rowNum) throws SQLException {
|
public Distance mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
Distance entity = new Distance();
|
Distance entity = new Distance();
|
||||||
|
|
||||||
entity.setFromNodeId(rs.getInt("from_node_id"));
|
entity.setId(rs.getInt("id"));
|
||||||
entity.setToNodeId(rs.getInt("to_node_id"));
|
|
||||||
entity.setFromNodeId(rs.getInt("from_user_node_id"));
|
var fromNodeId = rs.getInt("from_node_id");
|
||||||
entity.setToNodeId(rs.getInt("to_user_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.setDistance(rs.getBigDecimal("distance"));
|
||||||
entity.setFromGeoLng(rs.getBigDecimal("from_geo_lng"));
|
entity.setFromGeoLng(rs.getBigDecimal("from_geo_lng"));
|
||||||
entity.setFromGeoLat(rs.getBigDecimal("from_geo_lat"));
|
entity.setFromGeoLat(rs.getBigDecimal("from_geo_lat"));
|
||||||
|
|
@ -131,6 +147,9 @@ public class DistanceMatrixRepository {
|
||||||
entity.setState(DistanceMatrixState.valueOf(rs.getString("state")));
|
entity.setState(DistanceMatrixState.valueOf(rs.getString("state")));
|
||||||
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
|
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
|
||||||
|
|
||||||
|
var retries = rs.getInt("retries");
|
||||||
|
entity.setRetries(rs.wasNull() ? 0 : retries);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ public class DestinationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@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) {
|
if (id == null) {
|
||||||
throw new InvalidArgumentException("ID cannot be null");
|
throw new InvalidArgumentException("ID cannot be null");
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +99,9 @@ public class DestinationRepository {
|
||||||
setClauses.add("lead_time_d2d = :d2dLeadTime");
|
setClauses.add("lead_time_d2d = :d2dLeadTime");
|
||||||
parameters.put("d2dLeadTime", setD2d ? d2dLeadTime : null);
|
parameters.put("d2dLeadTime", setD2d ? d2dLeadTime : null);
|
||||||
|
|
||||||
|
setClauses.add("distance_d2d = :distanceD2d");
|
||||||
|
parameters.put("distanceD2d", distanceD2d);
|
||||||
|
|
||||||
|
|
||||||
if (annualAmount != null) {
|
if (annualAmount != null) {
|
||||||
setClauses.add("annual_amount = :annualAmount");
|
setClauses.add("annual_amount = :annualAmount");
|
||||||
|
|
@ -268,7 +271,7 @@ public class DestinationRepository {
|
||||||
public Integer insert(Destination destination) {
|
public Integer insert(Destination destination) {
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
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 -> {
|
jdbcTemplate.update(connection -> {
|
||||||
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
|
@ -297,6 +300,8 @@ public class DestinationRepository {
|
||||||
ps.setBigDecimal(11, destination.getGeoLat());
|
ps.setBigDecimal(11, destination.getGeoLat());
|
||||||
ps.setBigDecimal(12, destination.getGeoLng());
|
ps.setBigDecimal(12, destination.getGeoLng());
|
||||||
|
|
||||||
|
ps.setBigDecimal(13, destination.getDistanceD2d());
|
||||||
|
|
||||||
return ps;
|
return ps;
|
||||||
}, keyHolder);
|
}, keyHolder);
|
||||||
|
|
||||||
|
|
@ -363,6 +368,8 @@ public class DestinationRepository {
|
||||||
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
|
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
|
||||||
entity.setCountryId(rs.getInt("country_id"));
|
entity.setCountryId(rs.getInt("country_id"));
|
||||||
|
|
||||||
|
entity.setDistanceD2d(rs.getBigDecimal("distance_d2d"));
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||||
import org.springframework.jdbc.support.KeyHolder;
|
import org.springframework.jdbc.support.KeyHolder;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
@ -45,8 +46,8 @@ public class RouteSectionRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer insert(RouteSection premiseRouteSection) {
|
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) " +
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
|
|
||||||
jdbcTemplate.update(connection -> {
|
jdbcTemplate.update(connection -> {
|
||||||
|
|
@ -61,6 +62,7 @@ public class RouteSectionRepository {
|
||||||
ps.setBoolean(8, premiseRouteSection.getMainRun());
|
ps.setBoolean(8, premiseRouteSection.getMainRun());
|
||||||
ps.setBoolean(9, premiseRouteSection.getPostRun());
|
ps.setBoolean(9, premiseRouteSection.getPostRun());
|
||||||
ps.setBoolean(10, premiseRouteSection.getOutdated());
|
ps.setBoolean(10, premiseRouteSection.getOutdated());
|
||||||
|
ps.setBigDecimal(11, premiseRouteSection.getDistance() == null ? null : BigDecimal.valueOf(premiseRouteSection.getDistance()));
|
||||||
return ps;
|
return ps;
|
||||||
}, keyHolder);
|
}, keyHolder);
|
||||||
|
|
||||||
|
|
@ -92,6 +94,9 @@ public class RouteSectionRepository {
|
||||||
entity.setPostRun(rs.getBoolean("is_post_run"));
|
entity.setPostRun(rs.getBoolean("is_post_run"));
|
||||||
entity.setOutdated(rs.getBoolean("is_outdated"));
|
entity.setOutdated(rs.getBoolean("is_outdated"));
|
||||||
|
|
||||||
|
var distance = rs.getBigDecimal("distance");
|
||||||
|
entity.setDistance(rs.wasNull() ? null : distance.doubleValue());
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import de.avatic.lcc.model.db.premises.route.*;
|
||||||
import de.avatic.lcc.repositories.NodeRepository;
|
import de.avatic.lcc.repositories.NodeRepository;
|
||||||
import de.avatic.lcc.repositories.premise.*;
|
import de.avatic.lcc.repositories.premise.*;
|
||||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
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.calculation.RoutingService;
|
||||||
import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
|
import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
|
||||||
import de.avatic.lcc.service.users.AuthorizationService;
|
import de.avatic.lcc.service.users.AuthorizationService;
|
||||||
|
|
@ -35,8 +36,9 @@ public class DestinationService {
|
||||||
private final PremiseRepository premiseRepository;
|
private final PremiseRepository premiseRepository;
|
||||||
private final UserNodeRepository userNodeRepository;
|
private final UserNodeRepository userNodeRepository;
|
||||||
private final AuthorizationService authorizationService;
|
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.destinationRepository = destinationRepository;
|
||||||
this.destinationTransformer = destinationTransformer;
|
this.destinationTransformer = destinationTransformer;
|
||||||
this.routeRepository = routeRepository;
|
this.routeRepository = routeRepository;
|
||||||
|
|
@ -47,6 +49,7 @@ public class DestinationService {
|
||||||
this.premiseRepository = premiseRepository;
|
this.premiseRepository = premiseRepository;
|
||||||
this.userNodeRepository = userNodeRepository;
|
this.userNodeRepository = userNodeRepository;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
|
this.distanceService = distanceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -199,11 +202,30 @@ public class DestinationService {
|
||||||
destinationUpdateDTO.getDisposalCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getDisposalCost().doubleValue()),
|
destinationUpdateDTO.getDisposalCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getDisposalCost().doubleValue()),
|
||||||
destinationUpdateDTO.getHandlingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getHandlingCost().doubleValue()),
|
destinationUpdateDTO.getHandlingCost() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getHandlingCost().doubleValue()),
|
||||||
destinationUpdateDTO.getD2d(), destinationUpdateDTO.getRateD2d() == null ? null : BigDecimal.valueOf(destinationUpdateDTO.getRateD2d().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<RouteIds, List<RouteInformation>> findRoutes(List<Premise> premisses, Map<Integer, List<Integer>> routingRequest) {
|
private Map<RouteIds, List<RouteInformation>> findRoutes(List<Premise> premisses, Map<Integer, List<Integer>> routingRequest) {
|
||||||
|
|
||||||
Map<RouteIds, List<RouteInformation>> routes = new HashMap<>();
|
Map<RouteIds, List<RouteInformation>> routes = new HashMap<>();
|
||||||
|
|
@ -267,6 +289,7 @@ public class DestinationService {
|
||||||
premiseRouteSection.setFromRouteNodeId(fromNodeId);
|
premiseRouteSection.setFromRouteNodeId(fromNodeId);
|
||||||
premiseRouteSection.setToRouteNodeId(toNodeId);
|
premiseRouteSection.setToRouteNodeId(toNodeId);
|
||||||
|
|
||||||
|
|
||||||
routeSectionRepository.insert(premiseRouteSection);
|
routeSectionRepository.insert(premiseRouteSection);
|
||||||
|
|
||||||
fromNodeId = toNodeId;
|
fromNodeId = toNodeId;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package de.avatic.lcc.service.api;
|
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.azuremaps.route.RouteDirectionsResponse;
|
||||||
import de.avatic.lcc.model.db.nodes.Distance;
|
import de.avatic.lcc.model.db.nodes.Distance;
|
||||||
import de.avatic.lcc.model.db.nodes.DistanceMatrixState;
|
import de.avatic.lcc.model.db.nodes.DistanceMatrixState;
|
||||||
|
|
@ -10,6 +12,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
|
@ -25,14 +28,18 @@ public class DistanceApiService {
|
||||||
|
|
||||||
private final DistanceMatrixRepository distanceMatrixRepository;
|
private final DistanceMatrixRepository distanceMatrixRepository;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final GeoApiService geoApiService;
|
||||||
|
|
||||||
@Value("${azure.maps.subscription.key}")
|
@Value("${azure.maps.subscription.key}")
|
||||||
private String subscriptionKey;
|
private String subscriptionKey;
|
||||||
|
|
||||||
public DistanceApiService(DistanceMatrixRepository distanceMatrixRepository,
|
public DistanceApiService(DistanceMatrixRepository distanceMatrixRepository,
|
||||||
RestTemplate restTemplate) {
|
RestTemplate restTemplate, ObjectMapper objectMapper, GeoApiService geoApiService) {
|
||||||
this.distanceMatrixRepository = distanceMatrixRepository;
|
this.distanceMatrixRepository = distanceMatrixRepository;
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.geoApiService = geoApiService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Integer> getDistance(Location from, Location to) {
|
public Optional<Integer> getDistance(Location from, Location to) {
|
||||||
|
|
@ -70,22 +77,55 @@ public class DistanceApiService {
|
||||||
|
|
||||||
Optional<Distance> cachedDistance = distanceMatrixRepository.getDistance(from, isUsrFrom, to, isUsrTo);
|
Optional<Distance> 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());
|
logger.info("Found cached distance from node {} to node {}", from.getExternalMappingId(), to.getExternalMappingId());
|
||||||
return cachedDistance;
|
return cachedDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Fetching distance from Azure Maps for nodes {} to {}", from.getExternalMappingId(), to.getExternalMappingId());
|
if (cachedDistance.isPresent() && cachedDistance.get().getState() == DistanceMatrixState.EXCEPTION) {
|
||||||
Optional<Distance> fetchedDistance = fetchDistanceFromAzureMaps(from, isUsrFrom, to, isUsrTo);
|
if (cachedDistance.get().getRetries() >= 3)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
if (fetchedDistance.isPresent()) {
|
distanceMatrixRepository.updateRetries(cachedDistance.get().getId());
|
||||||
distanceMatrixRepository.saveDistance(fetchedDistance.get());
|
}
|
||||||
return fetchedDistance;
|
|
||||||
|
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();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Distance getErrorDistance(Optional<Distance> 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) {
|
private RouteDirectionsResponse fetchDistanceFromAzureMaps(BigDecimal fromLat, BigDecimal fromLng, BigDecimal toLat, BigDecimal toLng) {
|
||||||
String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API)
|
String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API)
|
||||||
.queryParam("api-version", "1.0")
|
.queryParam("api-version", "1.0")
|
||||||
|
|
@ -99,89 +139,144 @@ public class DistanceApiService {
|
||||||
return restTemplate.getForObject(url, RouteDirectionsResponse.class);
|
return restTemplate.getForObject(url, RouteDirectionsResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO;
|
|
||||||
// private Optional<Distance> 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<Distance> fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo) {
|
private AzureMapResponse fetchDistanceFromAzureMaps(Node from, boolean isUsrFrom, Node to, boolean isUsrTo, boolean allowFixing) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
RouteDirectionsResponse response = fetchDistanceFromAzureMaps(from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng());
|
RouteDirectionsResponse response = fetchDistanceFromAzureMaps(from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng());
|
||||||
|
return convertToDistance(response, from, isUsrFrom, to, 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());
|
|
||||||
|
|
||||||
logger.info("Successfully fetched distance: {} meters", distanceInMeters);
|
|
||||||
return Optional.of(distance);
|
|
||||||
} else {
|
|
||||||
logger.warn("No routes found in Azure Maps response");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//TODO parse 400 Bad Request on GET request for "https://atlas.microsoft.com/route/directions/json": "{<EOL> "error": {<EOL> "code": "400 BadRequest",<EOL> "message": "Engine error while executing route request: MAP_MATCHING_FAILURE: Destination (31.364, 121.598)"<EOL> }<EOL>}
|
if (HttpClientErrorException.class.isAssignableFrom(e.getClass()))
|
||||||
// "{<EOL> "error": {<EOL> "code": "400 BadRequest",<EOL> "message": "Engine error while executing route request: NO_ROUTE_FOUND: Origin and destination have different ProductId's."<EOL> }<EOL>}"
|
return handleAzureMapsError((HttpClientErrorException) e, from, isUsrFrom, to, isUsrTo, allowFixing);
|
||||||
|
|
||||||
logger.error("Error fetching distance from Azure Maps", e);
|
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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public class GeoApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeocodingResult geocode(String address) {
|
public GeocodingResult geocode(String address) {
|
||||||
|
|
||||||
if (address == null || address.trim().isEmpty()) {
|
if (address == null || address.trim().isEmpty()) {
|
||||||
logger.warn("Address is null or empty");
|
logger.warn("Address is null or empty");
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -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.country.IsoCode;
|
||||||
import de.avatic.lcc.model.db.nodes.Location;
|
import de.avatic.lcc.model.db.nodes.Location;
|
||||||
import de.avatic.lcc.model.db.nodes.Node;
|
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.repositories.country.CountryRepository;
|
||||||
import de.avatic.lcc.service.api.DistanceApiService;
|
import de.avatic.lcc.service.api.DistanceApiService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -21,29 +20,36 @@ public class DistanceService {
|
||||||
this.distanceApiService = distanceApiService;
|
this.distanceApiService = distanceApiService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDistance(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) {
|
public double getDistanceForLocation(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) {
|
||||||
if (fast) return getDistanceFast(srcCountryId, src, destCountryId, dest);
|
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);
|
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);
|
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);
|
var fastDistance = getDistanceFast(src, dest);
|
||||||
|
|
||||||
// use fast distance if more than 3000km
|
// use fast distance if more than 3000km
|
||||||
if (fast || fastDistance > 3000) return fastDistance;
|
if (fast || fastDistance > 3000) return fastDistance;
|
||||||
|
|
||||||
var distance = distanceApiService.getDistance(src, src.isUserNode(), dest, dest.isUserNode());
|
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);
|
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 srcLatitudeRadians = Math.toRadians(src.getLatitude());
|
||||||
double srcLongitudeRad = Math.toRadians(src.getLongitude());
|
double srcLongitudeRad = Math.toRadians(src.getLongitude());
|
||||||
double destLatitudeRadians = Math.toRadians(dest.getLatitude());
|
double destLatitudeRadians = Math.toRadians(dest.getLatitude());
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,8 @@ public class RoutingService {
|
||||||
routeSection.setPostRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN));
|
routeSection.setPostRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.POST_RUN));
|
||||||
routeSection.setPreRun(false);
|
routeSection.setPreRun(false);
|
||||||
|
|
||||||
|
routeSection.setDistance(section.getApproxDistance());
|
||||||
|
|
||||||
return routeSection;
|
return routeSection;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -334,7 +336,7 @@ public class RoutingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
finalSection.setRate(matrixRate);
|
finalSection.setRate(matrixRate);
|
||||||
finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, true));
|
finalSection.setApproxDistance(distanceService.getDistanceForNode(container.getSourceNode(), toNode));
|
||||||
rates.add(finalSection);
|
rates.add(finalSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -699,7 +701,7 @@ public class RoutingService {
|
||||||
|
|
||||||
if (matrixRate.isPresent()) {
|
if (matrixRate.isPresent()) {
|
||||||
matrixRateObj.setRate(matrixRate.get());
|
matrixRateObj.setRate(matrixRate.get());
|
||||||
matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, true));
|
matrixRateObj.setApproxDistance(distanceService.getDistanceForNode(startNode, endNode));
|
||||||
container.getRates().add(matrixRateObj);
|
container.getRates().add(matrixRateObj);
|
||||||
return matrixRateObj;
|
return matrixRateObj;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -927,16 +929,16 @@ public class RoutingService {
|
||||||
|
|
||||||
sb.append(sections.getLast().getFromNode().getDebugText());
|
sb.append(sections.getLast().getFromNode().getDebugText());
|
||||||
for (var section : sections.reversed()) {
|
for (var section : sections.reversed()) {
|
||||||
sb.append(" -");
|
sb.append(" --[");
|
||||||
sb.append(section.getType().getDebugText());
|
sb.append(section.getType().getDebugText());
|
||||||
sb.append("-> ");
|
sb.append("]--> ");
|
||||||
sb.append(section.getToNode().getDebugText());
|
sb.append(section.getToNode().getDebugText());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else sb.append("Empty");
|
} 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) {
|
public void setQuality(ChainQuality quality) {
|
||||||
|
|
@ -1074,8 +1076,8 @@ public class RoutingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TemporaryRateObjectType {
|
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;
|
private final String debugText;
|
||||||
|
|
||||||
TemporaryRateObjectType(String debugText) {
|
TemporaryRateObjectType(String debugText) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package de.avatic.lcc.service.calculation.execution;
|
||||||
|
|
||||||
import de.avatic.lcc.dto.calculation.execution.*;
|
import de.avatic.lcc.dto.calculation.execution.*;
|
||||||
import de.avatic.lcc.model.db.calculations.CalculationJob;
|
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.calculations.CalculationJobState;
|
||||||
import de.avatic.lcc.model.db.premises.PremiseState;
|
import de.avatic.lcc.model.db.premises.PremiseState;
|
||||||
import de.avatic.lcc.model.db.properties.PropertySet;
|
import de.avatic.lcc.model.db.properties.PropertySet;
|
||||||
|
|
@ -126,7 +125,7 @@ public class CalculationJobProcessorManagementService {
|
||||||
dto.setFailed(failed);
|
dto.setFailed(failed);
|
||||||
dto.setDrafts(draft);
|
dto.setDrafts(draft);
|
||||||
dto.setRunning(running);
|
dto.setRunning(running);
|
||||||
dto.setTotal(completed + draft);
|
dto.setCompleted(completed);
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package de.avatic.lcc.service.calculation.execution.steps;
|
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.ContainerType;
|
||||||
import de.avatic.lcc.dto.generic.RateType;
|
import de.avatic.lcc.dto.generic.RateType;
|
||||||
import de.avatic.lcc.dto.generic.TransportType;
|
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.calculations.CalculationJobRouteSection;
|
||||||
import de.avatic.lcc.model.db.nodes.Location;
|
import de.avatic.lcc.model.db.nodes.Location;
|
||||||
import de.avatic.lcc.model.db.nodes.Node;
|
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 fromNode = premise.getSupplierNodeId() != null ? nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow() : userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow();
|
||||||
Node toNode = nodeRepository.getById(destination.getDestinationNodeId()).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));
|
result.setDistance(BigDecimal.valueOf(distance));
|
||||||
|
|
||||||
// Get rate and transit time
|
// Get rate and transit time
|
||||||
|
|
@ -130,8 +130,14 @@ public class RouteSectionCostCalculationService {
|
||||||
// Get nodes and distance
|
// Get nodes and distance
|
||||||
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
|
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
|
||||||
RouteNode toNode = routeNodeRepository.getById(section.getToRouteNodeId()).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
|
// Get rate and transit time
|
||||||
BigDecimal rate;
|
BigDecimal rate;
|
||||||
|
|
@ -143,7 +149,7 @@ public class RouteSectionCostCalculationService {
|
||||||
transitTime = containerRate.getLeadTime();
|
transitTime = containerRate.getLeadTime();
|
||||||
} else if (RateType.MATRIX == section.getRateType()) {
|
} else if (RateType.MATRIX == section.getRateType()) {
|
||||||
MatrixRate matrixRate = findMatrixRate(fromNode, toNode, periodId);
|
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
|
transitTime = 3; // Default transit time for matrix rate
|
||||||
} else if (RateType.NEAR_BY == section.getRateType()) {
|
} else if (RateType.NEAR_BY == section.getRateType()) {
|
||||||
rate = BigDecimal.ZERO;
|
rate = BigDecimal.ZERO;
|
||||||
|
|
@ -183,8 +189,8 @@ public class RouteSectionCostCalculationService {
|
||||||
|
|
||||||
|
|
||||||
BigDecimal annualCost = (containerCalculation.isWeightExceeded() ? prices.weightPrice.multiply(annualWeight) : prices.volumePrice.multiply(annualVolume));
|
BigDecimal annualCost = (containerCalculation.isWeightExceeded() ? prices.weightPrice.multiply(annualWeight) : prices.volumePrice.multiply(annualVolume));
|
||||||
BigDecimal annualRiskCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getRiskFactor()) : annualCost;
|
BigDecimal annualRiskCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getRiskFactor()) : annualCost;
|
||||||
BigDecimal annualChanceCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getChanceFactor()): annualCost;
|
BigDecimal annualChanceCost = useRiskChange ? annualCost.multiply(chanceRiskFactors.getChanceFactor()) : annualCost;
|
||||||
|
|
||||||
result.setAnnualRiskCost(annualRiskCost);
|
result.setAnnualRiskCost(annualRiskCost);
|
||||||
result.setAnnualChanceCost(annualChanceCost);
|
result.setAnnualChanceCost(annualChanceCost);
|
||||||
|
|
@ -278,8 +284,8 @@ public class RouteSectionCostCalculationService {
|
||||||
|
|
||||||
private double getDistance(RouteNode fromNode, RouteNode toNode) {
|
private double getDistance(RouteNode fromNode, RouteNode toNode) {
|
||||||
|
|
||||||
if(fromNode.getOutdated() || toNode.getOutdated()) {
|
if (fromNode.getOutdated() || toNode.getOutdated()) {
|
||||||
return distanceService.getDistance(
|
return distanceService.getDistanceForLocation(
|
||||||
fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()),
|
fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()),
|
||||||
toNode.getCountryId(), new Location(toNode.getGeoLng().doubleValue(), toNode.getGeoLat().doubleValue()), false);
|
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 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());
|
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());
|
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());
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,13 @@ ALTER TABLE calculation_job
|
||||||
ADD INDEX idx_priority (priority);
|
ADD INDEX idx_priority (priority);
|
||||||
|
|
||||||
ALTER TABLE distance_matrix
|
ALTER TABLE distance_matrix
|
||||||
|
ADD COLUMN retries INT NOT NULL DEFAULT 0,
|
||||||
DROP CONSTRAINT chk_distance_matrix_state,
|
DROP CONSTRAINT chk_distance_matrix_state,
|
||||||
ADD CONSTRAINT chk_distance_matrix_state CHECK (`state` IN ('VALID', 'STALE', 'EXCEPTION'));
|
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';
|
||||||
|
|
@ -433,6 +433,7 @@ CREATE TABLE IF NOT EXISTS premise_destination
|
||||||
is_d2d BOOLEAN DEFAULT FALSE,
|
is_d2d BOOLEAN DEFAULT FALSE,
|
||||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL CHECK (rate_d2d >= 0),
|
rate_d2d DECIMAL(15, 2) DEFAULT NULL CHECK (rate_d2d >= 0),
|
||||||
lead_time_d2d INT UNSIGNED DEFAULT NULL CHECK (lead_time_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),
|
repacking_cost DECIMAL(15, 2) DEFAULT NULL CHECK (repacking_cost >= 0),
|
||||||
handling_cost DECIMAL(15, 2) DEFAULT NULL CHECK (handling_cost >= 0),
|
handling_cost DECIMAL(15, 2) DEFAULT NULL CHECK (handling_cost >= 0),
|
||||||
disposal_cost DECIMAL(15, 2) DEFAULT NULL CHECK (disposal_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_main_run BOOLEAN DEFAULT FALSE,
|
||||||
is_post_run BOOLEAN DEFAULT FALSE,
|
is_post_run BOOLEAN DEFAULT FALSE,
|
||||||
is_outdated 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),
|
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 (from_route_node_id) REFERENCES premise_route_node (id),
|
||||||
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),
|
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue