From be1ef5091b5e9be1cc168df0785ccfec6565aaf6 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 1 Nov 2025 13:27:12 +0100 Subject: [PATCH] Refined distance calculations and enhanced reporting layout: - **Backend**: Adjusted `getDistance` implementation to retrieve distances via `DistanceApiService`, with fallback to `fast` algorithm. Updated `DistanceService` to utilize API responses for more accurate calculations. Enhanced `ExcelReportingService` handling for main run containers and mixed premises logic. - **Frontend**: Improved `Report.vue` structure for better readability and modularity. Added logic to conditionally display container details based on the main run's existence. --- .../src/components/layout/report/Report.vue | 429 +++++++++--------- .../src/pages/CalculationMassEdit.vue | 7 +- .../src/pages/CalculationSingleEdit.vue | 40 +- src/frontend/src/store/premiseEdit.js | 5 + src/frontend/src/store/reports.js | 3 +- .../de/avatic/lcc/config/SecurityConfig.java | 2 +- .../lcc/service/api/DistanceApiService.java | 12 +- .../service/calculation/DistanceService.java | 12 +- .../service/calculation/RoutingService.java | 4 +- .../RouteSectionCostCalculationService.java | 2 +- .../PreCalculationCheckService.java | 2 +- .../service/report/ExcelReportingService.java | 19 +- .../lcc/service/report/ReportingService.java | 11 +- .../transformer/report/ReportTransformer.java | 3 +- 14 files changed, 297 insertions(+), 254 deletions(-) diff --git a/src/frontend/src/components/layout/report/Report.vue b/src/frontend/src/components/layout/report/Report.vue index 855b606..be41be6 100644 --- a/src/frontend/src/components/layout/report/Report.vue +++ b/src/frontend/src/components/layout/report/Report.vue @@ -1,7 +1,8 @@ @@ -265,9 +273,12 @@ export default { } }, methods: { + hasMainRun(sections) { + return sections.some(section => section.transport_type === 'SEA' || section.transport_type === 'RAIL'); + }, shorten(text, length) { - if(text !== null && text !== undefined && text.length > length) { - return `${text.substring(0, length - 3)} …` ; + if (text !== null && text !== undefined && text.length > length) { + return `${text.substring(0, length - 3)} …`; } return text; @@ -284,11 +295,11 @@ export default { return '' }, toFixedDimension(value, unit) { - if(unit === 'm') { + if (unit === 'm') { return value.toFixed(2); - } else if(unit === 'cm') { + } else if (unit === 'cm') { return value.toFixed(2); - } else if(unit === 'mm') { + } else if (unit === 'mm') { return value.toFixed(); } }, diff --git a/src/frontend/src/pages/CalculationMassEdit.vue b/src/frontend/src/pages/CalculationMassEdit.vue index b0a389e..3fd7015 100644 --- a/src/frontend/src/pages/CalculationMassEdit.vue +++ b/src/frontend/src/pages/CalculationMassEdit.vue @@ -190,7 +190,7 @@ export default { return this.modalType ? this.componentsData[this.modalType] : null; }, showProcessingModal() { - return this.premiseEditStore.showProcessingModal; + return this.premiseEditStore.showProcessingModal || this.showCalculationModal; } }, created() { @@ -224,7 +224,8 @@ export default { }, editIds: null, dataSourceId: null, - processingMessage: "Please wait. Calculating routes ...", + processingMessage: "Please wait. Calculating ...", + showCalculationModal: false } }, methods: { @@ -244,6 +245,7 @@ export default { } }, async startCalculation() { + this.showCalculationModal = true; const error = await this.premiseEditStore.startCalculation(); if (error !== null) { @@ -258,6 +260,7 @@ export default { } else { this.closeMassEdit() } + this.showCalculationModal = false; }, closeMassEdit() { this.$router.push({name: "calculation-list"}); diff --git a/src/frontend/src/pages/CalculationSingleEdit.vue b/src/frontend/src/pages/CalculationSingleEdit.vue index 9e2ff29..34cef13 100644 --- a/src/frontend/src/pages/CalculationSingleEdit.vue +++ b/src/frontend/src/pages/CalculationSingleEdit.vue @@ -86,6 +86,13 @@

Destinations & routes

+ +
+ + {{ processingMessage }} +
+
+ @@ -131,6 +138,8 @@ export default { traceModal: false, bulkEditQuery: null, id: null, + processingMessage: "Please wait. Calculating ...", + showCalculationModal: false, } }, computed: { @@ -140,15 +149,18 @@ export default { }, fromMassEdit() { return this.bulkEditQuery !== null; - } + }, + showProcessingModal() { + return this.premiseEditStore.showProcessingModal || this.showCalculationModal; + }, }, methods: { + async startCalculation() { + this.showCalculationModal = true; const error = await this.premiseEditStore.startCalculation(); - if (error !== null) { - this.$refs.toast.addToast({ icon: 'warning', message: error.message, @@ -159,6 +171,8 @@ export default { } else { this.close(); } + + this.showCalculationModal = false; }, close() { if (this.bulkEditQuery) { @@ -181,17 +195,6 @@ export default { success = await this.premiseEditStore.savePackaging(); } - - // if(success) { - // this.$refs.toast.addToast({ - // icon: 'floppy-disk', - // message: `Changes on ${type} saved.`, - // - // variant: 'primary', - // duration: 3000 - // }) - // } - }, updateMaterial(id, action) { console.log(id, action); @@ -287,11 +290,18 @@ export default { text-decoration: underline; } +.space-around { + margin: 3rem; +} + .edit-calculation-spinner-container { display: flex; + flex-direction: column; align-items: center; justify-content: center; - flex: 1 1 30rem; + gap: 3.6rem; + flex: 1 1 auto; + font-size: 1.6rem; } .edit-calculation-spinner { diff --git a/src/frontend/src/store/premiseEdit.js b/src/frontend/src/store/premiseEdit.js index 9034713..fb54a12 100644 --- a/src/frontend/src/store/premiseEdit.js +++ b/src/frontend/src/store/premiseEdit.js @@ -599,6 +599,8 @@ export const usePremiseEditStore = defineStore('premiseEdit', { } else { const id = node.id; + this.processDestinationMassEdit = true; + const toBeUpdated = this.destinations.fromMassEditView ? this.destinations.premise_ids : this.premisses?.filter(p => p.selected).map(p => p.id); @@ -611,6 +613,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', { const {data: destinations} = await performRequest(this, 'POST', url, body).catch(e => { this.loading = false; this.selectedLoading = false; + this.processDestinationMassEdit = false; throw e; }); @@ -624,6 +627,8 @@ export const usePremiseEditStore = defineStore('premiseEdit', { this.destinations.destinations.push(mappedDestination); } + this.processDestinationMassEdit = false; + return mappedIds; } }, diff --git a/src/frontend/src/store/reports.js b/src/frontend/src/store/reports.js index d805c94..d83e54e 100644 --- a/src/frontend/src/store/reports.js +++ b/src/frontend/src/store/reports.js @@ -39,9 +39,10 @@ export const useReportsStore = defineStore('reports', { const params = new URLSearchParams(); params.append('material', this.materialId); params.append('sources', this.supplierIds); + params.append('userSources', this.userSupplierIds); const url = `${config.backendUrl}/reports/download/${params.size === 0 ? '' : '?'}${params.toString()}`; - const fileName = `report_${this.materialId}_${this.supplierIds.join('_')}.xlsx`; + const fileName = `report_${this.materialId}_${this.supplierIds.join('_')}_u${this.userSupplierIds.join('_')}.xlsx`; await performDownload(url,fileName); }, reset() { diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java index 73c815f..4819709 100644 --- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java +++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java @@ -71,7 +71,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/actuator/health").permitAll() - .requestMatchers("/actuator/**").authenticated() + .requestMatchers("/actuator/**").hasRole("SERVICE") .requestMatchers("/oauth2/token").permitAll() .requestMatchers("/api/**").authenticated() .requestMatchers("/api/dev/**").denyAll() diff --git a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java index 11e550b..2f6176d 100644 --- a/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java +++ b/src/main/java/de/avatic/lcc/service/api/DistanceApiService.java @@ -13,6 +13,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.math.BigDecimal; +import java.net.URI; import java.time.LocalDateTime; import java.util.Optional; @@ -35,6 +36,7 @@ public class DistanceApiService { } public Optional getDistance(Node from, Node to) { + if (from == null || to == null) { logger.warn("Source or destination node is null"); return Optional.empty(); @@ -46,7 +48,6 @@ public class DistanceApiService { return Optional.empty(); } - // Check if distance exists in database and is valid Optional cachedDistance = distanceMatrixRepository.getDistance(from, to); if (cachedDistance.isPresent()) { @@ -54,12 +55,10 @@ public class DistanceApiService { return cachedDistance; } - // Distance not found or stale, fetch from Azure Maps - logger.info("Fetching distance from Azure Maps for nodes {} to {}", from.getId(), to.getId()); + logger.debug("Fetching distance from Azure Maps for nodes {} to {}", from.getId(), to.getId()); Optional fetchedDistance = fetchDistanceFromAzureMaps(from, to); if (fetchedDistance.isPresent()) { - // Store in database distanceMatrixRepository.saveDistance(fetchedDistance.get()); return fetchedDistance; } @@ -69,18 +68,19 @@ public class DistanceApiService { private Optional fetchDistanceFromAzureMaps(Node from, Node to) { try { - String url = UriComponentsBuilder.fromHttpUrl(AZURE_MAPS_ROUTE_API) + String url = UriComponentsBuilder.fromUriString(AZURE_MAPS_ROUTE_API) .queryParam("api-version", "1.0") .queryParam("subscription-key", subscriptionKey) .queryParam("query", String.format("%s,%s:%s,%s", from.getGeoLat(), from.getGeoLng(), to.getGeoLat(), to.getGeoLng())) + .encode() .toUriString(); RouteDirectionsResponse response = restTemplate.getForObject(url, RouteDirectionsResponse.class); if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) { - Integer distanceInMeters = response.getRoutes().get(0).getSummary().getLengthInMeters(); + Integer distanceInMeters = response.getRoutes().getFirst().getSummary().getLengthInMeters(); Distance distance = new Distance(); distance.setFromNodeId(from.getId()); diff --git a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java index a83314d..869c669 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/DistanceService.java @@ -6,6 +6,7 @@ import de.avatic.lcc.model.db.nodes.Location; import de.avatic.lcc.model.db.nodes.Node; import de.avatic.lcc.repositories.DistanceMatrixRepository; import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.service.api.DistanceApiService; import org.springframework.stereotype.Service; @Service @@ -14,20 +15,21 @@ public class DistanceService { private static final double EARTH_RADIUS = 6371.0; private final DistanceMatrixRepository distanceMatrixRepository; private final CountryRepository countryRepository; + private final DistanceApiService distanceApiService; - public DistanceService(DistanceMatrixRepository distanceMatrixRepository, CountryRepository countryRepository) { + public DistanceService(DistanceMatrixRepository distanceMatrixRepository, CountryRepository countryRepository, DistanceApiService distanceApiService) { this.distanceMatrixRepository = distanceMatrixRepository; this.countryRepository = countryRepository; + this.distanceApiService = distanceApiService; } public double getDistance(Node src, Node dest, boolean fast) { if (fast) return getDistanceFast(src, dest); - var distance = distanceMatrixRepository.getDistance(src, dest); - - // TODO do a api call if empty!. - return distance.map(value -> value.getDistance().intValue()).orElse(0); + var distance = distanceApiService.getDistance(src, dest); + if (distance.isEmpty()) return getDistanceFast(src, dest); + return distance.map(value -> value.getDistance().intValue()/1000).orElse(0); } public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) { 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 2d227e3..b4bab92 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/RoutingService.java @@ -334,7 +334,7 @@ public class RoutingService { } finalSection.setRate(matrixRate); - finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, true)); + finalSection.setApproxDistance(distanceService.getDistance(container.getSourceNode(), toNode, false)); rates.add(finalSection); } @@ -699,7 +699,7 @@ public class RoutingService { if (matrixRate.isPresent()) { matrixRateObj.setRate(matrixRate.get()); - matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, true)); + matrixRateObj.setApproxDistance(distanceService.getDistance(startNode, endNode, false)); container.getRates().add(matrixRateObj); return matrixRateObj; } else { 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 2182034..cf7a482 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 @@ -67,7 +67,7 @@ public class RouteSectionCostCalculationService { Node fromNode = nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow(); Node toNode = nodeRepository.getById(destination.getDestinationNodeId()).orElseThrow(); - double distance = distanceService.getDistance(fromNode, toNode, true); + double distance = distanceService.getDistance(fromNode, toNode, false); result.setDistance(BigDecimal.valueOf(distance)); // Get rate and transit time 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 21fe638..dba16b4 100644 --- a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java +++ b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java @@ -153,7 +153,7 @@ public class PreCalculationCheckService { var validDaysInt = Integer.parseInt(validDays.get().getCurrentValue()); - if(!period.getStartDate().plusDays((long) validDaysInt * renewals).isAfter(LocalDateTime.now())) + if(!period.getStartDate().plusDays((((long) validDaysInt * renewals)+validDaysInt)).isAfter(LocalDateTime.now())) throw new PremiseValidationError("There are no valid rates for the given date. Please contact your administrator."); } diff --git a/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java b/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java index c5b25eb..64f7c83 100644 --- a/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java +++ b/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java @@ -1,6 +1,7 @@ package de.avatic.lcc.service.report; import de.avatic.lcc.dto.generic.NodeDTO; +import de.avatic.lcc.dto.generic.TransportType; import de.avatic.lcc.dto.report.ReportDTO; import de.avatic.lcc.dto.report.ReportDestinationDTO; import de.avatic.lcc.dto.report.ReportEntryDTO; @@ -145,6 +146,9 @@ public class ExcelReportingService { } private void flattenDestination(ReportDestinationDTO destination) { + + var hasMainRun = destination.getSections().stream().anyMatch(s -> s.getTransportType().equals(TransportType.RAIL) || s.getTransportType().equals(TransportType.SEA)); + addData(DESTINATION_NAME, destination.getDestination().getName()); addData(DESTINATION_ADDRESS, destination.getDestination().getAddress()); @@ -152,6 +156,7 @@ public class ExcelReportingService { addData(DESTINATION_HS_CODE, destination.getHsCode()); addData(DESTINATION_TARIFF_RATE, destination.getTariffRate().toString()); addData(DESTINATION_OVERSHARE, destination.getOverseaShare().toString()); + if(destination.getAirFreightShare() != null) addData(DESTINATION_AIR_FREIGHT_SHARE, destination.getAirFreightShare().toString()); addData(DESTINATION_TRANSPORT_TIME, destination.getTransportTime().toString()); @@ -164,14 +169,14 @@ public class ExcelReportingService { addData(DESTINATION_WEIGHT, destination.getWeight().toString()); addData(DESTINATION_WEIGHT_UNIT, destination.getWeightUnit().toString()); addData(DESTINATION_HU_UNIT_COUNT, destination.getHuUnitCount().toString()); - addData(DESTINATION_CONTAINER_LAYER, destination.getLayer().toString()); + addData(DESTINATION_CONTAINER_LAYER, !hasMainRun ? "-" : destination.getLayer().toString()); - addData(DESTINATION_CONTAINER_UNIT_COUNT, destination.getUnitCount().toString()); - addData(DESTINATION_CONTAINER_UTILIZATION, destination.getUtilization().toString()); - addData(DESTINATION_CONTAINER_TYPE, destination.getType().toString()); - addData(DESTINATION_CONTAINER_WEIGHT_EXCEEDED, destination.getWeightExceeded().toString()); - addData(DESTINATION_CONTAINER_RATE, destination.getRate().toString()); - addData(DESTINATION_MIXED, destination.getMixed().toString()); + addData(DESTINATION_CONTAINER_UNIT_COUNT, !hasMainRun ? "-" : destination.getUnitCount().toString()); + addData(DESTINATION_CONTAINER_UTILIZATION, !hasMainRun ? "-" : destination.getUtilization().toString()); + addData(DESTINATION_CONTAINER_TYPE, !hasMainRun ? "-" : destination.getType().toString()); + addData(DESTINATION_CONTAINER_WEIGHT_EXCEEDED, !hasMainRun ? "-" : destination.getWeightExceeded().toString()); + addData(DESTINATION_CONTAINER_RATE, !hasMainRun ? "-" : destination.getRate().toString()); + addData(DESTINATION_MIXED, !hasMainRun ? "-" : destination.getMixed().toString()); } diff --git a/src/main/java/de/avatic/lcc/service/report/ReportingService.java b/src/main/java/de/avatic/lcc/service/report/ReportingService.java index 774e08f..895fb4e 100644 --- a/src/main/java/de/avatic/lcc/service/report/ReportingService.java +++ b/src/main/java/de/avatic/lcc/service/report/ReportingService.java @@ -2,6 +2,7 @@ package de.avatic.lcc.service.report; import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.report.ReportDTO; +import de.avatic.lcc.model.db.calculations.CalculationJob; import de.avatic.lcc.repositories.NodeRepository; import de.avatic.lcc.repositories.calculation.CalculationJobRepository; import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; @@ -58,10 +59,14 @@ public class ReportingService { var periodId = tuple.get().periodId(); var setId = tuple.get().propertySetId(); - //TODO check user node id and node id for null - var jobs = new ArrayList<>(nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, setId, nodeId, materialId)).filter(Optional::isPresent).map(Optional::get).toList()); - jobs.addAll(userNodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValidUserNodeId(periodId, setId,nodeId ,materialId)).filter(Optional::isPresent).map(Optional::get).toList()); + var jobs = new ArrayList(); + + if(!nodeIds.isEmpty()) + jobs.addAll(nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, setId, nodeId, materialId)).filter(Optional::isPresent).map(Optional::get).toList()); + + if(!userNodeIds.isEmpty()) + jobs.addAll(userNodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValidUserNodeId(periodId, setId,nodeId ,materialId)).filter(Optional::isPresent).map(Optional::get).toList()); return jobs.stream().map(reportTransformer::toReportDTO).toList(); } 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 af370df..210f2a1 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 @@ -190,6 +190,8 @@ public class ReportTransformer { destinationDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight())); destinationDTO.setHuUnitCount(premise.getHuUnitCount()); + CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null); + destinationDTO.setLayer(destination.getLayerCount()); destinationDTO.setOverseaShare(premise.getOverseaShare().doubleValue()); @@ -199,7 +201,6 @@ public class ReportTransformer { if (includeAirfreight) destinationDTO.setAirFreightShare(destination.getAirFreightShare().doubleValue()); - CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null); destinationDTO.setMixed(premise.getHuMixable()); destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate());