diff --git a/src/frontend/src/pages/CalculationSingleEdit.vue b/src/frontend/src/pages/CalculationSingleEdit.vue index caa285a..d382f25 100644 --- a/src/frontend/src/pages/CalculationSingleEdit.vue +++ b/src/frontend/src/pages/CalculationSingleEdit.vue @@ -170,13 +170,26 @@ export default { const error = await this.premiseEditStore.startCalculation(); if (error !== null) { - this.$refs.toast.addToast({ - icon: 'warning', - message: error.message, - title: "Cannot start calculation", - variant: 'exception', - duration: 8000 - }) + + if(error.title === 'Internal Server Error') { + this.$refs.toast.addToast({ + icon: 'warning', + message: error.message, + title: "Calculation finished with errors", + variant: 'exception', + duration: 8000 + }); + this.close(); + } else { + this.$refs.toast.addToast({ + icon: 'warning', + message: error.message, + title: "Cannot start calculation", + variant: 'exception', + duration: 8000 + }); + } + } else { this.close(); } diff --git a/src/main/java/de/avatic/lcc/config/AsyncConfig.java b/src/main/java/de/avatic/lcc/config/AsyncConfig.java index 511205f..92245f3 100644 --- a/src/main/java/de/avatic/lcc/config/AsyncConfig.java +++ b/src/main/java/de/avatic/lcc/config/AsyncConfig.java @@ -23,6 +23,17 @@ public class AsyncConfig { return executor; } + @Bean(name = "customLookupExecutor") + public Executor customLookupExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setMaxPoolSize(8); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("calc-"); + executor.initialize(); + return executor; + } + @Bean(name = "bulkProcessingExecutor") public Executor bulkProcessingExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); diff --git a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java index 36780f1..0ed0980 100644 --- a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java @@ -253,6 +253,10 @@ public class NodeRepository { // Mark all linked RouteNodes as outdated jdbcTemplate.update("UPDATE premise_route_node SET is_outdated = TRUE WHERE node_id = ?", node.getId()); + // Mark all distance matrix entries as stale + jdbcTemplate.update("UPDATE distance_matrix SET state = 'STALE' WHERE ((from_node_id = ?) OR (to_node_id = ?))", node.getId(), node.getId()); + + return Optional.of(node.getId()); } 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 6767f77..c6a39f8 100644 --- a/src/main/java/de/avatic/lcc/service/access/PremisesService.java +++ b/src/main/java/de/avatic/lcc/service/access/PremisesService.java @@ -110,7 +110,8 @@ public class PremisesService { var validSetId = propertySetRepository.getValidSetId(); var validPeriodId = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("no set of transport rates found that is VALID")); - premises.forEach(premiseId -> preCalculationCheckService.doPrecheck(premiseId, validSetId, validPeriodId)); + var checkResult = premises.stream().map(premiseId -> preCalculationCheckService.doPrecheck(premiseId, validSetId, validPeriodId)).toList(); + CompletableFuture.allOf(checkResult.toArray(new CompletableFuture[0])).join(); var calculationIds = new ArrayList(); diff --git a/src/main/java/de/avatic/lcc/service/api/EUTaxationApiWrapperService.java b/src/main/java/de/avatic/lcc/service/api/EUTaxationApiWrapperService.java index 9caa955..f3fc96f 100644 --- a/src/main/java/de/avatic/lcc/service/api/EUTaxationApiWrapperService.java +++ b/src/main/java/de/avatic/lcc/service/api/EUTaxationApiWrapperService.java @@ -28,6 +28,17 @@ public class EUTaxationApiWrapperService { this.propertyRepository = propertyRepository; } + + public boolean validate(String hsCode) { + try { + var goodsDescription = eUTaxationApiService.getGoodsDescription(hsCode, "en"); + return goodsDescription.getReturn().getResult().getData().isDeclarable() == true; + } catch (Exception e) { + // just continue + } + return false; + } + public List getTariffRates(String hsCode, List countryId) { var futures = countryId.stream().map(country -> getTariffRate(hsCode, country)).toList(); @@ -85,7 +96,7 @@ public class EUTaxationApiWrapperService { } - @Async + @Async("customLookupExecutor") public CompletableFuture getTariffRate(String hsCode, Integer countryId) { var country = countryRepository.getById(countryId); String iso = country.orElseThrow().getIsoCode().name(); @@ -157,7 +168,7 @@ public class EUTaxationApiWrapperService { var dutyRate = measure.getDutyRate(); - if(dutyRate == null) return Optional.empty(); + if (dutyRate == null) return Optional.empty(); if (dutyRate.trim().matches("\\d+\\.\\d+\\s*%")) { return Optional.of(Double.parseDouble(dutyRate.trim().replace("%", "").trim()) / 100); diff --git a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java index 8331eb2..fa3a464 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java @@ -55,7 +55,7 @@ public class NodeBulkImportService { var convertedNode = nodeTransformer.toNodeEntity(excelNode); convertedNode.setId(node.get().getId()); if (!compare(convertedNode, node.get())) { - nodeRepository.update(convertedNode); + var id = nodeRepository.update(convertedNode); } } } 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 dba16b4..8edf572 100644 --- a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java +++ b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java @@ -21,8 +21,10 @@ import de.avatic.lcc.repositories.rates.MatrixRateRepository; import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; import de.avatic.lcc.service.access.PropertyService; import de.avatic.lcc.service.api.CustomApiService; +import de.avatic.lcc.service.api.EUTaxationApiWrapperService; import de.avatic.lcc.service.transformer.generic.DimensionTransformer; import de.avatic.lcc.util.exception.internalerror.PremiseValidationError; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.math.BigDecimal; @@ -31,6 +33,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalUnit; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @Service @@ -53,8 +56,9 @@ public class PreCalculationCheckService { private final ValidityPeriodRepository validityPeriodRepository; private final PropertySetRepository propertySetRepository; private final PropertyRepository propertyRepository; + private final EUTaxationApiWrapperService eUTaxationApiWrapperService; - public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository, PropertyRepository propertyRepository) { + public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository, PropertyRepository propertyRepository, EUTaxationApiWrapperService eUTaxationApiWrapperService) { this.premiseRepository = premiseRepository; this.customApiService = customApiService; this.destinationRepository = destinationRepository; @@ -70,9 +74,11 @@ public class PreCalculationCheckService { this.validityPeriodRepository = validityPeriodRepository; this.propertySetRepository = propertySetRepository; this.propertyRepository = propertyRepository; + this.eUTaxationApiWrapperService = eUTaxationApiWrapperService; } - public void doPrecheck(Integer premiseId, Integer setId, Integer periodId) { + @Async("calculationExecutor") + public CompletableFuture doPrecheck(Integer premiseId, Integer setId, Integer periodId) { var premise = premiseRepository.getPremiseById(premiseId).orElseThrow(); supplierCheck(premise); @@ -126,6 +132,7 @@ public class PreCalculationCheckService { } + return CompletableFuture.completedFuture(null); } private void periodCheck(ValidityPeriod period, PropertySet set) { @@ -319,7 +326,10 @@ public class PreCalculationCheckService { } private void materialCheck(Premise premise) { - if (!customApiService.validate(premise.getHsCode())) + + var isDeclarable = eUTaxationApiWrapperService.validate(premise.getHsCode()); + + if (!isDeclarable) throw new PremiseValidationError("Invalid HS code."); if (premise.getTariffRate() == null) {