+
Pre carriage
{{ report.costs.pre_run.total.toFixed(2) }}
{{ (report.costs.pre_run.percentage * 100).toFixed(2) }}
-
+
Main run
{{ report.costs.main_run.total.toFixed(2) }}
{{ (report.costs.main_run.percentage * 100).toFixed(2) }}
-
-
Airfreight cost
-
{{ report.costs.air_freight_cost.total.toFixed(2) }}
-
{{ (report.costs.air_freight_cost.percentage * 100).toFixed(2) }}
-
-
-
+
Post carriage
{{ report.costs.post_run.total.toFixed(2) }}
{{ (report.costs.post_run.percentage * 100).toFixed(2) }}
+
+
Door 2 door costs
+
{{ report.costs.d2d.total.toFixed(2) }}
+
{{ (report.costs.d2d.percentage * 100).toFixed(2) }}
+
+
+
+
Transportation costs
+
{{ report.costs.transport.total.toFixed(2) }}
+
{{ (report.costs.transport.percentage * 100).toFixed(2) }}
+
+
+
+
Airfreight costs
+
{{ report.costs.air_freight_cost.total.toFixed(2) }}
+
{{ (report.costs.air_freight_cost.percentage * 100).toFixed(2) }}
+
+
+
-
Custom duty
+
Custom costs
{{ report.costs.custom.total.toFixed(2) }}
{{ (report.costs.custom.percentage * 100).toFixed(2) }}
@@ -102,22 +115,23 @@
-
Space cost
+
Disposal costs
+
{{ report.costs.disposal.total.toFixed(2) }}
+
{{ (report.costs.disposal.percentage * 100).toFixed(2) }}
+
+
+
+
Space costs
{{ report.costs.storage.total.toFixed(2) }}
{{ (report.costs.storage.percentage * 100).toFixed(2) }}
-
Capital cost
+
Capital costs
{{ report.costs.capital.total.toFixed(2) }}
{{ (report.costs.capital.percentage * 100).toFixed(2) }}
-
-
Disposal cost
-
{{ report.costs.disposal.total.toFixed(2) }}
-
{{ (report.costs.disposal.percentage * 100).toFixed(2) }}
-
diff --git a/src/frontend/src/pages/CalculationAssistant.vue b/src/frontend/src/pages/CalculationAssistant.vue
index 5002382..4434f7c 100644
--- a/src/frontend/src/pages/CalculationAssistant.vue
+++ b/src/frontend/src/pages/CalculationAssistant.vue
@@ -3,12 +3,14 @@
@@ -27,7 +29,8 @@
- No report selected
+ No report selected.
@@ -90,17 +90,16 @@ export default {
return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')}`
},
closeModal(data) {
- console.log("closeModal: ", data.action)
if (data.action === 'accept') {
- console.log("create report")
this.reportsStore.fetchReports(data.materialId, data.supplierIds);
}
this.showModal = false;
}
},
created() {
- if (!this.hasReport)
- this.showModal = true;
+
+ this.reportsStore.reset();
+
}
}
@@ -142,7 +141,7 @@ export default {
justify-content: center;
align-items: center;
font-size: 1.6rem;
- font-weight: 500;
+ font-weight: 400;
}
.report-spinner-container {
diff --git a/src/frontend/src/store/assistant.js b/src/frontend/src/store/assistant.js
index 57648cd..595a8b8 100644
--- a/src/frontend/src/store/assistant.js
+++ b/src/frontend/src/store/assistant.js
@@ -12,7 +12,6 @@ export const useAssistantStore = defineStore('assistant', {
error: null,
pagination: {},
query: {},
- premises: null
}),
getters: {
count: state => state.materials.length * state.suppliers.length,
@@ -28,7 +27,7 @@ export const useAssistantStore = defineStore('assistant', {
const materialIds = this.materials.map((material) => material.id);
const supplierIds = this.suppliers.filter(s => s.id.startsWith('s')).map((supplier) => supplier.origId);
const userSupplierIds = this.suppliers.filter(s => s.id.startsWith('u')).map((supplier) => supplier.origId);
- const jsonBody = JSON.stringify({ material: materialIds, supplier: supplierIds, user_supplier: userSupplierIds, createEmpty: this.createEmpty });
+ const jsonBody = JSON.stringify({ material: materialIds, supplier: supplierIds, user_supplier: userSupplierIds, from_scratch: this.createEmpty });
console.log(`Creation body: ${jsonBody}`);
@@ -80,8 +79,8 @@ export const useAssistantStore = defineStore('assistant', {
return;
}
- this.premises = data;
- console.log(this.premises);
+
+ return data.map(p => p.id);
},
diff --git a/src/frontend/src/store/reports.js b/src/frontend/src/store/reports.js
index 3da689c..d704319 100644
--- a/src/frontend/src/store/reports.js
+++ b/src/frontend/src/store/reports.js
@@ -24,6 +24,9 @@ export const useReportsStore = defineStore('reports', {
}
},
actions: {
+ reset() {
+ this.reports = [];
+ },
async fetchReports(materialId, supplierIds) {
if (supplierIds == null || materialId == null) return;
diff --git a/src/main/java/de/avatic/lcc/calculationmodel/DestinationInfo.java b/src/main/java/de/avatic/lcc/calculationmodel/DestinationInfo.java
new file mode 100644
index 0000000..c6c1958
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/calculationmodel/DestinationInfo.java
@@ -0,0 +1,10 @@
+package de.avatic.lcc.calculationmodel;
+
+import de.avatic.lcc.model.calculations.CalculationJobDestination;
+import de.avatic.lcc.model.premises.route.Destination;
+
+import java.util.List;
+
+public record DestinationInfo(Destination destination, CalculationJobDestination destinationCalculationJob,
+ List
sectionInfo) {
+}
diff --git a/src/main/java/de/avatic/lcc/controller/users/GroupController.java b/src/main/java/de/avatic/lcc/controller/users/GroupController.java
index 21c5b3f..f466b0b 100644
--- a/src/main/java/de/avatic/lcc/controller/users/GroupController.java
+++ b/src/main/java/de/avatic/lcc/controller/users/GroupController.java
@@ -31,7 +31,7 @@ public class GroupController {
* @param page The index of the page to retrieve. Defaults to 0 (first page).
* @return A ResponseEntity containing the list of groups and pagination headers.
*/
- @GetMapping("/")
+ @GetMapping({"/", ""})
public ResponseEntity> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit,
@RequestParam(defaultValue = "1") @Min(1) int page) {
@@ -44,16 +44,4 @@ public class GroupController {
.body(groups.toList());
}
- /**
- * Updates the details of an existing group.
- *
- * @param group The DTO containing the updated group information.
- * @return A ResponseEntity indicating the operation status.
- */
- @PutMapping("/")
- public ResponseEntity updateGroup(GroupDTO group) {
- groupService.updateGroup(group);
- return ResponseEntity.ok().build();
- }
-
}
diff --git a/src/main/java/de/avatic/lcc/controller/users/UserController.java b/src/main/java/de/avatic/lcc/controller/users/UserController.java
index 8db3b89..0c90235 100644
--- a/src/main/java/de/avatic/lcc/controller/users/UserController.java
+++ b/src/main/java/de/avatic/lcc/controller/users/UserController.java
@@ -34,10 +34,10 @@ public class UserController {
* @param page The page number of the users to retrieve, with a default value of 0.
* @return A ResponseEntity containing the list of users, along with pagination headers.
*/
- @GetMapping("/")
+ @GetMapping({"/", ""})
public ResponseEntity> listUsers(
@RequestParam(defaultValue = "20") @Min(1) int limit,
- @RequestParam(defaultValue = "0") @Min(1) int page) {
+ @RequestParam(defaultValue = "1") @Min(1) int page) {
SearchQueryResult users = userService.listUsers(page, limit);
@@ -56,7 +56,7 @@ public class UserController {
* @param user A UserDTO object containing the updated user details.
* @return A ResponseEntity indicating the operation was successful.
*/
- @PutMapping("/")
+ @PutMapping({"/", ""})
public ResponseEntity updateUser(UserDTO user) {
userService.updateUser(user);
return ResponseEntity.ok().build();
diff --git a/src/main/java/de/avatic/lcc/model/calculations/CalculationJobDestination.java b/src/main/java/de/avatic/lcc/model/calculations/CalculationJobDestination.java
index d7bd743..7ad02c8 100644
--- a/src/main/java/de/avatic/lcc/model/calculations/CalculationJobDestination.java
+++ b/src/main/java/de/avatic/lcc/model/calculations/CalculationJobDestination.java
@@ -52,6 +52,8 @@ public class CalculationJobDestination {
private Integer layerCount;
private Boolean transportWeightExceeded;
private BigDecimal annualTransportationCost;
+ private Boolean isD2D;
+ private BigDecimal rateD2D;
private BigDecimal containerUtilization;
@@ -373,4 +375,20 @@ public class CalculationJobDestination {
public BigDecimal getSafetyStockInDays() {
return safetyStockInDays;
}
+
+ public Boolean getD2D() {
+ return isD2D;
+ }
+
+ public void setD2D(Boolean d2D) {
+ isD2D = d2D;
+ }
+
+ public BigDecimal getRateD2D() {
+ return rateD2D;
+ }
+
+ public void setRateD2D(BigDecimal rateD2D) {
+ this.rateD2D = rateD2D;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/model/calculations/CalculationResult.java b/src/main/java/de/avatic/lcc/model/calculations/CalculationResult.java
index 6e641f9..bcaf108 100644
--- a/src/main/java/de/avatic/lcc/model/calculations/CalculationResult.java
+++ b/src/main/java/de/avatic/lcc/model/calculations/CalculationResult.java
@@ -1,7 +1,12 @@
package de.avatic.lcc.model.calculations;
+import de.avatic.lcc.calculationmodel.DestinationInfo;
+
+import java.util.List;
+
public class CalculationResult {
+ private List destinationInfos;
Integer jobId;
CalculationJobState state;
@@ -9,10 +14,11 @@ public class CalculationResult {
Throwable exception;
- public CalculationResult(Integer jobId) {
+ public CalculationResult(Integer jobId, List destinationInfos) {
this.jobId = jobId;
this.exception = null;
this.state = CalculationJobState.VALID;
+ this.destinationInfos = destinationInfos;
}
public CalculationResult(Integer jobId, Throwable e) {
@@ -44,4 +50,8 @@ public class CalculationResult {
public void setJobId(Integer jobId) {
this.jobId = jobId;
}
+
+ public List getDestinationInfos() {
+ return destinationInfos;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java
index fe24bcd..502a5e3 100644
--- a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java
@@ -393,6 +393,7 @@ public class NodeRepository {
AND cj.validity_period_id = ?
AND cj.property_set_id = ?
AND p.supplier_node_id IS NOT NULL
+ AND cj.job_state = 'VALID'
""";
diff --git a/src/main/java/de/avatic/lcc/repositories/calculation/CalculationJobDestinationRepository.java b/src/main/java/de/avatic/lcc/repositories/calculation/CalculationJobDestinationRepository.java
index 1440648..22f7860 100644
--- a/src/main/java/de/avatic/lcc/repositories/calculation/CalculationJobDestinationRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/calculation/CalculationJobDestinationRepository.java
@@ -45,8 +45,8 @@ public class CalculationJobDestinationRepository {
tariff_rate, annual_custom_cost, air_freight_share_max, air_freight_share, air_freight_volumetric_weight,
air_freight_weight, annual_air_freight_cost, container_type, hu_count, layer_structure,
layer_count, transport_weight_exceeded, annual_transportation_cost, container_utilization,
- transit_time_in_days, safety_stock_in_days, material_cost, fca_cost
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ transit_time_in_days, safety_stock_in_days, material_cost, fca_cost, is_d2d, rate_d2d
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
KeyHolder keyHolder = new GeneratedKeyHolder();
@@ -109,6 +109,9 @@ public class CalculationJobDestinationRepository {
ps.setObject(paramIndex++, destination.getMaterialCost());
ps.setObject(paramIndex++, destination.getFcaCost());
+ ps.setObject(paramIndex++, destination.getD2D());
+ ps.setObject(paramIndex++, destination.getRateD2D());
+
return ps;
}, keyHolder);
diff --git a/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java b/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java
index 28ab8d0..16ff543 100644
--- a/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/users/UserNodeRepository.java
@@ -92,6 +92,7 @@ public class UserNodeRepository {
AND cj.validity_period_id = ?
AND cj.property_set_id = ?
AND p.user_supplier_node_id IS NOT NULL
+ AND cj.job_state = 'VALID'
""";
return jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, tuple.periodId(), tuple.propertySetId());
diff --git a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
index 7491b4c..344cfb5 100644
--- a/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
+++ b/src/main/java/de/avatic/lcc/repositories/users/UserRepository.java
@@ -96,7 +96,6 @@ public class UserRepository {
updateUserGroupMappings(userId, groupIds);
-
}
private List findGroupIds(List groups) {
diff --git a/src/main/java/de/avatic/lcc/service/CustomApiService.java b/src/main/java/de/avatic/lcc/service/CustomApiService.java
index 0f5eb70..a9abd22 100644
--- a/src/main/java/de/avatic/lcc/service/CustomApiService.java
+++ b/src/main/java/de/avatic/lcc/service/CustomApiService.java
@@ -7,7 +7,7 @@ import java.math.BigDecimal;
@Service
public class CustomApiService {
public BigDecimal getTariffRate(String hsCode, Integer countryId) {
- return BigDecimal.valueOf(0.3);
+ return BigDecimal.valueOf(0.03);
//TODO implement me
}
diff --git a/src/main/java/de/avatic/lcc/service/access/PremisesService.java b/src/main/java/de/avatic/lcc/service/access/PremisesService.java
index 2bf6d3b..b4e6069 100644
--- a/src/main/java/de/avatic/lcc/service/access/PremisesService.java
+++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java
@@ -10,19 +10,23 @@ import de.avatic.lcc.model.calculations.CalculationJob;
import de.avatic.lcc.model.calculations.CalculationJobState;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.PremiseState;
+import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRepository;
+import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.premise.PremiseRepository;
-import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.properties.PropertySetRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.calculation.execution.CalculationExecutionService;
import de.avatic.lcc.service.calculation.execution.CalculationStatusService;
+import de.avatic.lcc.service.precalculation.PostCalculationCheckService;
import de.avatic.lcc.service.precalculation.PreCalculationCheckService;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import de.avatic.lcc.util.exception.base.InternalErrorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -36,30 +40,39 @@ import java.util.concurrent.ExecutionException;
@Service
public class PremisesService {
+
+ private static final Logger log = LoggerFactory.getLogger(PremisesService.class);
+
private final PremiseRepository premiseRepository;
private final PremiseTransformer premiseTransformer;
private final DimensionTransformer dimensionTransformer;
private final DestinationService destinationService;
private final CalculationJobRepository calculationJobRepository;
- private final PropertyRepository propertyRepository;
+
private final PropertySetRepository propertySetRepository;
private final ValidityPeriodRepository validityPeriodRepository;
private final CalculationStatusService calculationStatusService;
private final CalculationExecutionService calculationExecutionService;
private final PreCalculationCheckService preCalculationCheckService;
+ private final CalculationJobDestinationRepository calculationJobDestinationRepository;
+ private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository;
+ private final PostCalculationCheckService postCalculationCheckService;
- public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService, CalculationExecutionService calculationExecutionService, PreCalculationCheckService preCalculationCheckService) {
+ public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, CalculationJobRepository calculationJobRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, CalculationStatusService calculationStatusService, CalculationExecutionService calculationExecutionService, PreCalculationCheckService preCalculationCheckService, CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PostCalculationCheckService postCalculationCheckService) {
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
this.dimensionTransformer = dimensionTransformer;
this.destinationService = destinationService;
this.calculationJobRepository = calculationJobRepository;
- this.propertyRepository = propertyRepository;
+
this.propertySetRepository = propertySetRepository;
this.validityPeriodRepository = validityPeriodRepository;
this.calculationStatusService = calculationStatusService;
this.calculationExecutionService = calculationExecutionService;
this.preCalculationCheckService = preCalculationCheckService;
+ this.calculationJobDestinationRepository = calculationJobDestinationRepository;
+ this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository;
+ this.postCalculationCheckService = postCalculationCheckService;
}
@Transactional(readOnly = true)
@@ -122,12 +135,32 @@ public class PremisesService {
for (var future : futures) {
var jobResult = future.get();
if (jobResult.getState().equals(CalculationJobState.EXCEPTION)) {
- throw new InternalErrorException("Calculation failed: " + jobResult.getException().getMessage(), new Exception(jobResult.getException()));
+ calculationJobRepository.setStateTo(jobResult.getJobId(), CalculationJobState.EXCEPTION);
+ throw new InternalErrorException("Execution of calculation was not successful. Please contact Administrator.", new Exception(jobResult.getException()));
+ } else {
+
+ postCalculationCheckService.doPostcheck(jobResult);
+
+ for (var destinationInfo : jobResult.getDestinationInfos()) {
+ destinationInfo.destinationCalculationJob().setCalculationJobId(jobResult.getJobId());
+ var destinationId = calculationJobDestinationRepository.insert(destinationInfo.destinationCalculationJob());
+
+ for (var sectionInfo : destinationInfo.sectionInfo()) {
+ var section = sectionInfo.result();
+ section.setCalculationJobDestinationId(destinationId);
+ calculationJobRouteSectionRepository.insert(section);
+ }
+ }
+
+ calculationJobRepository.setStateTo(jobResult.getJobId(), CalculationJobState.VALID);
+ log.info("Calculation job {} finished", jobResult.getJobId());
}
}
- ;
+
+
+
} catch (CompletionException | InterruptedException | ExecutionException e) {
- throw new InternalErrorException("Calculation failed", e);
+ throw new InternalErrorException("Calculation execution failed.", e);
}
}
@@ -181,8 +214,13 @@ public class PremisesService {
// only delete drafts.
var toBeDeleted = premiseRepository.getPremisesById(premiseIds).stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).map(Premise::getId).toList();
- destinationService.deleteAllDestinationsByPremiseId(toBeDeleted, false);
- premiseRepository.deletePremisesById(toBeDeleted);
+
+ if(!toBeDeleted.isEmpty())
+ {
+ destinationService.deleteAllDestinationsByPremiseId(toBeDeleted, false);
+ premiseRepository.deletePremisesById(toBeDeleted);
+ }
+
}
public void archive(List premiseIds) {
diff --git a/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java
index d54371e..e3e9f50 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/ChangeSupplierService.java
@@ -125,7 +125,8 @@ public class ChangeSupplierService {
//delete all conflicting premises:
- premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
+ if(!premisesToBeDeleted.isEmpty())
+ premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
diff --git a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
index b88e30f..c34fde4 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java
@@ -99,12 +99,15 @@ public class RoutingService {
*/
connectDestinationChainDirectly(container, destinationChains);
+ /* remove duplicates. */
+ removeDuplicateRoutes(container.getRoutes());
/*
* finally find and mark the fastest and the cheapest route.
*/
findAndMarkCheapestAndFastest(container);
- container.getRoutes().stream().map(TemporaryRouteObject::getFullDebugText).forEach(s -> log.info("Found route: {}",s));
+
+ container.getRoutes().stream().map(TemporaryRouteObject::getFullDebugText).forEach(s -> log.info("Found route: {}", s));
/*
@@ -143,7 +146,7 @@ public class RoutingService {
}
private String generateShortName(String name) {
- if(name.length() < 13){
+ if (name.length() < 13) {
return name;
}
@@ -375,7 +378,7 @@ public class RoutingService {
private List> getChain(Integer id, TemporaryContainer container) {
var chains = container.getChain(id);
- if(chains == null) {
+ if (chains == null) {
chains = chainResolver.buildChains(id);
container.setChain(id, chains);
}
@@ -475,33 +478,43 @@ public class RoutingService {
for (var route : routes) {
var sections = route.getSections();
+ boolean hasNearByRouting = sections.getLast().getType().equals(TemporaryRateObject.TemporaryRateObjectType.NEAR_BY);
- if(sections.getLast().getType().equals(TemporaryRateObject.TemporaryRateObjectType.NEAR_BY)) {
- for (var other : routes) {
- //skip same instance.
- if(other.equals(route)) continue;
+ for (var other : routes) {
- var otherIter = other.getSections().iterator();
- var iter = sections.iterator();
- boolean same = true;
+ //skip same instance.
+ if (other.equals(route)) continue;
+ if (toRemove.contains(other)) continue;
- while (otherIter.hasNext() && iter.hasNext()) {
- var otherSection = otherIter.next();
- var section = iter.next();
+ var otherIter = other.getSections().iterator();
+ var iter = sections.iterator();
+ boolean same = true;
- if (!otherSection.hasSameNodes(section)) {
- same = false;
- break;
- }
+ TemporaryRateObject lastSection = null;
+ while (otherIter.hasNext() && iter.hasNext()) {
+ var otherSection = otherIter.next();
+ var section = iter.next();
+
+ same = otherSection.hasSameNodes(section);
+
+ if(!same && hasNearByRouting && iter.hasNext()) {
+ lastSection = section;
+ section = iter.next();
+ same = section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.NEAR_BY) && otherSection.hasSameNodes(section, lastSection);
}
- if (same && !otherIter.hasNext() && !iter.hasNext()) {
- toRemove.add(route);
+ if(!same) {
break;
}
}
+
+ if (same && !otherIter.hasNext() && !iter.hasNext()) {
+ toRemove.add(route);
+ break;
+ }
}
+
}
routes.removeAll(toRemove);
@@ -691,6 +704,7 @@ public class RoutingService {
*/
private final Node source;
private final Node destination;
+ private final Map>> chains = new HashMap<>();
/*
* mainRuns (maps start node to rate) retrieved from the database.
*/
@@ -699,11 +713,8 @@ public class RoutingService {
* postRuns (maps main_run container rate id to post_run container rate id) retrieved from the database.
*/
private Map> postRuns;
-
private MatrixRate sourceMatrixRate;
- private final Map>> chains = new HashMap<>();
-
public TemporaryContainer(Node source, Node destination) {
this.source = source;
this.destination = destination;
@@ -882,7 +893,7 @@ public class RoutingService {
} 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().charAt(0) + (isFastest ? "\uD83D\uDE80" : "") + (isCheapest ? "\uD83D\uDCB2" : ""), sb);
}
public void setQuality(ChainQuality quality) {
@@ -1011,6 +1022,10 @@ public class RoutingService {
return fromNode.getId().equals(section.getFromNode().getId()) && toNode.getId().equals(section.getToNode().getId());
}
+ public boolean hasSameNodes(TemporaryRateObject firstSection, TemporaryRateObject secondSection) {
+ return fromNode.getId().equals(firstSection.getFromNode().getId()) && toNode.getId().equals(secondSection.getToNode().getId());
+ }
+
private enum TemporaryRateObjectType {
NEAR_BY("\uD83D\uDCCD"), MATRIX("\uD83D\uDCCA"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationExecutionService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationExecutionService.java
index 46c0739..de9a4fb 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationExecutionService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/execution/CalculationExecutionService.java
@@ -1,7 +1,6 @@
package de.avatic.lcc.service.calculation.execution;
import de.avatic.lcc.calculationmodel.*;
-import de.avatic.lcc.dto.calculation.CalculationStatus;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.model.calculations.*;
@@ -88,16 +87,13 @@ public class CalculationExecutionService {
@Async("taskExecutor")
public CompletableFuture launchJobCalculation(Integer calculationId) {
try {
- calculateJob(calculationId);
+ return CompletableFuture.completedFuture(new CalculationResult(calculationId, calculateJob(calculationId)));
} catch (Throwable e) {
- //TODO put error in database.
- calculationJobRepository.setStateTo(calculationId, CalculationJobState.EXCEPTION);
return CompletableFuture.completedFuture(new CalculationResult(calculationId, e));
}
- return CompletableFuture.completedFuture(new CalculationResult(calculationId));
}
- public void calculateJob(Integer calculationId) {
+ public List calculateJob(Integer calculationId) {
CalculationJob calculation = calculationJobRepository.getCalculationJob(calculationId).orElseThrow();
@@ -117,24 +113,10 @@ public class CalculationExecutionService {
}
List destinations = destinationRepository.getByPremiseId(premise.getId());
- var destinationInfos = destinations.stream().map(destination -> doDestinationCalculation(destination, premise, materialCost, fcaFee)).toList();
-
- for (var destinationInfo : destinationInfos) {
- destinationInfo.destinationCalculationJob.setCalculationJobId(calculation.getId());
- var destinationId = calculationJobDestinationRepository.insert(destinationInfo.destinationCalculationJob);
-
- for (var sectionInfo : destinationInfo.sectionInfo) {
- var section = sectionInfo.result();
- section.setCalculationJobDestinationId(destinationId);
- calculationJobRouteSectionRepository.insert(section);
- }
- }
-
- calculationJobRepository.setStateTo(calculationId, CalculationJobState.VALID);
- log.info("Calculation job {} finished", calculationId);
+ return destinations.stream().map(destination -> doDestinationCalculation(destination, premise, materialCost, fcaFee)).toList();
}
-
+ throw new IllegalStateException("Calculation job is not scheduled");
}
private DestinationInfo doDestinationCalculation(Destination destination, Premise premise, BigDecimal materialCost, BigDecimal fcaFee) {
@@ -161,8 +143,22 @@ public class CalculationExecutionService {
usedContainerType = bestContainerTypeResult.containerType;
hasMainRun = sections.stream().anyMatch(s -> s.section().getMainRun());
leadTime = BigDecimal.valueOf(sections.stream().mapToInt(s -> s.result().getTransitTime()).sum());
+
+ /* inner eu routing */
+ if(!hasMainRun) {
+ sections.forEach(s -> {
+ s.result().setMainRun(false);
+ s.result().setPreRun(false);
+ s.result().setPostRun(false);
+ });
+ }
}
+ destinationCalculationJob.setD2D(destination.getD2d());
+
+ if(destination.getD2d())
+ destinationCalculationJob.setRateD2D(destination.getRateD2d());
+
customCost = customCostCalculationService.doCalculation(premise, destination, sections);
handlingCost = handlingCostCalculationService.doCalculation(premise, destination, hasMainRun);
inventoryCost = inventoryCostCalculationService.doCalculation(premise, destination, leadTime);
@@ -226,6 +222,7 @@ public class CalculationExecutionService {
destinationCalculationJob.setPremiseDestinationId(destination.getId());
+
return new DestinationInfo(destination, destinationCalculationJob, sections);
}
@@ -264,7 +261,4 @@ public class CalculationExecutionService {
private record BestContainerTypeResult(ContainerType containerType, List sections) {
}
- private record DestinationInfo(Destination destination, CalculationJobDestination destinationCalculationJob,
- List sectionInfo) {
- }
}
diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ContainerCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ContainerCalculationService.java
index 309fcb9..d4e3c3f 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ContainerCalculationService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ContainerCalculationService.java
@@ -10,6 +10,8 @@ import de.avatic.lcc.repositories.properties.PropertyRepository;
import org.jetbrains.annotations.Debug.Renderer;
import org.springframework.stereotype.Service;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
@@ -49,9 +51,9 @@ public class ContainerCalculationService {
*/
public ContainerCalculationResult doCalculation(PackagingDimension hu, ContainerType containerType) {
- var weightInKg = WeightUnit.KG.convertFromG(hu.getWeight()).intValue();
- var maxContainerLoad = getMaxContainerLoad(containerType);
- var maxUnitByWeight = maxContainerLoad / weightInKg;
+ var weightInKg = BigDecimal.valueOf(WeightUnit.KG.convertFromG(hu.getWeight()));
+ var maxContainerLoad = BigDecimal.valueOf(getMaxContainerLoad(containerType));
+ var maxUnitByWeight = maxContainerLoad.divide(weightInKg, 0, RoundingMode.HALF_UP).intValueExact();
var dimensions = hu.withTolerance(DIMENSION_TOLERANCE);
@@ -61,12 +63,12 @@ public class ContainerCalculationService {
int layers = getLayerCount(dimensions, containerType);
if(PalletType.EURO_PALLET.fitsOn(hu) && bestSolution.getTotal() < containerType.getPalletCount(PalletType.EURO_PALLET)) {
- return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.EURO_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.EURO_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad);
+ return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.EURO_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.EURO_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad.intValueExact());
} else if(PalletType.INDUSTRIAL_PALLET.fitsOn(hu) && bestSolution.getTotal() < containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET)) {
- return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad);
+ return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad.intValueExact());
}
- return createContainerCalculationResult(bestSolution, layers, maxUnitByWeight, containerType, dimensions, maxContainerLoad);
+ return createContainerCalculationResult(bestSolution, layers, maxUnitByWeight, containerType, dimensions, maxContainerLoad.intValueExact());
}
diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java
index eba674d..bef9df3 100644
--- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java
+++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java
@@ -37,48 +37,71 @@ public class HandlingCostCalculationService {
}
private HandlingResult getSLCCost(Destination destination, PackagingDimension hu, LoadCarrierType loadCarrierType, boolean addRepackingCosts) {
- int huAnnualAmount = destination.getAnnualAmount() / hu.getContentUnitCount();
- double handling = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_HANDLING).orElseThrow().getCurrentValue());
- double release = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_RELEASE).orElseThrow().getCurrentValue());
- double dispatch = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_DISPATCH).orElseThrow().getCurrentValue());
- double wageFactor = Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue());
- double booking = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue());
+
+ var destinationHandling = destination.getHandlingCost();
+ var destinationDisposal = destination.getDisposalCost();
+ var destinationRepacking = destination.getRepackingCost();
+
+
+// BigDecimal huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),4,RoundingMode.UP);
+// double handling = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_HANDLING).orElseThrow().getCurrentValue());
+// double release = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_RELEASE).orElseThrow().getCurrentValue());
+// double dispatch = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_DISPATCH).orElseThrow().getCurrentValue());
+// double wageFactor = Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue());
+// double booking = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue());
+
+ BigDecimal huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),4, RoundingMode.UP );
+ BigDecimal handling = destinationHandling != null ? destinationHandling : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING).orElseThrow().getCurrentValue()));
+ BigDecimal release = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE).orElseThrow().getCurrentValue()));
+ BigDecimal dispatch = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH).orElseThrow().getCurrentValue()));
+ BigDecimal disposal = destinationDisposal != null ? destinationDisposal : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL).orElseThrow().getCurrentValue()));
+
+ BigDecimal wageFactor = BigDecimal.valueOf(Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue()));
+ BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue()));
return new HandlingResult(LoadCarrierType.SLC,
- BigDecimal.valueOf(getRepackingCost(hu, loadCarrierType, addRepackingCosts) * huAnnualAmount),
- BigDecimal.valueOf(handling*huAnnualAmount),
- BigDecimal.ZERO,
- BigDecimal.valueOf(huAnnualAmount * (handling + booking + release + dispatch + getRepackingCost(hu, loadCarrierType, addRepackingCosts)) * wageFactor));
+ getRepackingCost(hu, loadCarrierType, addRepackingCosts, destinationRepacking).multiply(huAnnualAmount),
+ handling.multiply(huAnnualAmount),
+ destinationDisposal == null ? BigDecimal.ZERO : (disposal.multiply(huAnnualAmount)), //TODO: disposal SLC, ignore?
+ huAnnualAmount.multiply((handling.add(booking).add(release).add(dispatch).add(getRepackingCost(hu, loadCarrierType, addRepackingCosts, destinationRepacking)))).multiply(wageFactor));
}
- private double getRepackingCost(PackagingDimension hu, LoadCarrierType type, boolean addRepackingCosts) {
+ private BigDecimal getRepackingCost(PackagingDimension hu, LoadCarrierType type, boolean addRepackingCosts, BigDecimal userInput) {
if (!addRepackingCosts)
- return 0;
+ return BigDecimal.ZERO;
+
+ if(userInput != null)
+ return userInput;
return switch (type) {
case SLC ->
- Double.parseDouble((hu.getWeight() < 15_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_REPACK_S).orElseThrow().getCurrentValue() : propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_REPACK_M).orElseThrow().getCurrentValue());
+ BigDecimal.valueOf(Double.parseDouble((hu.getWeight() < 15_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_REPACK_S).orElseThrow().getCurrentValue() : propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.KLT_REPACK_M).orElseThrow().getCurrentValue()));
case LLC ->
- Double.parseDouble(((hu.getWeight() < 15_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_S) : (hu.getWeight() < 2_000_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_M) : propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_L)).orElseThrow().getCurrentValue());
+ BigDecimal.valueOf(Double.parseDouble(((hu.getWeight() < 15_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_S) : (hu.getWeight() < 2_000_000) ? propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_M) : propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_REPACK_L)).orElseThrow().getCurrentValue()));
};
}
private HandlingResult getLLCCost(Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingCosts) {
- double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),2, RoundingMode.UP ).doubleValue();
- double handling = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING).orElseThrow().getCurrentValue());
- double release = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE).orElseThrow().getCurrentValue());
- double dispatch = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH).orElseThrow().getCurrentValue());
- double disposal = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL).orElseThrow().getCurrentValue());
- double wageFactor = Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue());
- double booking = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue());
+ var destinationHandling = destination.getHandlingCost();
+ var destinationDisposal = destination.getDisposalCost();
+ var destinationRepacking = destination.getRepackingCost();
- var annualRepacking = BigDecimal.valueOf(getRepackingCost(hu, type, addRepackingCosts) * wageFactor * huAnnualAmount);
- var annualHandling = BigDecimal.valueOf((handling+dispatch+release)*wageFactor*huAnnualAmount + (booking * shippingFrequencyCalculationService.doCalculation(huAnnualAmount)));
- var annualDisposal = BigDecimal.valueOf(disposal * huAnnualAmount);
+ BigDecimal huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(hu.getContentUnitCount()),4, RoundingMode.UP );
+ BigDecimal handling = destinationHandling != null ? destinationHandling : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING).orElseThrow().getCurrentValue()));
+ BigDecimal release = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE).orElseThrow().getCurrentValue()));
+ BigDecimal dispatch = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH).orElseThrow().getCurrentValue()));
+ BigDecimal disposal = destinationDisposal != null ? destinationDisposal : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL).orElseThrow().getCurrentValue()));
+
+ BigDecimal wageFactor = BigDecimal.valueOf(Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue()));
+ BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue()));
+
+ var annualRepacking = getRepackingCost(hu, type, addRepackingCosts, destinationRepacking).multiply(wageFactor).multiply( huAnnualAmount);
+ var annualHandling = ((handling.add(dispatch).add(release)).multiply(wageFactor).multiply(huAnnualAmount)).add(booking.multiply(BigDecimal.valueOf(shippingFrequencyCalculationService.doCalculation(huAnnualAmount.doubleValue()))));
+ var annualDisposal = (disposal.multiply(huAnnualAmount));
return new HandlingResult(LoadCarrierType.LLC, annualRepacking, annualHandling, annualDisposal, annualRepacking.add(annualHandling).add(annualDisposal));
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 68e34f9..199d7d9 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
@@ -6,6 +6,7 @@ import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.model.nodes.Location;
+import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.model.premises.route.RouteNode;
@@ -15,6 +16,7 @@ import de.avatic.lcc.model.rates.ContainerRate;
import de.avatic.lcc.model.rates.MatrixRate;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
+import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
@@ -35,14 +37,16 @@ public class RouteSectionCostCalculationService {
private final DistanceService distanceService;
private final PropertyRepository propertyRepository;
private final ChangeRiskFactorCalculationService changeRiskFactorCalculationService;
+ private final NodeRepository nodeRepository;
- public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService) {
+ public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService, NodeRepository nodeRepository) {
this.containerRateRepository = containerRateRepository;
this.matrixRateRepository = matrixRateRepository;
this.routeNodeRepository = routeNodeRepository;
this.distanceService = distanceService;
this.propertyRepository = propertyRepository;
this.changeRiskFactorCalculationService = changeRiskFactorCalculationService;
+ this.nodeRepository = nodeRepository;
}
public CalculationJobRouteSection doD2dCalculation(Premise premise, Destination destination, ContainerCalculationResult containerCalculation) {
@@ -60,9 +64,10 @@ public class RouteSectionCostCalculationService {
result.setUnmixedPrice(premise.getHuMixable());
// Get nodes and distance
- RouteNode fromNode = routeNodeRepository.getById(premise.getSupplierNodeId()).orElseThrow();
- RouteNode toNode = routeNodeRepository.getById(destination.getDestinationNodeId()).orElseThrow();
- double distance = getDistance(fromNode, toNode);
+ Node fromNode = nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow();
+ Node toNode = nodeRepository.getById(destination.getDestinationNodeId()).orElseThrow();
+
+ double distance = distanceService.getDistance(fromNode, toNode, true);
result.setDistance(BigDecimal.valueOf(distance));
// Get rate and transit time
@@ -267,6 +272,8 @@ public class RouteSectionCostCalculationService {
}
+
+
/**
* Simple data class to hold volume and weight price results
*/
diff --git a/src/main/java/de/avatic/lcc/service/precalculation/PostCalculationCheckService.java b/src/main/java/de/avatic/lcc/service/precalculation/PostCalculationCheckService.java
new file mode 100644
index 0000000..a32633f
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/precalculation/PostCalculationCheckService.java
@@ -0,0 +1,13 @@
+package de.avatic.lcc.service.precalculation;
+
+import de.avatic.lcc.model.calculations.CalculationResult;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PostCalculationCheckService {
+
+ public void doPostcheck(CalculationResult calculationResult) {
+ //TODO check if all destinations are valid
+ }
+
+}
diff --git a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java
index 8ecaf1a..fa7fc83 100644
--- a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java
+++ b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java
@@ -38,12 +38,19 @@ public class PreCalculationCheckService {
materialCheck(premise);
+
packagingCheck(premise);
priceCheck(premise);
+ var destinations = destinationRepository.getByPremiseId(premiseId);
- for (Destination destination : destinationRepository.getByPremiseId(premiseId)) {
+ if(destinations == null || destinations.isEmpty()) {
+ throw new PremiseValidationError("No destination for calculation. At least one destination is required.");
+ }
+
+
+ for (Destination destination : destinations ) {
var node = nodeRepository.getByDestinationId(destination.getId()).orElseThrow();
@@ -53,12 +60,20 @@ public class PreCalculationCheckService {
- if(routes.isEmpty())
+ if(routes.isEmpty() && destination.getD2d() == false)
throw new PremiseValidationError("No route found for destination " + node.getName() + ". Cannot use standard routing.");
- if(routes.stream().noneMatch(Route::getSelected))
+ if(routes.stream().noneMatch(Route::getSelected) && destination.getD2d() == false)
throw new PremiseValidationError("No route selected for destination " + node.getName());
+ if(destination.getD2d() && (destination.getRateD2d() == null || destination.getRateD2d().compareTo(BigDecimal.ZERO) == 0)) {
+ throw new PremiseValidationError("Door-2-door rate not set or set to zero.");
+ }
+
+ if(destination.getD2d() && (destination.getLeadTimeD2d() == null || destination.getLeadTimeD2d() == 0)) {
+ throw new PremiseValidationError("Door-2-door rate not set or set to zero.");
+ }
+
}
}
@@ -72,7 +87,7 @@ public class PreCalculationCheckService {
throw new PremiseValidationError("In destination " + node.getName() +": D2D not set.");
if (destination.getD2d() == true) {
- if (destination.getRateD2d() == null || destination.getRateD2d().equals(BigDecimal.ZERO)) {
+ if (destination.getRateD2d() == null || destination.getRateD2d().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("In destination " + node.getName() + ": D2D Rate not set or set to zero.");
}
@@ -89,15 +104,15 @@ public class PreCalculationCheckService {
throw new PremiseValidationError("In destination " + node.getName() + ": destination geo location not set. Please contact administrator");
}
- if (destination.getDisposalCost() != null && destination.getDisposalCost().equals(BigDecimal.ZERO)) {
+ if (destination.getDisposalCost() != null && destination.getDisposalCost().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("In destination " + node.getName() + ": disposal cost set to zero.");
}
- if (destination.getHandlingCost() != null && destination.getHandlingCost().equals(BigDecimal.ZERO)) {
+ if (destination.getHandlingCost() != null && destination.getHandlingCost().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("In destination " + node.getName() + ": handling cost set to zero.");
}
- if (destination.getRepackingCost() != null && destination.getRepackingCost().equals(BigDecimal.ZERO)) {
+ if (destination.getRepackingCost() != null && destination.getRepackingCost().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("In destination " + node.getName() + ": repackaging cost set to zero.");
}
@@ -112,11 +127,11 @@ public class PreCalculationCheckService {
private void priceCheck(Premise premise) {
- if (premise.getMaterialCost() == null || premise.getMaterialCost().equals(BigDecimal.ZERO)) {
+ if (premise.getMaterialCost() == null || premise.getMaterialCost().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("MEK_A is not set or set to zero");
}
- if (premise.getOverseaShare() == null || premise.getOverseaShare().equals(BigDecimal.ZERO)) {
+ if (premise.getOverseaShare() == null || premise.getOverseaShare().compareTo(BigDecimal.ZERO) == 0) {
throw new PremiseValidationError("Oversea share is not set or set to zero");
}
@@ -156,6 +171,10 @@ public class PreCalculationCheckService {
throw new PremiseValidationError("Packaging weight is not set or set to zero");
}
+ if (premise.getHuUnitCount() == null || premise.getHuUnitCount() == 0) {
+ throw new PremiseValidationError("Packaging unit count is not set or set to zero");
+ }
+
if (premise.getHuDisplayedWeightUnit() == null) {
throw new PremiseValidationError("Packaging weight unit is not set");
}
diff --git a/src/main/java/de/avatic/lcc/service/transformer/report/ReportTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/report/ReportTransformer.java
index 69cae77..57f6f90 100644
--- a/src/main/java/de/avatic/lcc/service/transformer/report/ReportTransformer.java
+++ b/src/main/java/de/avatic/lcc/service/transformer/report/ReportTransformer.java
@@ -24,6 +24,7 @@ import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.transformer.generic.MaterialTransformer;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
+import de.avatic.lcc.util.exception.base.InternalErrorException;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@@ -92,12 +93,18 @@ public class ReportTransformer {
if (premise.getUserSupplierNodeId() != null)
sourceNode = userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow();
- reportDTO.setCost(getCostMap(job, destinations, weightedTotalCost, includeAirfreight));
- reportDTO.setRisk(getRisk(job, destinations, weightedTotalCost, includeAirfreight));
- Node finalSourceNode = sourceNode;
- reportDTO.setDestination(destinations.stream().map(d -> getDestinationDTO(d, sections.get(d.getId()), premise, finalSourceNode, includeAirfreight)).toList());
- if (!reportDTO.getDestinations().isEmpty()) {
+ if (!destinations.isEmpty()) {
+ reportDTO.setCost(getCostMap(job, destinations, weightedTotalCost, includeAirfreight));
+ reportDTO.setRisk(getRisk(job, destinations, weightedTotalCost, includeAirfreight));
+ Node finalSourceNode = sourceNode;
+ reportDTO.setDestination(destinations.stream().map(d -> getDestinationDTO(d, sections.get(d.getId()), premise, finalSourceNode, includeAirfreight)).toList());
+ } else {
+ throw new InternalErrorException("Malformed calculation. No destinations found.");
+ }
+
+ if (reportDTO.getDestinations() != null && !reportDTO.getDestinations().isEmpty()) {
+
var source = reportDTO.getDestinations().getFirst().getSections().stream().map(ReportSectionDTO::getFromNode).filter(n -> n.getTypes().contains(NodeType.SOURCE)).findFirst().orElseThrow();
reportDTO.setSupplier(source);
}
@@ -112,10 +119,10 @@ public class ReportTransformer {
var startDate = propertySet.getStartDate().isBefore(validityPeriod.getStartDate()) ? validityPeriod.getStartDate() : propertySet.getStartDate();
- if(propertySet.getEndDate() == null)
+ if (propertySet.getEndDate() == null)
return new TimePeriod(startDate, validityPeriod.getEndDate());
- if(validityPeriod.getEndDate() == null)
+ if (validityPeriod.getEndDate() == null)
return new TimePeriod(startDate, propertySet.getEndDate());
var endDate = propertySet.getEndDate().isAfter(validityPeriod.getEndDate()) ? validityPeriod.getEndDate() : propertySet.getEndDate();
@@ -130,6 +137,8 @@ public class ReportTransformer {
BigDecimal totalPreRunCost = BigDecimal.ZERO;
BigDecimal totalMainRunCost = BigDecimal.ZERO;
BigDecimal totalPostRunCost = BigDecimal.ZERO;
+ BigDecimal totalD2D = BigDecimal.ZERO;
+
BigDecimal totalCost = BigDecimal.ZERO;
@@ -138,16 +147,19 @@ public class ReportTransformer {
if (section.getPreRun())
totalPreRunCost = totalPreRunCost.add(section.getAnnualCost());
- if (section.getMainRun())
+ else if (section.getMainRun())
totalMainRunCost = totalMainRunCost.add(section.getAnnualCost());
- if (section.getPostRun())
+ else if (section.getPostRun())
totalPostRunCost = totalPostRunCost.add(section.getAnnualCost());
+ else if (section.getRateType().equals(RateType.D2D))
+ totalD2D = totalD2D.add(section.getAnnualCost());
+
totalCost = totalCost.add(section.getAnnualCost());
}
}
- return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalCost);
+ return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalD2D, totalCost);
}
private ReportDestinationDTO getDestinationDTO(CalculationJobDestination destination, List sections, Premise premise, Node sourceNode, boolean includeAirfreight) {
@@ -245,7 +257,7 @@ public class ReportTransformer {
var worstValue = destination.stream().map(CalculationJobDestination::getTotalRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
var bestValue = destination.stream().map(CalculationJobDestination::getTotalChanceCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var totalValue = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var totalValue = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
//var totalValue = weightedTotalCost.totalCost.divide(annualAmount, 2, RoundingMode.HALF_UP);
totalValue = totalValue.add(airfreightValue.multiply(BigDecimal.valueOf(includeAirfreight ? 1 : 0)));
@@ -277,19 +289,25 @@ public class ReportTransformer {
var materialValue = destination.stream().map(CalculationJobDestination::getMaterialCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destination.size()), 4, RoundingMode.HALF_UP);
var fcaFeesValues = destination.stream().map(CalculationJobDestination::getFcaCost).reduce(BigDecimal.ZERO, BigDecimal::add);
- var repackingValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualRepackingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var handlingValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualHandlingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var storageValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualStorageCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var capitalValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCapitalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var disposalValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualDisposalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
- var customValues = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCustomCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var repackingValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualRepackingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var handlingValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualHandlingCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var storageValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualStorageCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var capitalValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCapitalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var disposalValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualDisposalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var customValues = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getAnnualCustomCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+
+ var noTransportSplit = weightedTotalCost.totalPreRunCost.compareTo(BigDecimal.ZERO) == 0 && weightedTotalCost.totalMainRunCost.compareTo(BigDecimal.ZERO) == 0 && weightedTotalCost.totalPostRunCost.compareTo(BigDecimal.ZERO) == 0;
+
var preRunValues = weightedTotalCost.totalPreRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
var mainRunValues = weightedTotalCost.totalMainRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
var postRunValues = weightedTotalCost.totalPostRunCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var d2dValues = weightedTotalCost.totalD2D.divide(annualAmount, 4, RoundingMode.HALF_UP);
+ var transportValues = weightedTotalCost.totalCost.divide(annualAmount, 4, RoundingMode.HALF_UP);
- var totalValue = annualAmount.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
+
+ var totalValue = annualAmount.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : destination.stream().map(CalculationJobDestination::getTotalCost).reduce(BigDecimal.ZERO, BigDecimal::add).divide(annualAmount, 4, RoundingMode.HALF_UP);
if (includeAirfreight) {
totalValue = totalValue.add(airfreightValue);
@@ -307,20 +325,35 @@ public class ReportTransformer {
cost.put("air_freight_cost", airfreight);
}
- ReportEntryDTO preRun = new ReportEntryDTO();
- preRun.setTotal(preRunValues);
- preRun.setPercentage(preRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
- cost.put("pre_run", preRun);
-
- ReportEntryDTO mainRun = new ReportEntryDTO();
- mainRun.setTotal(mainRunValues);
- mainRun.setPercentage(mainRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
- cost.put("main_run", mainRun);
-
- ReportEntryDTO postRun = new ReportEntryDTO();
- postRun.setTotal(postRunValues);
- postRun.setPercentage(postRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
- cost.put("post_run", postRun);
+ if (!noTransportSplit) {
+ ReportEntryDTO preRun = new ReportEntryDTO();
+ preRun.setTotal(preRunValues);
+ preRun.setPercentage(preRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
+ cost.put("pre_run", preRun);
+ }
+ if (!noTransportSplit) {
+ ReportEntryDTO mainRun = new ReportEntryDTO();
+ mainRun.setTotal(mainRunValues);
+ mainRun.setPercentage(mainRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
+ cost.put("main_run", mainRun);
+ }
+ if (!noTransportSplit) {
+ ReportEntryDTO postRun = new ReportEntryDTO();
+ postRun.setTotal(postRunValues);
+ postRun.setPercentage(postRunValues.divide(totalValue, 4, RoundingMode.HALF_UP));
+ cost.put("post_run", postRun);
+ }
+ if (d2dValues.compareTo(BigDecimal.ZERO) != 0) {
+ ReportEntryDTO d2d = new ReportEntryDTO();
+ d2d.setTotal(d2dValues);
+ d2d.setPercentage(d2dValues.divide(totalValue, 4, RoundingMode.HALF_UP));
+ cost.put("d2d", d2d);
+ } else if (noTransportSplit) {
+ ReportEntryDTO transport = new ReportEntryDTO();
+ transport.setTotal(transportValues);
+ transport.setPercentage(transportValues.divide(totalValue, 4, RoundingMode.HALF_UP));
+ cost.put("transport", transport);
+ }
ReportEntryDTO material = new ReportEntryDTO();
material.setTotal(materialValue);
@@ -369,6 +402,6 @@ public class ReportTransformer {
}
private record WeightedTotalCosts(BigDecimal totalPreRunCost, BigDecimal totalMainRunCost,
- BigDecimal totalPostRunCost, BigDecimal totalCost) {
+ BigDecimal totalPostRunCost, BigDecimal totalD2D, BigDecimal totalCost) {
}
}
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 7056453..fd8d7fa 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -351,7 +351,7 @@ CREATE TABLE IF NOT EXISTS premise
individual_hu_width INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
individual_hu_weight INT UNSIGNED COMMENT 'user entered weight in g (if system-wide packaging is used, packaging weight are copied here after creation)',
hu_displayed_dimension_unit CHAR(2) DEFAULT 'MM',
- hu_displayed_weight_unit CHAR(2) DEFAULT 'G',
+ hu_displayed_weight_unit CHAR(2) DEFAULT 'KG',
hu_unit_count INT UNSIGNED DEFAULT NULL,
hu_stackable BOOLEAN DEFAULT TRUE,
hu_mixable BOOLEAN DEFAULT TRUE,