Integrated EUTaxationApiWrapperService and asynchronous precheck:

- **Backend**: Added `EUTaxationApiWrapperService` to validate HS codes during pre-calculation. Updated `PreCalculationCheckService` to run asynchronously and return `CompletableFuture`.
- **Frontend**: Refined toast messages in `CalculationSingleEdit.vue` to handle and display server errors more clearly.
- **Database**: Marked distance matrix entries as stale during node updates.
- **Other**: Added `customLookupExecutor` for asynchronous processing in `AsyncConfig`. Updated related services and repositories to utilize async improvements.
This commit is contained in:
Jan 2025-11-07 12:09:21 +01:00
parent a17d50b9a6
commit d9229b3d73
7 changed files with 64 additions and 14 deletions

View file

@ -170,13 +170,26 @@ export default {
const error = await this.premiseEditStore.startCalculation();
if (error !== null) {
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();
}

View file

@ -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();

View file

@ -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());
}

View file

@ -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<Integer>();

View file

@ -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<CustomDTO> getTariffRates(String hsCode, List<Integer> countryId) {
var futures = countryId.stream().map(country -> getTariffRate(hsCode, country)).toList();
@ -85,7 +96,7 @@ public class EUTaxationApiWrapperService {
}
@Async
@Async("customLookupExecutor")
public CompletableFuture<CustomDTO> 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);

View file

@ -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);
}
}
}

View file

@ -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<Void> 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) {