From 088a4c62d81e0d5f4930ed58d7c2d57a1c7f3725 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 15 Dec 2025 10:26:50 +0100 Subject: [PATCH 01/88] fixed so that properties with "0" as value are shown correctly --- src/frontend/src/components/layout/config/Property.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/components/layout/config/Property.vue b/src/frontend/src/components/layout/config/Property.vue index 66417b0..805cb49 100644 --- a/src/frontend/src/components/layout/config/Property.vue +++ b/src/frontend/src/components/layout/config/Property.vue @@ -178,15 +178,16 @@ export default { } if (this.property.data_type === 'INT') { - this.value = parseNumberFromString(this.value, 0); + this.value = parseNumberFromString(this.value, 0, true); } if (this.property.data_type === 'PERCENTAGE') { - this.value = parseNumberFromString(this.value, 4); + this.value = parseNumberFromString(this.value, 4, true); } if (this.property.data_type === 'CURRENCY') { - this.value = parseNumberFromString(this.value, 2); + this.value = parseNumberFromString(this.value, 2, true); + console.log(this.property.name, " parsed from 'currency' property: '", this.value, "'") } } } -- 2.45.3 From e53dd7b920e24875aeaa3dee43c466c54502a25a Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 16 Dec 2025 20:25:17 +0100 Subject: [PATCH 02/88] Reworked excel reporting --- .../src/components/layout/report/Report.vue | 48 +- src/frontend/src/store/reports.js | 2 +- .../report/ReportingController.java | 6 +- .../avatic/lcc/dto/generic/ContainerType.java | 16 +- .../lcc/dto/report/ReportDestinationDTO.java | 52 +- .../lcc/dto/report/ReportPremisesDTO.java | 47 + .../lcc/model/bulk/header/HeaderProvider.java | 10 + .../bulk/header/ReportSummaryHeader.java | 49 ++ .../lcc/service/bulk/BulkExportService.java | 11 +- .../service/bulk/TemplateExportService.java | 10 +- .../bulk/helper/CellStyleProvider.java | 138 +++ .../bulk/helper/HeaderCellStyleProvider.java | 44 - .../service/bulk/helper/HeaderGenerator.java | 131 ++- .../service/report/ExcelReportingService.java | 816 ++++++++++++++---- .../transformer/report/ReportTransformer.java | 21 +- 15 files changed, 1093 insertions(+), 308 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/model/bulk/header/ReportSummaryHeader.java create mode 100644 src/main/java/de/avatic/lcc/service/bulk/helper/CellStyleProvider.java delete mode 100644 src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java diff --git a/src/frontend/src/components/layout/report/Report.vue b/src/frontend/src/components/layout/report/Report.vue index 4ea9a50..3b5420b 100644 --- a/src/frontend/src/components/layout/report/Report.vue +++ b/src/frontend/src/components/layout/report/Report.vue @@ -171,16 +171,28 @@
- + :stretch-content="true">
total [€]
-
of MEK_B [%]
+
of MEK B [%]
+
+ +
+
Current scenario
+
{{ + report.overview.mek_b.total.toFixed(2) + }} € +
+
{{ + `${(report.overview.mek_b.percentage * 100).toFixed(2)}` + }} +
@@ -229,6 +241,20 @@
{{ (report.premises.tariff_rate * 100).toFixed(2) }}%
+
+
Oversea share
+
{{ (report.premises.oversea_share * 100).toFixed(2) }}%
+
+ +
+
Airfreight share
+
{{ (report.premises.air_freight_share * 100).toFixed(2) }}%
+
+
+
Safety stock [w-days]
+
{{ report.premises.safety_stock }}
+
+
Handling unit
@@ -260,7 +286,7 @@
Mixed transport
-
{{ report.premises.mixed ? 'Yes' : 'No' }}
+
{{ report.premises.mixable ? 'Yes' : 'No' }}
@@ -287,25 +313,13 @@
{{ destination.annual_quantity }}
-
-
Oversea share
-
{{ (destination.oversea_share * 100).toFixed(2) }}%
-
- -
-
Airfreight share
-
{{ (destination.air_freight_share * 100).toFixed(2) }}%
-
Transit time [days]
{{ destination.transport_time }}
-
-
Safety stock [w-days]
-
{{ destination.safety_stock }}
-
+ diff --git a/src/frontend/src/store/reports.js b/src/frontend/src/store/reports.js index 0e73e42..72795fc 100644 --- a/src/frontend/src/store/reports.js +++ b/src/frontend/src/store/reports.js @@ -37,7 +37,7 @@ export const useReportsStore = defineStore('reports', { return; const params = new URLSearchParams(); - params.append('material', this.materialId); + params.append('materials', [this.materialId]); params.append('sources', this.supplierIds); params.append('userSources', this.userSupplierIds); diff --git a/src/main/java/de/avatic/lcc/controller/report/ReportingController.java b/src/main/java/de/avatic/lcc/controller/report/ReportingController.java index 423dfe1..1982f1c 100644 --- a/src/main/java/de/avatic/lcc/controller/report/ReportingController.java +++ b/src/main/java/de/avatic/lcc/controller/report/ReportingController.java @@ -81,13 +81,13 @@ public class ReportingController { /** * Downloads an Excel report for the given material and source nodes. * - * @param materialId The ID of the material for which the report will be downloaded. + * @param materialIds The IDs of the materials for which the report will be downloaded. * @param nodeIds A list of node IDs (sources) to include in the downloaded report. * @return The Excel file as an attachment in the response. */ @GetMapping({"/download", "/download/"}) @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC')") - public ResponseEntity downloadReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources", required = false) List nodeIds, @RequestParam(value = "userSources", required = false) List userNodeIds) { + public ResponseEntity downloadReport(@RequestParam(value = "materials") List materialIds, @RequestParam(value = "sources", required = false) List nodeIds, @RequestParam(value = "userSources", required = false) List userNodeIds) { HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=lcc_report.xlsx"); @@ -96,6 +96,6 @@ public class ReportingController { .ok() .headers(headers) .contentType(MediaType.parseMediaType("application/vnd.ms-excel")) - .body(new InputStreamResource(excelReportingService.generateExcelReport(materialId, nodeIds, userNodeIds))); + .body(new InputStreamResource(excelReportingService.generateExcelReport(materialIds, nodeIds, userNodeIds))); } } diff --git a/src/main/java/de/avatic/lcc/dto/generic/ContainerType.java b/src/main/java/de/avatic/lcc/dto/generic/ContainerType.java index d1d0369..2df42a1 100644 --- a/src/main/java/de/avatic/lcc/dto/generic/ContainerType.java +++ b/src/main/java/de/avatic/lcc/dto/generic/ContainerType.java @@ -1,10 +1,10 @@ package de.avatic.lcc.dto.generic; public enum ContainerType { - FEU(12030, 2350, 2390, 67.7, 24,21), - TEU(5890 ,2350,2390, 33.0, 11,10), - HC(12030, 2350, 2690, 76.4, 24,21), - TRUCK(13600,2450, 2650, 88.3, 34, 33); + FEU(12030, 2350, 2390, 67.7, 24,21, "40' GP"), + TEU(5890 ,2350,2390, 33.0, 11,10, "20' GP"), + HC(12030, 2350, 2690, 76.4, 24,21,"40' HC"), + TRUCK(13600,2450, 2650, 88.3, 34, 33, "Truck"); private final int length; private final int width; @@ -12,14 +12,16 @@ public enum ContainerType { private final double volume; private final int euroPalletCount; private final int industrialPalletCount; + private final String description; - ContainerType(int length, int width, int height, double volume, int euroPalletCount, int industrialPalletCount) { + ContainerType(int length, int width, int height, double volume, int euroPalletCount, int industrialPalletCount, String description) { this.length = length; this.width = width; this.height = height; this.volume = volume; this.euroPalletCount = euroPalletCount; this.industrialPalletCount = industrialPalletCount; + this.description = description; } public int getLength() { @@ -40,4 +42,8 @@ public enum ContainerType { return palletType == PalletType.EURO_PALLET ? euroPalletCount : industrialPalletCount; } + + public String getDescription() { + return description; + } } diff --git a/src/main/java/de/avatic/lcc/dto/report/ReportDestinationDTO.java b/src/main/java/de/avatic/lcc/dto/report/ReportDestinationDTO.java index bed315e..0ac4807 100644 --- a/src/main/java/de/avatic/lcc/dto/report/ReportDestinationDTO.java +++ b/src/main/java/de/avatic/lcc/dto/report/ReportDestinationDTO.java @@ -16,24 +16,19 @@ public class ReportDestinationDTO { private List sections; /* general */ - @JsonProperty("oversea_share") - private Double overseaShare; - - @JsonProperty("air_freight_share") - private Double airFreightShare; @JsonProperty("transport_time") private Double transportTime; - @JsonProperty("safety_stock") - private Integer safetyStock; @JsonProperty("annual_quantity") private Integer annualQuantity; - private Integer layer; /* container */ + + private Integer layer; + @JsonProperty("unit_count") private Number unitCount; @@ -48,7 +43,6 @@ public class ReportDestinationDTO { @JsonProperty("container_rate") private Number rate; - private Boolean mixed; public Integer getId() { return id; @@ -82,38 +76,6 @@ public class ReportDestinationDTO { this.annualQuantity = annualQuantity; } - public Double getOverseaShare() { - return overseaShare; - } - - public void setOverseaShare(Double overseaShare) { - this.overseaShare = overseaShare; - } - - public Double getAirFreightShare() { - return airFreightShare; - } - - public void setAirFreightShare(Double airFreightShare) { - this.airFreightShare = airFreightShare; - } - - public Double getTransportTime() { - return transportTime; - } - - public void setTransportTime(Double transportTime) { - this.transportTime = transportTime; - } - - public Integer getSafetyStock() { - return safetyStock; - } - - public void setSafetyStock(Integer safetyStock) { - this.safetyStock = safetyStock; - } - public Integer getLayer() { return layer; } @@ -162,11 +124,11 @@ public class ReportDestinationDTO { this.rate = rate; } - public Boolean getMixed() { - return mixed; + public Double getTransportTime() { + return transportTime; } - public void setMixed(Boolean mixed) { - this.mixed = mixed; + public void setTransportTime(Double transportTime) { + this.transportTime = transportTime; } } diff --git a/src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java b/src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java index d325ac6..b5bf464 100644 --- a/src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java +++ b/src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java @@ -13,6 +13,16 @@ public class ReportPremisesDTO { @JsonProperty("tariff_rate") private Number tariffRate; + @JsonProperty("oversea_share") + private Number overseaShare; + + @JsonProperty("air_freight_share") + private Number airFreightShare; + + @JsonProperty("safety_stock") + private Number safetyStock; + + /* packaging */ @@ -33,6 +43,9 @@ public class ReportPremisesDTO { @JsonProperty("hu_unit_count") private Integer huUnitCount; + @JsonProperty("mixable") + private Boolean mixable; + public String getHsCode() { return hsCode; @@ -105,4 +118,38 @@ public class ReportPremisesDTO { public void setHuUnitCount(Integer huUnitCount) { this.huUnitCount = huUnitCount; } + + + + public Number getOverseaShare() { + return overseaShare; + } + + public void setOverseaShare(Number overseaShare) { + this.overseaShare = overseaShare; + } + + public Number getAirFreightShare() { + return airFreightShare; + } + + public void setAirFreightShare(Number airFreightShare) { + this.airFreightShare = airFreightShare; + } + + public Number getSafetyStock() { + return safetyStock; + } + + public void setSafetyStock(Number safetyStock) { + this.safetyStock = safetyStock; + } + + public Boolean getMixable() { + return mixable; + } + + public void setMixable(Boolean mixable) { + this.mixable = mixable; + } } diff --git a/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java b/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java index eeade12..2ecb607 100644 --- a/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java +++ b/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java @@ -3,4 +3,14 @@ package de.avatic.lcc.model.bulk.header; public interface HeaderProvider { String getHeader(); + default int occupiedCells() { + return 1; + } + + default int getColumn() { throw new UnsupportedOperationException();} + + default boolean useOrdinal() { + return true; + } + } diff --git a/src/main/java/de/avatic/lcc/model/bulk/header/ReportSummaryHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/ReportSummaryHeader.java new file mode 100644 index 0000000..c513739 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/header/ReportSummaryHeader.java @@ -0,0 +1,49 @@ +package de.avatic.lcc.model.bulk.header; + +public enum ReportSummaryHeader implements HeaderProvider{ + MATERIAL("Material", 0, 1), + SUPPLIER("Supplier", 1, 1), + MEK_A("MEK A", 2, 2), + LOGISTICS_COST("Logistics costs", 4, 2), + MEK_B("MEK B", 6, 2), + TRANSPORT("Transport", 8, 1), + HANDLING("Handling", 9, 1), + STORAGE("Storage", 10, 1), + REPACKAGING("Repackaging", 11, 1), + DISPOSAL("Disposal", 12, 1), + CAPITAL("Capital", 13, 1), + CUSTOM("Custom", 14, 1), + FCA_FEE("FCA Fee", 15, 1), + AIR_FREIGHT("Air freight", 16, 1); + + + private final int occupiedCells; + private final String header; + private final int column; + + ReportSummaryHeader(String header, int column, int occupiedCells) { + this.occupiedCells = occupiedCells; + this.header = header; + this.column = column; + } + + @Override + public boolean useOrdinal() { + return false; + } + + @Override + public String getHeader() { + return header; + } + + @Override + public int occupiedCells() { + return occupiedCells; + } + + @Override + public int getColumn() { + return column; + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java index 6bd323d..6b0c1a6 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java @@ -4,8 +4,7 @@ import de.avatic.lcc.dto.bulk.BulkFileType; import de.avatic.lcc.model.bulk.BulkFileTypes; import de.avatic.lcc.model.bulk.BulkOperation; import de.avatic.lcc.model.bulk.HiddenTableType; -import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; -import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; +import de.avatic.lcc.service.bulk.helper.CellStyleProvider; import de.avatic.lcc.service.excelMapper.*; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Sheet; @@ -21,7 +20,7 @@ import java.io.IOException; @Service public class BulkExportService { - private final HeaderCellStyleProvider headerCellStyleProvider; + private final CellStyleProvider cellStyleProvider; private final ContainerRateExcelMapper containerRateExcelMapper; private final MatrixRateExcelMapper matrixRateExcelMapper; private final PackagingExcelMapper packagingExcelMapper; @@ -31,8 +30,8 @@ public class BulkExportService { private final String sheetPassword; private final MaterialFastExcelMapper materialFastExcelMapper; - public BulkExportService(@Value("${lcc.bulk.sheet_password}") String sheetPassword, HeaderCellStyleProvider headerCellStyleProvider, ContainerRateExcelMapper containerRateExcelMapper, MatrixRateExcelMapper matrixRateExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, HiddenNodeExcelMapper hiddenNodeExcelMapper, HiddenCountryExcelMapper hiddenCountryExcelMapper, MaterialFastExcelMapper materialFastExcelMapper) { - this.headerCellStyleProvider = headerCellStyleProvider; + public BulkExportService(@Value("${lcc.bulk.sheet_password}") String sheetPassword, CellStyleProvider cellStyleProvider, ContainerRateExcelMapper containerRateExcelMapper, MatrixRateExcelMapper matrixRateExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, HiddenNodeExcelMapper hiddenNodeExcelMapper, HiddenCountryExcelMapper hiddenCountryExcelMapper, MaterialFastExcelMapper materialFastExcelMapper) { + this.cellStyleProvider = cellStyleProvider; this.containerRateExcelMapper = containerRateExcelMapper; this.matrixRateExcelMapper = matrixRateExcelMapper; this.packagingExcelMapper = packagingExcelMapper; @@ -66,7 +65,7 @@ public class BulkExportService { Workbook workbook = new XSSFWorkbook(); Sheet worksheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); - CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook); + CellStyle style = cellStyleProvider.createHeaderCellStyle(workbook); if (bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) { diff --git a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java index e5159f4..79954a8 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java @@ -3,7 +3,7 @@ package de.avatic.lcc.service.bulk; import de.avatic.lcc.dto.bulk.BulkFileType; import de.avatic.lcc.model.bulk.*; import de.avatic.lcc.model.bulk.header.*; -import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; +import de.avatic.lcc.service.bulk.helper.CellStyleProvider; import de.avatic.lcc.service.bulk.helper.HeaderGenerator; import de.avatic.lcc.service.excelMapper.*; import org.apache.poi.ss.usermodel.CellStyle; @@ -24,7 +24,7 @@ public class TemplateExportService { private final HeaderGenerator headerGenerator; - private final HeaderCellStyleProvider headerCellStyleProvider; + private final CellStyleProvider cellStyleProvider; private final HiddenNodeExcelMapper hiddenNodeExcelMapper; private final HiddenCountryExcelMapper hiddenCountryExcelMapper; private final String sheetPassword; @@ -34,9 +34,9 @@ public class TemplateExportService { private final PackagingExcelMapper packagingExcelMapper; private final NodeExcelMapper nodeExcelMapper; - public TemplateExportService(@Value("${lcc.bulk.sheet_password}") String sheetPassword, HeaderGenerator headerGenerator, HeaderCellStyleProvider headerCellStyleProvider, HiddenNodeExcelMapper hiddenNodeExcelMapper, HiddenCountryExcelMapper hiddenCountryExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MatrixRateExcelMapper matrixRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper) { + public TemplateExportService(@Value("${lcc.bulk.sheet_password}") String sheetPassword, HeaderGenerator headerGenerator, CellStyleProvider cellStyleProvider, HiddenNodeExcelMapper hiddenNodeExcelMapper, HiddenCountryExcelMapper hiddenCountryExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MatrixRateExcelMapper matrixRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper) { this.headerGenerator = headerGenerator; - this.headerCellStyleProvider = headerCellStyleProvider; + this.cellStyleProvider = cellStyleProvider; this.hiddenNodeExcelMapper = hiddenNodeExcelMapper; this.hiddenCountryExcelMapper = hiddenCountryExcelMapper; this.sheetPassword = sheetPassword; @@ -53,7 +53,7 @@ public class TemplateExportService { Sheet sheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); - CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook); + CellStyle style = cellStyleProvider.createHeaderCellStyle(workbook); if (bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) { diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/CellStyleProvider.java b/src/main/java/de/avatic/lcc/service/bulk/helper/CellStyleProvider.java new file mode 100644 index 0000000..ce6f063 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/CellStyleProvider.java @@ -0,0 +1,138 @@ +package de.avatic.lcc.service.bulk.helper; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class CellStyleProvider { + + public CellStyle createHeaderCellStyle(Workbook workbook) { + + XSSFFont headerFont = (XSSFFont) workbook.createFont(); + + headerFont.setColor(PredefinedColors.NEUTRAL.getColor()); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 11); + headerFont.setBold(true); + + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFont(headerFont); + headerStyle.setFillForegroundColor(PredefinedColors.TITLE.getColor()); + + headerStyle.setAlignment(HorizontalAlignment.CENTER); + headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + return updateBorderStyle(headerStyle, IndexedColors.WHITE); + } + + private XSSFFont createFontStyle(Workbook workbook, boolean bold) { + XSSFFont font = (XSSFFont) workbook.createFont(); + + font.setColor(PredefinedColors.TITLE.getColor()); + font.setFontName("Arial"); + font.setFontHeightInPoints((short) 11); + font.setBold(bold); + + return font; + } + + private CellStyle updateBorderStyle(CellStyle style, IndexedColors color) { + + style.setBorderBottom(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + + style.setLeftBorderColor(color.getIndex()); + style.setRightBorderColor(color.getIndex()); + style.setBottomBorderColor(color.getIndex()); + style.setTopBorderColor(color.getIndex()); + + return style; + } + + + private CellStyle createStyle(Workbook workbook, Short textFormat, XSSFFont fontStyle) { + CellStyle style = workbook.createCellStyle(); + + style.setFont(fontStyle); + style.setAlignment(HorizontalAlignment.RIGHT); + style.setDataFormat(textFormat); + + return updateBorderStyle(style, IndexedColors.GREY_25_PERCENT); + } + + + private CellStyle updateHighlight(CellStyle style, PredefinedColors background) { + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(background.getColor()); + return style; + } + + public Map> createCellStyles(Workbook workbook) { + + Map> styles = new HashMap<>(); + + Map textFormat = new HashMap<>(); + textFormat.put(TextFormat.TEXT_ONLY, workbook.createDataFormat().getFormat("@")); + textFormat.put(TextFormat.CURRENCY, workbook.createDataFormat().getFormat("#,##0.00 €")); + textFormat.put(TextFormat.PERCENTAGE, workbook.createDataFormat().getFormat("0.00%")); + + var fontStyle = createFontStyle(workbook, false); + var headerFontStyle = createFontStyle(workbook, true); + + for (TextFormat currentFormat : TextFormat.values()) { + + Map colors = new HashMap<>(); + + for (PredefinedColors color : PredefinedColors.values()) { + + CellStyle style = createStyle(workbook, textFormat.get(currentFormat), color.getBold() ? headerFontStyle : fontStyle); + colors.put(color, updateHighlight(style, color)); + + } + + styles.put(currentFormat, colors); + } + + return styles; + } + + + public enum TextFormat { + TEXT_ONLY, + CURRENCY, + PERCENTAGE, + } + + public enum PredefinedColors { + HEADER(true, new XSSFColor(new byte[]{(byte) 195, (byte) 207, (byte) 223}, null)), + SUB_HEADER(true, new XSSFColor(new byte[]{(byte) 220, (byte) 227, (byte) 236}, null)), + LIGHT_BLUE_1(true, new XSSFColor(new byte[]{(byte) 220, (byte) 227, (byte) 236}, null)), + LIGHT_BLUE_2(true, new XSSFColor(new byte[]{(byte) 195, (byte) 207, (byte) 223}, null)), + GREEN_1(false, new XSSFColor(new byte[]{(byte) 160, (byte) 246, (byte) 211}, null)), + GREEN_2(false, new XSSFColor(new byte[]{(byte) 90, (byte) 240, (byte) 180}, null)), + TITLE(true, new XSSFColor(new byte[]{(byte) 0, (byte) 47, (byte) 84}, null)), + NEUTRAL(false, new XSSFColor(new byte[]{(byte) 255, (byte) 255, (byte) 255}, null)), + EXCEPTION(false, new XSSFColor(new byte[]{(byte) 188, (byte) 43, (byte) 114}, null)); + + final XSSFColor color; + private final boolean bold; + + PredefinedColors(boolean bold, XSSFColor xssfColor) { + this.color = xssfColor; + this.bold = bold; + } + + public XSSFColor getColor() { + return color; + } + + public boolean getBold() { return bold; } + } + +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java deleted file mode 100644 index f218a98..0000000 --- a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.avatic.lcc.service.bulk.helper; - -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.xssf.usermodel.XSSFCellStyle; -import org.apache.poi.xssf.usermodel.XSSFColor; -import org.apache.poi.xssf.usermodel.XSSFFont; -import org.springframework.stereotype.Service; - -@Service -public class HeaderCellStyleProvider { - - public CellStyle createHeaderCellStyle(Workbook workbook) { - - XSSFFont headerFont = (XSSFFont) workbook.createFont(); - - XSSFColor customTextColor = new XSSFColor(new byte[]{(byte)0, (byte)47, (byte)84}, null); // Blue - headerFont.setColor(customTextColor); - - headerFont.setFontName("Arial"); - headerFont.setFontHeightInPoints((short)10); - headerFont.setBold(true); - - - - XSSFColor customColor = new XSSFColor(new byte[]{(byte)90, (byte)240, (byte)180}, null); - - CellStyle headerStyle = workbook.createCellStyle(); - headerFont.setBold(true); - headerStyle.setFont(headerFont); - headerStyle.setFillForegroundColor(customColor); - - - headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - headerStyle.setBorderBottom(BorderStyle.THIN); - headerStyle.setBorderTop(BorderStyle.THIN); - headerStyle.setBorderLeft(BorderStyle.THIN); - headerStyle.setBorderRight(BorderStyle.THIN); - headerStyle.setAlignment(HorizontalAlignment.CENTER); - return headerStyle; - } - - - -} diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java index e9a3a83..cce553e 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java @@ -6,6 +6,8 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.RegionUtil; import org.springframework.stereotype.Service; import java.util.EnumSet; @@ -13,13 +15,13 @@ import java.util.EnumSet; @Service public class HeaderGenerator { - private static final int ADD_COLUMN_SIZE = (10*256); + private static final int ADD_COLUMN_SIZE = (10 * 256); public & HeaderProvider> void validateHeader(Sheet sheet, Class headers) { Row row = sheet.getRow(0); - for(H header : EnumSet.allOf(headers)){ + for (H header : EnumSet.allOf(headers)) { Cell cell = row.getCell(header.ordinal()); - if(cell == null || !cell.getStringCellValue().equals(header.getHeader())){ + if (cell == null || !cell.getStringCellValue().equals(header.getHeader())) { throw new ExcelValidationError("Unable to validate header \"" + header.getHeader() + "\": Header of column " + toExcelLetter(header.ordinal()) + " has to be \"" + header.getHeader() + "\""); } } @@ -28,9 +30,9 @@ public class HeaderGenerator { public void validateHeader(Sheet sheet, String[] headers) { Row row = sheet.getRow(0); int idx = 0; - for(String header : headers){ + for (String header : headers) { Cell cell = row.getCell(idx++); - if(cell == null || !cell.getStringCellValue().equals(header)){ + if (cell == null || !cell.getStringCellValue().equals(header)) { throw new ExcelValidationError("Unable to validate header \"" + header + "\": Header of column " + toExcelLetter(idx) + " has to be \"" + header + "\""); } @@ -39,39 +41,132 @@ public class HeaderGenerator { public & HeaderProvider> void generateHeader(Sheet worksheet, Class headers, CellStyle style) { Row row = worksheet.createRow(0); + + Boolean usesOrdinals = null; + for (H header : EnumSet.allOf(headers)) { - Cell cell = row.createCell(header.ordinal()); - cell.setCellValue(header.getHeader()); - cell.setCellStyle(style); - worksheet.autoSizeColumn(header.ordinal()); + + if (usesOrdinals == null) usesOrdinals = header.useOrdinal(); + + if (usesOrdinals) { + Cell cell = row.createCell(header.ordinal()); + cell.setCellValue(header.getHeader()); + cell.setCellStyle(style); + worksheet.autoSizeColumn(header.ordinal()); + + } else { + Cell cell = row.createCell(header.getColumn()); + + if (header.occupiedCells() > 1) { + + + var merged = new CellRangeAddress(0, 0, header.getColumn(), header.getColumn() + header.occupiedCells() - 1); + + RegionUtil.setBorderBottom(style.getBorderBottom(), merged, worksheet); + RegionUtil.setBorderTop(style.getBorderTop(), merged, worksheet); + RegionUtil.setBorderLeft(style.getBorderLeft(), merged, worksheet); + RegionUtil.setBorderRight(style.getBorderRight(), merged, worksheet); + RegionUtil.setBottomBorderColor(style.getBottomBorderColor(), merged, worksheet); + RegionUtil.setTopBorderColor(style.getTopBorderColor(), merged, worksheet); + RegionUtil.setLeftBorderColor(style.getLeftBorderColor(), merged, worksheet); + RegionUtil.setRightBorderColor(style.getRightBorderColor(), merged, worksheet); + + worksheet.addMergedRegion(merged); + } + + cell.setCellValue(header.getHeader()); + cell.setCellStyle(style); + worksheet.autoSizeColumn(header.getColumn()); + } } } public void generateHeader(Sheet worksheet, String[] headers, CellStyle style) { + generateHeader(worksheet, headers, style, false); + } + + public void generateHeader(Sheet worksheet, String[] headers, CellStyle style, boolean mergeCells) { Row row = worksheet.createRow(0); - int idx = 0; - for (String header : headers) { - Cell cell = row.createCell(idx); - cell.setCellValue(header); - cell.setCellStyle(style); - worksheet.autoSizeColumn(idx++); + + for (int idx = 0; idx < headers.length; idx++) { + + if (headers[idx] != null || !mergeCells) { + String header = headers[idx]; + + Cell cell = row.createCell(idx); + cell.setCellValue(header); + cell.setCellStyle(style); + + if (mergeCells) { + int lookAhead = 1; + while (idx + lookAhead < headers.length && headers[idx + lookAhead] == null) lookAhead++; + + if (lookAhead > 1) { + var merged = new CellRangeAddress( + 0, + 0, + idx, + idx + lookAhead - 1 + ); + + RegionUtil.setBorderBottom(style.getBorderBottom(), merged, worksheet); + RegionUtil.setBorderTop(style.getBorderTop(), merged, worksheet); + RegionUtil.setBorderLeft(style.getBorderLeft(), merged, worksheet); + RegionUtil.setBorderRight(style.getBorderRight(), merged, worksheet); + RegionUtil.setBottomBorderColor(style.getBottomBorderColor(), merged, worksheet); + RegionUtil.setTopBorderColor(style.getTopBorderColor(), merged, worksheet); + RegionUtil.setLeftBorderColor(style.getLeftBorderColor(), merged, worksheet); + RegionUtil.setRightBorderColor(style.getRightBorderColor(), merged, worksheet); + + worksheet.addMergedRegion(merged); + } + } + + + } + + worksheet.autoSizeColumn(idx); } } + public void fixWidth(Sheet sheet, String[] headers, boolean respectMerged) { + + if (respectMerged) { + int idx = 0; + for (String header : headers) { + sheet.autoSizeColumn(idx, true); + sheet.setColumnWidth(idx, sheet.getColumnWidth(idx) + ADD_COLUMN_SIZE); + idx++; + } + } else + fixWidth(sheet, headers); + + } + + public void fixWidth(Sheet sheet, String[] headers) { int idx = 0; for (String header : headers) { sheet.autoSizeColumn(idx); - sheet.setColumnWidth(idx,sheet.getColumnWidth(idx)+ADD_COLUMN_SIZE); + sheet.setColumnWidth(idx, sheet.getColumnWidth(idx) + ADD_COLUMN_SIZE); idx++; } } public & HeaderProvider> void fixWidth(Sheet sheet, Class headers) { + Boolean usesOrdinals = null; + for (H header : EnumSet.allOf(headers)) { - sheet.autoSizeColumn(header.ordinal()); - sheet.setColumnWidth(header.ordinal(),sheet.getColumnWidth(header.ordinal())+ADD_COLUMN_SIZE); + if (usesOrdinals == null) usesOrdinals = header.useOrdinal(); + + if (usesOrdinals) { + sheet.autoSizeColumn(header.ordinal()); + sheet.setColumnWidth(header.ordinal(), sheet.getColumnWidth(header.ordinal()) + ADD_COLUMN_SIZE); + } else { + sheet.autoSizeColumn(header.getColumn()); + sheet.setColumnWidth(header.getColumn(), sheet.getColumnWidth(header.getColumn()) + ADD_COLUMN_SIZE); + } } } 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 2c9990f..863462f 100644 --- a/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java +++ b/src/main/java/de/avatic/lcc/service/report/ExcelReportingService.java @@ -1,215 +1,721 @@ 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; -import de.avatic.lcc.dto.report.ReportPremisesDTO; -import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; +import de.avatic.lcc.dto.report.ReportSectionDTO; +import de.avatic.lcc.model.bulk.header.ReportSummaryHeader; +import de.avatic.lcc.model.db.properties.SystemPropertyMappingId; +import de.avatic.lcc.repositories.MaterialRepository; +import de.avatic.lcc.repositories.properties.PropertyRepository; +import de.avatic.lcc.service.bulk.helper.CellStyleProvider; import de.avatic.lcc.service.bulk.helper.HeaderGenerator; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.RegionUtil; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.core.io.ByteArrayResource; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Stream; @Service public class ExcelReportingService { - private final ReportingService reportingService; - private final HeaderCellStyleProvider headerCellStyleProvider; - private final HeaderGenerator headerGenerator; + private static final Map rowSequence = new HashMap<>() { + { + put(0, new RowInfo(0, RowCaption.SUPPLIER, "Supplier", RowType.TITLE, true)); + put(1, new RowInfo(1, RowCaption.ADDRESS, "Address", RowType.SUB_TITLE, true)); - public ExcelReportingService(ReportingService reportingService, HeaderCellStyleProvider headerCellStyleProvider, HeaderGenerator headerGenerator) { + put(2, new RowInfo(2, RowCaption.SUMMARY, "Summary", RowType.HEADER, false)); + put(3, new RowInfo(3, RowCaption.SUMMARY_MEK_A, "MEK A", RowType.SPLIT_DATA, false)); + put(4, new RowInfo(4, RowCaption.SUMMARY_LOGISTICS_COST, "Logistics cost", RowType.SPLIT_DATA, false)); + put(5, new RowInfo(5, RowCaption.SUMMARY_MEK_B, "MEK B", RowType.SPLIT_DATA, false)); + + put(6, new RowInfo(6, RowCaption.WEIGHTED_COST_BREAKDOWN, "Weighted cost breakdown", RowType.HEADER, false)); + put(7, new RowInfo(7, RowCaption.BREAKDOWN_MEK_A, "MEK A", RowType.SPLIT_DATA, false)); + put(8, new RowInfo(8, RowCaption.BREAKDOWN_TRANSPORT, "Transport", RowType.SPLIT_DATA, false)); + put(9, new RowInfo(9, RowCaption.BREAKDOWN_HANDLING, "Handling", RowType.SPLIT_DATA, false)); + put(10, new RowInfo(10, RowCaption.BREAKDOWN_STORAGE, "Storage", RowType.SPLIT_DATA, false)); + put(11, new RowInfo(11, RowCaption.BREAKDOWN_REPACKAGING, "Repackaging", RowType.SPLIT_DATA, false)); + put(12, new RowInfo(12, RowCaption.BREAKDOWN_DISPOSAL, "Disposal", RowType.SPLIT_DATA, false)); + put(13, new RowInfo(13, RowCaption.BREAKDOWN_CAPITAL, "Capital", RowType.SPLIT_DATA, false)); + put(14, new RowInfo(14, RowCaption.BREAKDOWN_CUSTOM, "Custom", RowType.SPLIT_DATA, false)); + put(15, new RowInfo(15, RowCaption.BREAKDOWN_FCA_FEES, "FCA fees", RowType.SPLIT_DATA, false)); + put(16, new RowInfo(16, RowCaption.BREAKDOWN_TOTAL, "Total", RowType.SPLIT_DATA, false)); + + put(17, new RowInfo(17, RowCaption.FLUCTUATION, "Transport costs fluctuations", RowType.HEADER, false)); + put(18, new RowInfo(18, RowCaption.FLUCTUATION_CURRENT, "Current scenario", RowType.SPLIT_DATA, false)); + put(19, new RowInfo(19, RowCaption.FLUCTUATION_OPPORTUNITY, "Opportunity scenario", RowType.SPLIT_DATA, false)); + put(20, new RowInfo(20, RowCaption.FLUCTUATION_RISK, "Risk scenario", RowType.SPLIT_DATA, false)); + + put(21, new RowInfo(21, RowCaption.ASSUMPTIONS, "Assumptions", RowType.HEADER, false)); + put(22, new RowInfo(22, RowCaption.ASSUMPTIONS_MATERIAL, "Material", RowType.SUB_HEADER, false)); + put(23, new RowInfo(23, RowCaption.ASSUMPTIONS_PART_NUMBER, "Part number", RowType.DATA, false)); + put(24, new RowInfo(24, RowCaption.ASSUMPTIONS_HS_CODE, "HS code", RowType.DATA, false)); + put(25, new RowInfo(25, RowCaption.ASSUMPTIONS_TARIFF_RATE, "Tariff rate", RowType.DATA, false)); + put(26, new RowInfo(26, RowCaption.ASSUMPTIONS_OVERSEA_SHARE, "Oversea share", RowType.DATA, false)); + put(27, new RowInfo(27, RowCaption.ASSUMPTIONS_AIR_FREIGHT_SHARE, "Airfreight share", RowType.DATA, false)); + put(28, new RowInfo(28, RowCaption.ASSUMPTIONS_SAFETY_STOCK, "Safety stock [working days]", RowType.DATA, false)); + + put(29, new RowInfo(29, RowCaption.HANDLING_UNIT, "Handling unit", RowType.SUB_HEADER, false)); + put(30, new RowInfo(30, RowCaption.HANDLING_UNIT_LENGTH, "Length", RowType.DATA, false)); + put(31, new RowInfo(31, RowCaption.HANDLING_UNIT_WIDTH, "Width", RowType.DATA, false)); + put(32, new RowInfo(32, RowCaption.HANDLING_UNIT_HEIGHT, "Height", RowType.DATA, false)); + put(33, new RowInfo(33, RowCaption.HANDLING_UNIT_DIMENSION_UNIT, "Unit", RowType.DATA, false)); + put(34, new RowInfo(34, RowCaption.HANDLING_UNIT_WEIGHT, "Weight", RowType.DATA, false)); + put(35, new RowInfo(35, RowCaption.HANDLING_UNIT_DIMENSION_WEIGHT, "Unit", RowType.DATA, false)); + put(36, new RowInfo(36, RowCaption.HANDLING_UNIT_PIECES, "Pieces per HU", RowType.DATA, false)); + put(37, new RowInfo(37, RowCaption.HANDLING_UNIT_MIXED, "Mixed transport", RowType.DATA, false)); + + } + }; + private static final Map destinationRowSequence = new HashMap<>() { + { + put(0, new RowInfo(0, RowCaption.DESTINATION, "Destination", RowType.HEADER, true)); + + put(1, new RowInfo(1, RowCaption.DESTINATION_GENERAL, "General", RowType.SUB_HEADER, false)); + + put(2, new RowInfo(2, RowCaption.DESTINATION_ROUTE, "Route", RowType.DATA, false)); + put(3, new RowInfo(3, RowCaption.DESTINATION_ANNUAL_QUANTITY, "Annual quantity", RowType.DATA, false)); + put(4, new RowInfo(4, RowCaption.DESTINATION_TRANSPORT_TIME, "Transit time [days]", RowType.DATA, false)); + + put(5, new RowInfo(5, RowCaption.DESTINATION_CONTAINER, "Container calculation", RowType.SUB_HEADER, false)); + put(6, new RowInfo(6, RowCaption.DESTINATION_CONTAINER_LAYER, "Stacked layers", RowType.DATA, false)); + put(7, new RowInfo(7, RowCaption.DESTINATION_CONTAINER_UNIT_COUNT, "Container unit count", RowType.DATA, false)); + put(8, new RowInfo(8, RowCaption.DESTINATION_CONTAINER_TYPE, "Container type", RowType.DATA, false)); + put(9, new RowInfo(9, RowCaption.DESTINATION_CONTAINER_LIMIT, "Limiting factor", RowType.DATA, false)); + } + }; + private final ReportingService reportingService; + private final CellStyleProvider cellStyleProvider; + private final HeaderGenerator headerGenerator; + private final MaterialRepository materialRepository; + private final PropertyRepository propertyRepository; + + public ExcelReportingService(ReportingService reportingService, CellStyleProvider cellStyleProvider, HeaderGenerator headerGenerator, MaterialRepository materialRepository, PropertyRepository propertyRepository) { this.reportingService = reportingService; - this.headerCellStyleProvider = headerCellStyleProvider; + this.cellStyleProvider = cellStyleProvider; this.headerGenerator = headerGenerator; + this.materialRepository = materialRepository; + this.propertyRepository = propertyRepository; } - public ByteArrayResource generateExcelReport(Integer materialId, List nodeIds, List userNodeIds ) { + public ByteArrayResource generateExcelReport(List materialIds, List nodeIds, List userNodeIds) { + var reportingProperty = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.REPORTING).orElseThrow(); + boolean includeAirfreight = reportingProperty.getCurrentValue().equals("MEK_C"); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - var reports = reportingService.getReport(materialId, nodeIds, userNodeIds); - Workbook workbook = new XSSFWorkbook(); - Sheet sheet = workbook.createSheet("report"); - - CellStyle headerStyle = headerCellStyleProvider.createHeaderCellStyle(workbook); - - ArrayList headers = new ArrayList<>(); - headers.add(""); - headers.addAll(reports.stream().map(ReportDTO::getSupplier).map(NodeDTO::getName).toList()); - - headerGenerator.generateHeader(sheet, headers.toArray(String[]::new), headerStyle); - - List flatterers = reports.stream().map(ReportFlattener::new).toList(); + CellStyle headerStyle = cellStyleProvider.createHeaderCellStyle(workbook); + var styles = cellStyleProvider.createCellStyles(workbook); - while(true) { - boolean hasData = false; - boolean headerWritten = false; - - var row = sheet.createRow(sheet.getLastRowNum() + 1); - int cellIdx = 1 /* 0 is the header column */; - for(ReportFlattener flattener : flatterers) { - - Cell cell = row.createCell(cellIdx); - - - if(flattener.hasData(row.getRowNum())) { - if(!headerWritten) { - row.createCell(0).setCellValue(flattener.getHeader(row.getRowNum())); - headerWritten = true; - } - cell.setCellValue(flattener.getCell(row.getRowNum())); - hasData = true; - } - - cellIdx++; - } - - if(!hasData) break; - } - - headerGenerator.fixWidth(sheet, headers.toArray(String[]::new)); + fillSummarySheet(workbook, headerStyle, materialIds, nodeIds, userNodeIds, styles); + materialIds.forEach(id -> fillExcelSheet(workbook, headerStyle, id, nodeIds, userNodeIds, styles, includeAirfreight)); // Return the Excel file as an InputStreamSource workbook.write(outputStream); return new ByteArrayResource(outputStream.toByteArray()); - } catch ( - IOException e) { + } catch (IOException e) { throw new RuntimeException("Failed to generate template", e); } } - private static class ReportFlattener { + private void fillSummarySheet(Workbook workbook, CellStyle headerStyle, List materialIds, List nodeIds, List userNodeIds, Map> styles) { + Sheet sheet = workbook.createSheet("Summary"); - private static final String SUPPLIER_NAME = "Supplier"; - private static final String SUPPLIER_ADDRESS = "Address"; + int row = 1; - private static final String DESTINATION_NAME = "Destination"; - private static final String DESTINATION_ADDRESS = "Address"; - - private static final String DESTINATION_QUANTITY = "Annual quantity"; - private static final String DESTINATION_HS_CODE = "HS code"; - private static final String DESTINATION_TARIFF_RATE = "Tariff rate"; - private static final String DESTINATION_OVERSHARE = "Oversea share"; - private static final String DESTINATION_AIR_FREIGHT_SHARE = "Air freight share"; - private static final String DESTINATION_TRANSPORT_TIME = "Transport time"; - private static final String DESTINATION_SAFETY_STOCK = "Safety stock"; - - private static final String DESTINATION_WIDTH = "HU Width"; - private static final String DESTINATION_HEIGHT = "HU Height"; - private static final String DESTINATION_LENGTH = "HU Length"; - private static final String DESTINATION_WEIGHT = "HU Weight"; - private static final String DESTINATION_HU_UNIT_COUNT = "HU Unit count"; - - private static final String DESTINATION_WEIGHT_UNIT = "HU Weight unit"; - private static final String DESTINATION_DIMENSION_UNIT = "HU Unit"; - - private static final String DESTINATION_CONTAINER_LAYER = "Container layers"; - private static final String DESTINATION_CONTAINER_UNIT_COUNT = "Container unit count"; - private static final String DESTINATION_CONTAINER_UTILIZATION = "Container utilization"; - private static final String DESTINATION_CONTAINER_TYPE = "Container type"; - private static final String DESTINATION_CONTAINER_WEIGHT_EXCEEDED = "Container weight exceeded"; - private static final String DESTINATION_CONTAINER_RATE = "Container rate"; - private static final String DESTINATION_MIXED = "Mixed"; - - - private final ReportDTO report; - private final List data = new ArrayList<>(); - private final List dataHeader = new ArrayList<>(); - - public ReportFlattener(ReportDTO report) { - this.report = report; - flatten(); + for (Integer materialId : materialIds) { + for (var mapper : reportingService.getReport(materialId, nodeIds, userNodeIds).stream().map(report -> new SummaryMapper(report, styles)).toList()) { + mapper.map(sheet, row++); + } } - private void flatten() { + headerGenerator.generateHeader(sheet, ReportSummaryHeader.class, headerStyle); + headerGenerator.fixWidth(sheet, ReportSummaryHeader.class); - addData(SUPPLIER_NAME, report.getSupplier().getName()); - addData(SUPPLIER_ADDRESS, report.getSupplier().getAddress()); + } - // TODO: hardcoded (otherwise values wont match - report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName))); - report.getOverview().keySet().forEach(riskName -> addData(riskName, report.getOverview().get(riskName))); + private void fillExcelSheet(Workbook workbook, CellStyle headerStyle, Integer materialId, List nodeIds, List userNodeIds, Map> styles, boolean includeAirfreight) { + var material = materialRepository.getById(materialId).orElseThrow(); + var reports = reportingService.getReport(materialId, nodeIds, userNodeIds); - commonPremisses(report.getPremises()); + Sheet sheet = workbook.createSheet(material.getPartNumber()); - report.getDestinations().forEach(this::flattenDestination); + ArrayList headers = new ArrayList<>(); + headers.add(""); + headers.addAll(reports.stream().flatMap(report -> Stream.of(report.getSupplier().getName(), null)).toList()); + + headerGenerator.generateHeader(sheet, headers.toArray(String[]::new), headerStyle, true); + List destinations = collectDestinations(reports); + + var row = 1; + var mapper = new ReportRowMapper(reports.stream().map(report -> new ReportMapper(report, destinations, styles, includeAirfreight)).toList(), destinations, styles, rowSequence, destinationRowSequence); + + boolean finished = false; + while (!finished) { + finished = !mapper.map(sheet, row++); } - private void commonPremisses(ReportPremisesDTO premises) { - addData(DESTINATION_HS_CODE, premises.getHsCode()); - addData(DESTINATION_TARIFF_RATE, premises.getTariffRate().toString()); + headerGenerator.fixWidth(sheet, headers.toArray(String[]::new), true); - addData(DESTINATION_WIDTH, premises.getWidth().toString()); - addData(DESTINATION_HEIGHT, premises.getHeight().toString()); - addData(DESTINATION_LENGTH, premises.getLength().toString()); - addData(DESTINATION_DIMENSION_UNIT, premises.getDimensionUnit().toString()); - addData(DESTINATION_WEIGHT, premises.getWeight().toString()); - addData(DESTINATION_WEIGHT_UNIT, premises.getWeightUnit().toString()); - addData(DESTINATION_HU_UNIT_COUNT, premises.getHuUnitCount().toString()); + } + + private List collectDestinations(List reports) { + Map destinations = new HashMap<>(); + + reports.forEach(r -> r.getDestinations().forEach(d -> { + if (!destinations.containsKey(d.getDestination().getId())) + destinations.put(d.getDestination().getId(), d.getDestination()); + })); + + return destinations.values().stream().toList(); + } + + private enum RowCaption { + SUPPLIER, ADDRESS, SUMMARY, SUMMARY_MEK_A, SUMMARY_LOGISTICS_COST, SUMMARY_MEK_B, WEIGHTED_COST_BREAKDOWN, BREAKDOWN_MEK_A, BREAKDOWN_TRANSPORT, BREAKDOWN_HANDLING, BREAKDOWN_STORAGE, BREAKDOWN_REPACKAGING, BREAKDOWN_DISPOSAL, BREAKDOWN_CAPITAL, BREAKDOWN_CUSTOM, BREAKDOWN_FCA_FEES, BREAKDOWN_TOTAL, FLUCTUATION, FLUCTUATION_CURRENT, FLUCTUATION_OPPORTUNITY, FLUCTUATION_RISK, ASSUMPTIONS, ASSUMPTIONS_MATERIAL, ASSUMPTIONS_PART_NUMBER, ASSUMPTIONS_HS_CODE, ASSUMPTIONS_TARIFF_RATE, ASSUMPTIONS_OVERSEA_SHARE, ASSUMPTIONS_AIR_FREIGHT_SHARE, ASSUMPTIONS_SAFETY_STOCK, HANDLING_UNIT, HANDLING_UNIT_LENGTH, HANDLING_UNIT_WIDTH, HANDLING_UNIT_HEIGHT, HANDLING_UNIT_DIMENSION_UNIT, HANDLING_UNIT_WEIGHT, HANDLING_UNIT_DIMENSION_WEIGHT, HANDLING_UNIT_PIECES, HANDLING_UNIT_MIXED, DESTINATION, DESTINATION_ROUTE, DESTINATION_ANNUAL_QUANTITY, DESTINATION_TRANSPORT_TIME, DESTINATION_CONTAINER, DESTINATION_CONTAINER_LAYER, DESTINATION_CONTAINER_UNIT_COUNT, DESTINATION_CONTAINER_TYPE, DESTINATION_CONTAINER_LIMIT, DESTINATION_GENERAL; + } + + + private enum RowType { + TITLE, SUB_TITLE, HEADER, SUB_HEADER, DATA, SPLIT_DATA + } + + private record RowInfo(int rowIdx, RowCaption captionType, String caption, RowType type, boolean hideCaption) { + } + + private record ReportRowMapper(List mapper, List destinations, + Map> styles, + Map rowSequence, Map destinationRowSequence) { + + + public boolean map(Sheet sheet, int rowIndex) { + + RowInfo info = getRowInfo(rowIndex); + int destinationIdx = getDestinationIdx(rowIndex); + + Row row = sheet.createRow(rowIndex); + + Cell cell = row.createCell(0); + + if (info.captionType == RowCaption.DESTINATION) { + cell.setCellValue(destinations.get(destinationIdx).getName()); + } else { + cell.setCellValue(info.hideCaption() ? "" : info.caption()); + } + + cell.setCellStyle(getStyle(info)); + + int columnIdx = 1; + for (var curMapper : mapper) { + curMapper.map(sheet, row, rowIndex, columnIdx, info, destinationIdx); + columnIdx += 2; + } + + return rowIndex < getLastIndex(); } - 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()); - - addData(DESTINATION_QUANTITY, destination.getAnnualQuantity().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()); - addData(DESTINATION_SAFETY_STOCK, destination.getSafetyStock().toString()); - - - addData(DESTINATION_CONTAINER_LAYER, !hasMainRun ? "-" : destination.getLayer().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()); - + private int getLastIndex() { + return rowSequence.size() + (destinations.size() * destinationRowSequence.size()) - 1; } - private void addData(String header, String data) { - this.dataHeader.add(header); - this.data.add(data); + private CellStyle getStyle(RowInfo info) { + return switch (info.type) { + case TITLE -> + this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.TITLE); + case SUB_TITLE, DATA, SPLIT_DATA -> + this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + case HEADER -> + this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.HEADER); + case SUB_HEADER -> + this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.SUB_HEADER); + }; } - private void addData(String header, ReportEntryDTO data) { - this.dataHeader.add(header); - this.data.add(data.getTotal().toString()); // + " (" + data.getPercentage().doubleValue()*100 + "%)"); + private int getDestinationIdx(int rowIndex) { + + if (rowIndex < rowSequence.size()) return -1; + + return (rowIndex - rowSequence.size()) / destinationRowSequence.size(); } - public String getCell(int rowIdx) { - return data.get(rowIdx); - } + private RowInfo getRowInfo(int rowIndex) { + if (rowIndex < rowSequence.size()) return rowSequence.get(rowIndex); - public String getHeader(int rowIdx) { - return dataHeader.get(rowIdx); - } - - public boolean hasData(int index) { - return data.size() > index; + return destinationRowSequence.get((rowIndex - rowSequence.size()) % destinationRowSequence.size()); } } + + private record ReportMapper(ReportDTO report, List destinations, + Map> styles, + boolean includeAirfreight) { + + public void map(Sheet sheet, Row row, int rowIdx, int columnIdx, RowInfo info, int destinationIdx) { + + + boolean mergeCells = false; + CellStyle selectedStyle = null; + + /* ********************************** + * Cell 1 + * **********************************/ + + Cell cell = row.createCell(columnIdx); + + switch (info.type) { + case TITLE -> { + cell.setCellValue(report.getSupplier().getName()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.TITLE); + mergeCells = true; + } + case SUB_TITLE -> { + cell.setCellValue(report.getSupplier().getAddress()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + mergeCells = true; + } + case HEADER -> { + cell.setCellValue(""); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.HEADER); + mergeCells = true; + } + case SUB_HEADER -> { + cell.setCellValue(""); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.SUB_HEADER); + mergeCells = true; + } + case DATA -> { + selectedStyle = setCellValue(cell, info, destinationIdx, false); + mergeCells = true; + } + case SPLIT_DATA -> { + selectedStyle = setCellValue(cell, info, destinationIdx, false); + } + + } + + if (selectedStyle != null) { + cell.setCellStyle(selectedStyle); + } + + /* ********************************** + * Cell 2 + * **********************************/ + + if (mergeCells) { + var merged = new CellRangeAddress(rowIdx, rowIdx, columnIdx, columnIdx + 1); + + if (selectedStyle != null) { + RegionUtil.setBorderBottom(selectedStyle.getBorderBottom(), merged, sheet); + RegionUtil.setBorderTop(selectedStyle.getBorderTop(), merged, sheet); + RegionUtil.setBorderLeft(selectedStyle.getBorderLeft(), merged, sheet); + RegionUtil.setBorderRight(selectedStyle.getBorderRight(), merged, sheet); + RegionUtil.setBottomBorderColor(selectedStyle.getBottomBorderColor(), merged, sheet); + RegionUtil.setTopBorderColor(selectedStyle.getTopBorderColor(), merged, sheet); + RegionUtil.setLeftBorderColor(selectedStyle.getLeftBorderColor(), merged, sheet); + RegionUtil.setRightBorderColor(selectedStyle.getRightBorderColor(), merged, sheet); + } + sheet.addMergedRegion(merged); + } else { + Cell cell2nd = row.createCell(columnIdx + 1); + var selectedStyle2nCell = setCellValue(cell2nd, info, destinationIdx, true); + if (selectedStyle2nCell != null) { + cell2nd.setCellStyle(selectedStyle2nCell); + } + } + + } + + private CellStyle setCellValue(Cell cell, RowInfo info, int destinationIdx, boolean secondColumn) { + + CellStyle selectedStyle = null; + + switch (info.captionType) { + case SUPPLIER -> { + cell.setCellValue(report.getSupplier().getName()); + } + case ADDRESS -> { + cell.setCellValue(report.getSupplier().getAddress()); + } + case SUMMARY, WEIGHTED_COST_BREAKDOWN, FLUCTUATION, ASSUMPTIONS, ASSUMPTIONS_MATERIAL, HANDLING_UNIT, + DESTINATION, DESTINATION_CONTAINER, DESTINATION_GENERAL -> { + cell.setCellValue(""); + } + case SUMMARY_MEK_A -> { + if (!secondColumn) { + cell.setCellValue(report.getOverview().get("mek_a").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getOverview().get("mek_a").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case SUMMARY_LOGISTICS_COST -> { + if (!secondColumn) { + cell.setCellValue(report.getOverview().get("logistics").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getOverview().get("logistics").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case SUMMARY_MEK_B, FLUCTUATION_CURRENT -> { + if (!secondColumn) { + cell.setCellValue(report.getOverview().get("mek_b").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getOverview().get("mek_b").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_MEK_A -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("mek_a").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("mek_a").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_TRANSPORT -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("transport").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("transport").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_HANDLING -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("handling").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("handling").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_STORAGE -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("storage").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("storage").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_REPACKAGING -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("repacking").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("repacking").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_DISPOSAL -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("disposal").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("disposal").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_CAPITAL -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("capital").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("capital").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_CUSTOM -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("custom").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("custom").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_FCA_FEES -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("fca_fees").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("fca_fees").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case BREAKDOWN_TOTAL -> { + if (!secondColumn) { + cell.setCellValue(report.getCost().get("total").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getCost().get("total").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case FLUCTUATION_OPPORTUNITY -> { + if (!secondColumn) { + cell.setCellValue(report.getOverview().get("opportunity_scenario").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getOverview().get("opportunity_scenario").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case FLUCTUATION_RISK -> { + if (!secondColumn) { + cell.setCellValue(report.getOverview().get("risk_scenario").getTotal().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.CURRENCY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } else { + cell.setCellValue(report.getOverview().get("risk_scenario").getPercentage().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case ASSUMPTIONS_PART_NUMBER -> { + cell.setCellValue(report.getMaterial().getPartNumber()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case ASSUMPTIONS_HS_CODE -> { + cell.setCellValue(report.getPremises().getHsCode()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case ASSUMPTIONS_TARIFF_RATE -> { + cell.setCellValue(report.getPremises().getTariffRate().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case ASSUMPTIONS_OVERSEA_SHARE -> { + cell.setCellValue(report.getPremises().getOverseaShare().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case ASSUMPTIONS_AIR_FREIGHT_SHARE -> { + if (includeAirfreight) { + cell.setCellValue(report.getPremises().getAirFreightShare().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.PERCENTAGE).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } else { + cell.setCellValue("-"); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + } + case ASSUMPTIONS_SAFETY_STOCK -> { + cell.setCellValue(report.getPremises().getSafetyStock().doubleValue()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_LENGTH -> { + cell.setCellValue(report.getPremises().getLength()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_WIDTH -> { + cell.setCellValue(report.getPremises().getWidth()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_HEIGHT -> { + cell.setCellValue(report.getPremises().getHeight()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_DIMENSION_UNIT -> { + cell.setCellValue(report.getPremises().getDimensionUnit().getDisplayedName()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_WEIGHT -> { + cell.setCellValue(report.getPremises().getWeight()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_DIMENSION_WEIGHT -> { + cell.setCellValue(report.getPremises().getWeightUnit().getDisplayedName()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_PIECES -> { + cell.setCellValue(report.getPremises().getHuUnitCount()); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case HANDLING_UNIT_MIXED -> { + cell.setCellValue(report.getPremises().getMixable() ? "Yes" : "No"); + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case DESTINATION_ROUTE -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(getRoute(dest.getSections())); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case DESTINATION_ANNUAL_QUANTITY -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getAnnualQuantity()); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } + case DESTINATION_TRANSPORT_TIME -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getTransportTime()); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + } + case DESTINATION_CONTAINER_LAYER -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getLayer()); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } + case DESTINATION_CONTAINER_UNIT_COUNT -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getUnitCount().intValue() * report.getPremises().getHuUnitCount()); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } + case DESTINATION_CONTAINER_TYPE -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getType().getDescription()); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } + case DESTINATION_CONTAINER_LIMIT -> { + var dest = getDestination(destinationIdx); + + if (dest == null) { + cell.setCellValue(""); + } else { + cell.setCellValue(dest.getWeightExceeded() ? "Weight" : "Volume"); + } + selectedStyle = this.styles.get(CellStyleProvider.TextFormat.TEXT_ONLY).get(CellStyleProvider.PredefinedColors.NEUTRAL); + + } + } + + return selectedStyle; + } + + private String getRoute(List sections) { + + if (sections == null || sections.isEmpty()) + return ""; + + var firstNode = sections.getFirst().getFromNode(); + + StringBuilder route = new StringBuilder(firstNode.getExternalMappingId() == null ? shortened(firstNode.getName()) : firstNode.getExternalMappingId()); + + sections.forEach(s -> { + route.append(" > "); + route.append(s.getToNode().getExternalMappingId()); + }); + + return route.toString(); + } + + private String shortened(String name) { + if (name == null || name.length() < 10) + return name; + + return name.substring(0, 7) + "..."; + } + + private ReportDestinationDTO getDestination(int destinationIdx) { + if (destinationIdx == -1 || destinations.size() <= destinationIdx) + return null; + + var node = destinations.get(destinationIdx); + + if (node == null) return null; + + return report.getDestinations().stream().filter(d -> Objects.equals(d.getDestination().getId(), node.getId())).findFirst().orElse(null); + + } + + } + + private record SummaryMapper(ReportDTO report, + Map> styles) { + + public void map(Sheet sheet, int rowIdx) { + Row row = sheet.createRow(rowIdx); + + createStringCell(row, ReportSummaryHeader.MATERIAL, report.getMaterial().getPartNumber()); + createStringCell(row, ReportSummaryHeader.SUPPLIER, report.getSupplier().getName()); + + var currencyStyle = styles.get(CellStyleProvider.TextFormat.CURRENCY); + var percentStyle = styles.get(CellStyleProvider.TextFormat.PERCENTAGE); + + createSplitCell(row, ReportSummaryHeader.MEK_A, report.getOverview().get("mek_a"), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL), percentStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createSplitCell(row, ReportSummaryHeader.LOGISTICS_COST, report.getOverview().get("logistics"), currencyStyle.get(CellStyleProvider.PredefinedColors.LIGHT_BLUE_1), percentStyle.get(CellStyleProvider.PredefinedColors.LIGHT_BLUE_2)); + createSplitCell(row, ReportSummaryHeader.MEK_B, report.getOverview().get("mek_b"), currencyStyle.get(CellStyleProvider.PredefinedColors.GREEN_1), percentStyle.get(CellStyleProvider.PredefinedColors.GREEN_2)); + + createNumberCell(row, ReportSummaryHeader.TRANSPORT, report.getCost().get("transport").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.HANDLING, report.getCost().get("handling").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.STORAGE, report.getCost().get("storage").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.REPACKAGING, report.getCost().get("repacking").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.DISPOSAL, report.getCost().get("disposal").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.CAPITAL, report.getCost().get("capital").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.CUSTOM, report.getCost().get("custom").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + createNumberCell(row, ReportSummaryHeader.FCA_FEE, report.getCost().get("fca_fees").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + + if (report.getCost().containsKey("air_freight_cost")) + createNumberCell(row, ReportSummaryHeader.AIR_FREIGHT, report.getCost().get("air_freight_cost").getTotal(), currencyStyle.get(CellStyleProvider.PredefinedColors.NEUTRAL)); + else createStringCell(row, ReportSummaryHeader.AIR_FREIGHT, "-"); + + } + + private void createSplitCell(Row row, ReportSummaryHeader header, ReportEntryDTO value, CellStyle cellStyleHighlight1, CellStyle cellStyleHighlight2) { + Cell cell = row.createCell(header.getColumn()); + cell.setCellValue(value.getTotal().doubleValue()); + cell.setCellStyle(cellStyleHighlight1); + + Cell percentageCell = row.createCell(header.getColumn() + 1); + percentageCell.setCellValue(value.getPercentage().doubleValue()); + percentageCell.setCellStyle(cellStyleHighlight2); + } + + private void createStringCell(Row row, ReportSummaryHeader header, String value) { + Cell cell = row.createCell(header.getColumn()); + cell.setCellValue(value); + } + + private void createNumberCell(Row row, ReportSummaryHeader header, Number value, CellStyle style) { + Cell cell = row.createCell(header.getColumn()); + cell.setCellValue(value.doubleValue()); + cell.setCellStyle(style); + } + + + } } 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 3f23b41..08b45fc 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 @@ -98,7 +98,7 @@ public class ReportTransformer { reportDTO.setStartDate(period.startDate); reportDTO.setEndDate(period.endDate); - reportDTO.setPremises(getPremisesDTO(job, premise)); + reportDTO.setPremises(getPremisesDTO(job, premise, destinations, includeAirfreight)); if (!destinations.isEmpty()) { @@ -171,9 +171,11 @@ public class ReportTransformer { return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalD2D, totalCost); } - private ReportPremisesDTO getPremisesDTO(CalculationJob job, Premise premise) { + private ReportPremisesDTO getPremisesDTO(CalculationJob job, Premise premise, List destinations, boolean includeAirfreight) { ReportPremisesDTO premisesDTO = new ReportPremisesDTO(); + var destination = destinations.getFirst(); + var dimensionUnit = premise.getHuDisplayedDimensionUnit(); var weightUnit = premise.getHuDisplayedWeightUnit(); @@ -188,6 +190,14 @@ public class ReportTransformer { premisesDTO.setHsCode(premise.getHsCode()); premisesDTO.setTariffRate(premise.getTariffRate()); + if (includeAirfreight) + premisesDTO.setAirFreightShare(destination.getAirFreightShare().doubleValue()); + + premisesDTO.setOverseaShare(premise.getOverseaShare()); + + premisesDTO.setSafetyStock(destination.getSafetyStockInDays().intValue()); + + premisesDTO.setMixable(premise.getHuMixable()); return premisesDTO; } @@ -217,15 +227,8 @@ public class ReportTransformer { destinationDTO.setLayer(destination.getLayerCount()); - destinationDTO.setOverseaShare(premise.getOverseaShare().doubleValue()); - destinationDTO.setSafetyStock(destination.getSafetyStockInDays().intValue()); destinationDTO.setTransportTime(destination.getTotalTransitTime().doubleValue()); - if (includeAirfreight) - destinationDTO.setAirFreightShare(destination.getAirFreightShare().doubleValue()); - - - destinationDTO.setMixed(premise.getHuMixable()); destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate()); destinationDTO.setType(destination.getContainerType()); destinationDTO.setUtilization(destination.getContainerUtilization()); -- 2.45.3 From 1be35b5a8dd157b3cc0e28c8f41573b8b59e529e Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 16 Dec 2025 22:16:03 +0100 Subject: [PATCH 03/88] Bugfix: shipping frequency custom calculation. Stacking in container calcualtion --- .../calculation/execution/CalculationExecutionService.java | 4 ++-- .../execution/steps/ContainerCalculationService.java | 4 ++-- .../execution/steps/CustomCostCalculationService.java | 4 ++-- .../execution/steps/RouteSectionCostCalculationService.java | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) 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 b24a44b..cd85c23 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 @@ -142,7 +142,7 @@ public class CalculationExecutionService { BigDecimal leadTime = null; if (destination.getD2d()) { - var containerCalculation = containerCalculationService.doCalculation(setId, premiseToHuService.createHuFromPremise(premise), ContainerType.FEU, premise.getHuMixable()); + var containerCalculation = containerCalculationService.doCalculation(setId, premiseToHuService.createHuFromPremise(premise), ContainerType.FEU, premise.getHuMixable(), premise.getHuStackable()); sections = List.of(new SectionInfo(null, routeSectionCostCalculationService.doD2dCalculation(setId, periodId, premise, destination, containerCalculation), containerCalculation)); leadTime = BigDecimal.valueOf(destination.getLeadTimeD2d()); usedContainerType = ContainerType.FEU; @@ -246,7 +246,7 @@ public class CalculationExecutionService { // Get container calculation for (var containerType : ContainerType.values()) { - containerCalculation.put(containerType, containerCalculationService.doCalculation(setId, hu, containerType, premise.getHuMixable())); + containerCalculation.put(containerType, containerCalculationService.doCalculation(setId, hu, containerType, premise.getHuMixable(), premise.getHuStackable())); } for (var containerType : ContainerType.values()) { 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 9411e31..32da301 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 @@ -49,7 +49,7 @@ public class ContainerCalculationService { * @param containerType The type of container to be loaded * @return ContainerCalculationResult containing loading pattern and capacity information */ - public ContainerCalculationResult doCalculation(Integer setId, PackagingDimension hu, ContainerType containerType, boolean mixable) { + public ContainerCalculationResult doCalculation(Integer setId, PackagingDimension hu, ContainerType containerType, boolean mixable, boolean stackable) { var weightInKg = BigDecimal.valueOf(WeightUnit.KG.convertFromG(hu.getWeight())); var maxContainerLoad = BigDecimal.valueOf(getMaxContainerLoad(containerType, setId)); @@ -60,7 +60,7 @@ public class ContainerCalculationService { var solutionHorizontal = solveLayer(SolutionType.HORIZONTAL, dimensions, containerType.getLength(), containerType.getWidth()); var solutionVertical = solveLayer(SolutionType.VERTICAL, dimensions, containerType.getWidth(), containerType.getLength()); var bestSolution = solutionHorizontal.getTotal() < solutionVertical.getTotal() ? solutionVertical : solutionHorizontal; - int layers = mixable ? getLayerCount(dimensions, containerType) : 1; + int layers = stackable ? getLayerCount(dimensions, containerType) : 1; 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)*layers) > maxUnitByWeight, containerType, dimensions, maxContainerLoad.intValueExact()); diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java index 55bfd61..2dd079c 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java @@ -62,7 +62,7 @@ public class CustomCostCalculationService { var transportationRiskCost = relevantSections.stream().map(s -> s.result().getAnnualRiskCost()).reduce(BigDecimal.ZERO, BigDecimal::add); - double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(relevantSections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue(); + double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(premise.getHuUnitCount()),0, RoundingMode.CEILING).doubleValue(); return getCustomCalculationResult(setId, premise, destination, getContainerShare(premise, relevantSections.getFirst().containerResult()), huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost); } @@ -87,7 +87,7 @@ public class CustomCostCalculationService { var customValue = materialCost.add(fcaFee).add(transportationCost); var customDuties = customValue.multiply(tariffRate); - var annualCustomFee = BigDecimal.valueOf(shippingFrequency).multiply(BigDecimal.valueOf(customFee)).multiply(containerShare); + var annualCustomFee = BigDecimal.valueOf(shippingFrequency).multiply(BigDecimal.valueOf(customFee)); var annualCost = customDuties.add(annualCustomFee); var customRiskValue = materialCost.add(fcaFee).add(transportationRiskCost); 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 daf571a..cfe5b39 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 @@ -229,6 +229,8 @@ public class RouteSectionCostCalculationService { volumePrice = cbmRate.divide(totalVolumeUtilization, 10, RoundingMode.HALF_UP); weightPrice = weightRate.divide(totalWeightUtilization, 10, RoundingMode.HALF_UP); utilization = weightExceeded ? totalWeightUtilization : totalVolumeUtilization; + //TODO: wenn shippingfreq > annual hu -> shippingfreq * containerprice. + // gleiches für containercalculation * shippingfreq < annual hu ammount. } return new PriceCalculationResult(volumePrice, weightPrice, utilization); -- 2.45.3 From 9ac3cb7815761df6d02848b5e47798171795ceea Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 17 Dec 2025 09:42:19 +0100 Subject: [PATCH 04/88] Bugfix: if hu amount is less than min shipping frequency, fix total utilization accordingly --- .../steps/CustomCostCalculationService.java | 15 ++------ .../RouteSectionCostCalculationService.java | 34 +++++++++++++++---- .../ShippingFrequencyCalculationService.java | 8 ++--- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java index 2dd079c..1e2686d 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java @@ -37,17 +37,6 @@ public class CustomCostCalculationService { this.shippingFrequencyCalculationService = shippingFrequencyCalculationService; } - private BigDecimal getContainerShare(Premise premise, ContainerCalculationResult containerCalculationResult) { - var weightExceeded = containerCalculationResult.isWeightExceeded(); - var mixable = premise.getHuMixable(); - - if (mixable) { - return BigDecimal.valueOf(weightExceeded ? containerCalculationResult.getHuUtilizationByWeight() : containerCalculationResult.getHuUtilizationByVolume()); - } else { - return BigDecimal.ONE.divide(BigDecimal.valueOf(containerCalculationResult.getHuUnitCount()), 10, RoundingMode.HALF_UP); - } - } - public CustomResult doCalculation(Integer setId, Premise premise, Destination destination, List sections) { var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, setId, destination.getCountryId()).orElseThrow(); @@ -64,13 +53,13 @@ public class CustomCostCalculationService { double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(premise.getHuUnitCount()),0, RoundingMode.CEILING).doubleValue(); - return getCustomCalculationResult(setId, premise, destination, getContainerShare(premise, relevantSections.getFirst().containerResult()), huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost); + return getCustomCalculationResult(setId, premise, destination, huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost); } return CustomResult.EMPTY; } - private CustomResult getCustomCalculationResult(Integer setId, Premise premise, Destination destination, BigDecimal containerShare, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) { + private CustomResult getCustomCalculationResult(Integer setId, Premise premise, Destination destination, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) { var shippingFrequency = shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount); var customFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CUSTOM_FEE, setId).orElseThrow().getCurrentValue()); 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 cfe5b39..db414e9 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 @@ -40,8 +40,9 @@ public class RouteSectionCostCalculationService { private final ChangeRiskFactorCalculationService changeRiskFactorCalculationService; private final NodeRepository nodeRepository; private final UserNodeRepository userNodeRepository; + private final ShippingFrequencyCalculationService shippingFrequencyCalculationService; - public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository) { + public RouteSectionCostCalculationService(ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository, ChangeRiskFactorCalculationService changeRiskFactorCalculationService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, ShippingFrequencyCalculationService shippingFrequencyCalculationService) { this.containerRateRepository = containerRateRepository; this.matrixRateRepository = matrixRateRepository; this.routeNodeRepository = routeNodeRepository; @@ -50,6 +51,7 @@ public class RouteSectionCostCalculationService { this.changeRiskFactorCalculationService = changeRiskFactorCalculationService; this.nodeRepository = nodeRepository; this.userNodeRepository = userNodeRepository; + this.shippingFrequencyCalculationService = shippingFrequencyCalculationService; } public CalculationJobRouteSection doD2dCalculation(Integer setId, Integer periodId, Premise premise, Destination destination, ContainerCalculationResult containerCalculation) { @@ -94,7 +96,10 @@ public class RouteSectionCostCalculationService { containerCalculation.getMaxContainerWeight(), BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()), BigDecimal.valueOf(containerCalculation.getHuUtilizationByWeight()), - utilization); + utilization, + shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue()), + huAnnualAmount.doubleValue(), + containerCalculation); result.setCbmPrice(!containerCalculation.isWeightExceeded()); result.setWeightPrice(containerCalculation.isWeightExceeded()); @@ -177,7 +182,10 @@ public class RouteSectionCostCalculationService { containerCalculation.getMaxContainerWeight(), BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()), BigDecimal.valueOf(containerCalculation.getTotalUtilizationByWeight()), - utilization); + utilization, + shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue()), + huAnnualAmount.doubleValue(), + containerCalculation); result.setCbmPrice(!containerCalculation.isWeightExceeded()); result.setWeightPrice(containerCalculation.isWeightExceeded()); @@ -211,7 +219,11 @@ public class RouteSectionCostCalculationService { int maxContainerWeight, BigDecimal totalVolumeUtilization, BigDecimal totalWeightUtilization, - BigDecimal propertyUtilization) { + BigDecimal propertyUtilization, + double shippingFrequency, + double annualHuAmount, + ContainerCalculationResult containerCalculationResult + ) { BigDecimal utilization; @@ -221,16 +233,26 @@ public class RouteSectionCostCalculationService { BigDecimal cbmRate = rate.divide(BigDecimal.valueOf(containerType.getVolume()), 10, RoundingMode.HALF_UP); BigDecimal weightRate = rate.divide(BigDecimal.valueOf(maxContainerWeight), 10, RoundingMode.HALF_UP); + if (huMixable) { volumePrice = cbmRate.divide(propertyUtilization, 10, RoundingMode.HALF_UP); weightPrice = weightRate.divide(BigDecimal.valueOf(1), 10, RoundingMode.HALF_UP); utilization = weightExceeded ? BigDecimal.ONE : propertyUtilization; } else { + + double huPerContainer = annualHuAmount / shippingFrequency; + + // if the shipping frequency is bigger than the annual amount the "totalXXUtilization" cannot be used. + if(huPerContainer < (containerCalculationResult.getHuUnitCount() * containerCalculationResult.getLayer())) { + + totalVolumeUtilization = BigDecimal.valueOf(huPerContainer * containerCalculationResult.getHu().getVolume(DimensionUnit.M)).divide(BigDecimal.valueOf(containerCalculationResult.getContainerType().getVolume()), 20, RoundingMode.HALF_UP); + totalWeightUtilization = BigDecimal.valueOf(huPerContainer * containerCalculationResult.getHu().getWeight(WeightUnit.KG)).divide(BigDecimal.valueOf(containerCalculationResult.getMaxContainerWeight()), 20, RoundingMode.HALF_UP); + } + volumePrice = cbmRate.divide(totalVolumeUtilization, 10, RoundingMode.HALF_UP); weightPrice = weightRate.divide(totalWeightUtilization, 10, RoundingMode.HALF_UP); utilization = weightExceeded ? totalWeightUtilization : totalVolumeUtilization; - //TODO: wenn shippingfreq > annual hu -> shippingfreq * containerprice. - // gleiches für containercalculation * shippingfreq < annual hu ammount. + } return new PriceCalculationResult(volumePrice, weightPrice, utilization); diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java index 04687f4..aa7213a 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java @@ -25,13 +25,13 @@ public class ShippingFrequencyCalculationService { } public double doCalculation(Integer setId, double huAnnualAmount) { - Integer minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN, setId).orElseThrow().getCurrentValue()); - Integer maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX, setId).orElseThrow().getCurrentValue()); + int minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN, setId).orElseThrow().getCurrentValue()); + int maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX, setId).orElseThrow().getCurrentValue()); - if (huAnnualAmount > maxAnnualFrequency.doubleValue()) + if (huAnnualAmount > (double) maxAnnualFrequency) return maxAnnualFrequency; - return Math.max(huAnnualAmount, minAnnualFrequency.doubleValue()); + return Math.max(huAnnualAmount, (double) minAnnualFrequency); } -- 2.45.3 From 1788a7ef1c8a011d43a1bf59dc95703ef3788714 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 17 Dec 2025 14:29:08 +0100 Subject: [PATCH 05/88] Refactor: Pass `ContainerCalculationResult` into cost calculation services and update `ShippingFrequencyCalculationService` logic to consider `HU per container` handling. --- .../configuration/apps/AppExchangeDTO.java | 4 ++++ .../ContainerCalculationResult.java | 5 ++++- .../CalculationExecutionService.java | 20 +++++++++++-------- .../steps/CustomCostCalculationService.java | 9 +++++---- .../steps/HandlingCostCalculationService.java | 11 +++++----- .../InventoryCostCalculationService.java | 5 +++-- .../RouteSectionCostCalculationService.java | 6 +++--- .../ShippingFrequencyCalculationService.java | 15 ++++++++++++-- 8 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/dto/configuration/apps/AppExchangeDTO.java diff --git a/src/main/java/de/avatic/lcc/dto/configuration/apps/AppExchangeDTO.java b/src/main/java/de/avatic/lcc/dto/configuration/apps/AppExchangeDTO.java new file mode 100644 index 0000000..1ce2641 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/configuration/apps/AppExchangeDTO.java @@ -0,0 +1,4 @@ +package de.avatic.lcc.dto.configuration.apps; + +public class AppExchangeDTO { +} diff --git a/src/main/java/de/avatic/lcc/model/calculation/ContainerCalculationResult.java b/src/main/java/de/avatic/lcc/model/calculation/ContainerCalculationResult.java index c2e69ca..cd879c9 100644 --- a/src/main/java/de/avatic/lcc/model/calculation/ContainerCalculationResult.java +++ b/src/main/java/de/avatic/lcc/model/calculation/ContainerCalculationResult.java @@ -215,7 +215,7 @@ public class ContainerCalculationResult { * @return The total utilization value for the container. */ public double getTotalUtilizationByVolume() { - return getHuUtilizationByVolume() * huUnitCount * layer; + return getHuUtilizationByVolume() * huUnitCount; } /** @@ -254,4 +254,7 @@ public class ContainerCalculationResult { return WeightUnit.KG.convertFromG(hu.getWeight()) / maxContainerWeight; } + public int getHuPerContainer() { + return this.huUnitCount; + } } 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 cd85c23..dfad5a0 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 @@ -137,13 +137,15 @@ public class CalculationExecutionService { AirfreightResult airfreightCost = airfreightCalculationService.doCalculation(setId, periodId, premise, destination); ContainerType usedContainerType = null; + ContainerCalculationResult selectedContainerCalculation = null; + CalculationJobDestination destinationCalculationJob = new CalculationJobDestination(); boolean hasMainRun = true; BigDecimal leadTime = null; if (destination.getD2d()) { - var containerCalculation = containerCalculationService.doCalculation(setId, premiseToHuService.createHuFromPremise(premise), ContainerType.FEU, premise.getHuMixable(), premise.getHuStackable()); - sections = List.of(new SectionInfo(null, routeSectionCostCalculationService.doD2dCalculation(setId, periodId, premise, destination, containerCalculation), containerCalculation)); + selectedContainerCalculation = containerCalculationService.doCalculation(setId, premiseToHuService.createHuFromPremise(premise), ContainerType.FEU, premise.getHuMixable(), premise.getHuStackable()); + sections = List.of(new SectionInfo(null, routeSectionCostCalculationService.doD2dCalculation(setId, periodId, premise, destination, selectedContainerCalculation), selectedContainerCalculation)); leadTime = BigDecimal.valueOf(destination.getLeadTimeD2d()); usedContainerType = ContainerType.FEU; } else { @@ -161,6 +163,8 @@ public class CalculationExecutionService { s.result().setPostRun(false); }); } + + selectedContainerCalculation = bestContainerTypeResult.selectedContainerCalculation; } destinationCalculationJob.setD2D(destination.getD2d()); @@ -168,9 +172,9 @@ public class CalculationExecutionService { if(destination.getD2d()) destinationCalculationJob.setRateD2D(destination.getRateD2d()); - customCost = customCostCalculationService.doCalculation(setId, premise, destination, sections); - handlingCost = handlingCostCalculationService.doCalculation(setId, premise, destination, hasMainRun); - inventoryCost = inventoryCostCalculationService.doCalculation(setId, premise, destination, leadTime); + customCost = customCostCalculationService.doCalculation(setId, premise, destination, sections, selectedContainerCalculation); + handlingCost = handlingCostCalculationService.doCalculation(setId, premise, destination, hasMainRun, selectedContainerCalculation); + inventoryCost = inventoryCostCalculationService.doCalculation(setId, premise, destination, leadTime, selectedContainerCalculation); destinationCalculationJob.setContainerType(usedContainerType); @@ -209,7 +213,7 @@ public class CalculationExecutionService { destinationCalculationJob.setHuCount(sections.getFirst().containerResult().getHuUnitCount()); destinationCalculationJob.setAnnualAmount(BigDecimal.valueOf(destination.getAnnualAmount())); - destinationCalculationJob.setShippingFrequency(shippingFrequencyCalculationService.doCalculation(setId, destination.getAnnualAmount())); + destinationCalculationJob.setShippingFrequency(shippingFrequencyCalculationService.doCalculation(setId, destination.getAnnualAmount(), selectedContainerCalculation.getHuPerContainer(),!premise.getHuMixable())); var commonCost = destinationCalculationJob.getAnnualHandlingCost() .add(destinationCalculationJob.getAnnualDisposalCost()) @@ -264,10 +268,10 @@ public class CalculationExecutionService { } var bestContainerType = getBestContainerType(sectionInfos); - return new BestContainerTypeResult(bestContainerType, sectionInfos.get(bestContainerType)); + return new BestContainerTypeResult(bestContainerType, sectionInfos.get(bestContainerType), containerCalculation.get(bestContainerType)); } - private record BestContainerTypeResult(ContainerType containerType, List sections) { + private record BestContainerTypeResult(ContainerType containerType, List sections, ContainerCalculationResult selectedContainerCalculation) { } } diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java index 1e2686d..2b34abf 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java @@ -37,7 +37,7 @@ public class CustomCostCalculationService { this.shippingFrequencyCalculationService = shippingFrequencyCalculationService; } - public CustomResult doCalculation(Integer setId, Premise premise, Destination destination, List sections) { + public CustomResult doCalculation(Integer setId, Premise premise, Destination destination, List sections, ContainerCalculationResult containerCalculationResult) { var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, setId, destination.getCountryId()).orElseThrow(); var sourceUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, setId, premise.getCountryId()).orElseThrow(); @@ -53,14 +53,15 @@ public class CustomCostCalculationService { double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(premise.getHuUnitCount()),0, RoundingMode.CEILING).doubleValue(); - return getCustomCalculationResult(setId, premise, destination, huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost); + return getCustomCalculationResult(setId, premise, destination, huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost, containerCalculationResult); } return CustomResult.EMPTY; } - private CustomResult getCustomCalculationResult(Integer setId, Premise premise, Destination destination, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) { - var shippingFrequency = shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount); + private CustomResult getCustomCalculationResult(Integer setId, Premise premise, Destination destination, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost, ContainerCalculationResult containerCalculationResult) { + + var shippingFrequency = shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount, containerCalculationResult.getHuPerContainer(), !premise.getHuMixable()); var customFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CUSTOM_FEE, setId).orElseThrow().getCurrentValue()); var tariffRate = premise.getTariffRate(); 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 d172f77..7c22837 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 @@ -1,5 +1,6 @@ package de.avatic.lcc.service.calculation.execution.steps; +import de.avatic.lcc.model.calculation.ContainerCalculationResult; import de.avatic.lcc.model.calculation.HandlingResult; import de.avatic.lcc.model.db.packaging.LoadCarrierType; import de.avatic.lcc.model.db.packaging.PackagingDimension; @@ -30,13 +31,13 @@ public class HandlingCostCalculationService { this.shippingFrequencyCalculationService = shippingFrequencyCalculationService; } - public HandlingResult doCalculation(Integer setId, Premise premise, Destination destination, Boolean addRepackingAndDisposalCost) { + public HandlingResult doCalculation(Integer setId, Premise premise, Destination destination, Boolean addRepackingAndDisposalCost, ContainerCalculationResult containerCalculationResult) { var hu = premiseToHuService.createHuFromPremise(premise); - return (LoadCarrierType.SLC == hu.getLoadCarrierType() ? getSLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost) : getLLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost)); + return (LoadCarrierType.SLC == hu.getLoadCarrierType() ? getSLCCost(setId, premise, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost, containerCalculationResult) : getLLCCost(setId, premise, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost, containerCalculationResult)); } - private HandlingResult getSLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType loadCarrierType, boolean addRepackingAndDisposalCost) { + private HandlingResult getSLCCost(Integer setId, Premise premise, Destination destination, PackagingDimension hu, LoadCarrierType loadCarrierType, boolean addRepackingAndDisposalCost, ContainerCalculationResult containerCalculationResult) { var destinationHandling = destination.getHandlingCost(); var destinationDisposal = destination.getDisposalCost(); @@ -77,7 +78,7 @@ public class HandlingCostCalculationService { } - private HandlingResult getLLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingAndDisposalCost) { + private HandlingResult getLLCCost(Integer setId, Premise premise, Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingAndDisposalCost, ContainerCalculationResult containerCalculationResult) { var destinationHandling = destination.getHandlingCost(); var destinationDisposal = destination.getDisposalCost(); @@ -93,7 +94,7 @@ public class HandlingCostCalculationService { BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING, setId).orElseThrow().getCurrentValue())); var annualRepacking = getRepackingCost(setId, hu, type, addRepackingAndDisposalCost, destinationRepacking).multiply(wageFactor).multiply( huAnnualAmount); - var annualHandling = ((handling.add(dispatch).add(release)).multiply(wageFactor).multiply(huAnnualAmount)).add(booking.multiply(BigDecimal.valueOf(shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue())))); + var annualHandling = ((handling.add(dispatch).add(release)).multiply(wageFactor).multiply(huAnnualAmount)).add(booking.multiply(BigDecimal.valueOf(shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue(), containerCalculationResult.getHuPerContainer(), !premise.getHuMixable())))); 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/InventoryCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/InventoryCostCalculationService.java index d9b0658..dafb733 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/InventoryCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/InventoryCostCalculationService.java @@ -1,5 +1,6 @@ package de.avatic.lcc.service.calculation.execution.steps; +import de.avatic.lcc.model.calculation.ContainerCalculationResult; import de.avatic.lcc.model.calculation.InventoryCostResult; import de.avatic.lcc.model.db.packaging.PackagingDimension; import de.avatic.lcc.model.db.premises.Premise; @@ -30,7 +31,7 @@ public class InventoryCostCalculationService { this.premiseToHuService = premiseToHuService; } - public InventoryCostResult doCalculation(Integer setId, Premise premise, Destination destination, BigDecimal leadTime) { + public InventoryCostResult doCalculation(Integer setId, Premise premise, Destination destination, BigDecimal leadTime, ContainerCalculationResult containerCalculationResult) { var fcaFee = BigDecimal.ZERO; @@ -53,7 +54,7 @@ public class InventoryCostCalculationService { var dailyAmount = annualAmount.divide(BigDecimal.valueOf(365), 10, RoundingMode.HALF_UP); var workdayAmount = annualAmount.divide(workdays, 10, RoundingMode.HALF_UP); - var opStock = (annualAmount.divide(BigDecimal.valueOf(Math.max(shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount),1)), 10, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(.5))); + var opStock = (annualAmount.divide(BigDecimal.valueOf(Math.max(shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount, containerCalculationResult.getHuPerContainer(), !premise.getHuMixable()),1)), 10, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(.5))); var safetyStock = safetyDays.multiply(workdayAmount); var stockedInventory = opStock.add(safetyStock); var inTransportStock = dailyAmount.multiply(leadTime); 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 db414e9..6139420 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 @@ -97,7 +97,7 @@ public class RouteSectionCostCalculationService { BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()), BigDecimal.valueOf(containerCalculation.getHuUtilizationByWeight()), utilization, - shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue()), + shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue(), containerCalculation.getHuPerContainer(), !premise.getHuMixable()), huAnnualAmount.doubleValue(), containerCalculation); @@ -183,7 +183,7 @@ public class RouteSectionCostCalculationService { BigDecimal.valueOf(containerCalculation.getTotalUtilizationByVolume()), BigDecimal.valueOf(containerCalculation.getTotalUtilizationByWeight()), utilization, - shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue()), + shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue(), containerCalculation.getHuPerContainer(), !premise.getHuMixable()), huAnnualAmount.doubleValue(), containerCalculation); @@ -243,7 +243,7 @@ public class RouteSectionCostCalculationService { double huPerContainer = annualHuAmount / shippingFrequency; // if the shipping frequency is bigger than the annual amount the "totalXXUtilization" cannot be used. - if(huPerContainer < (containerCalculationResult.getHuUnitCount() * containerCalculationResult.getLayer())) { + if(huPerContainer < (containerCalculationResult.getHuPerContainer())) { totalVolumeUtilization = BigDecimal.valueOf(huPerContainer * containerCalculationResult.getHu().getVolume(DimensionUnit.M)).divide(BigDecimal.valueOf(containerCalculationResult.getContainerType().getVolume()), 20, RoundingMode.HALF_UP); totalWeightUtilization = BigDecimal.valueOf(huPerContainer * containerCalculationResult.getHu().getWeight(WeightUnit.KG)).divide(BigDecimal.valueOf(containerCalculationResult.getMaxContainerWeight()), 20, RoundingMode.HALF_UP); diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java index aa7213a..969e838 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/ShippingFrequencyCalculationService.java @@ -13,10 +13,16 @@ public class ShippingFrequencyCalculationService { this.propertyRepository = propertyRepository; } - public int doCalculation(Integer setId, int huAnnualAmount) { + public int doCalculation(Integer setId, int huAnnualAmount, int maxHuPerContainer, boolean fillContainer) { var minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN, setId).orElseThrow().getCurrentValue()); var maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX, setId).orElseThrow().getCurrentValue()); + var fullContainers = huAnnualAmount / maxHuPerContainer; + + + if(fillContainer && huAnnualAmount > maxAnnualFrequency) + return fullContainers; + if (huAnnualAmount > maxAnnualFrequency) return maxAnnualFrequency; @@ -24,10 +30,15 @@ public class ShippingFrequencyCalculationService { } - public double doCalculation(Integer setId, double huAnnualAmount) { + public double doCalculation(Integer setId, double huAnnualAmount, int maxHuPerContainer, boolean fillContainer) { int minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN, setId).orElseThrow().getCurrentValue()); int maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX, setId).orElseThrow().getCurrentValue()); + var fullContainers = huAnnualAmount / maxHuPerContainer; + + if(fillContainer && huAnnualAmount > (double) maxAnnualFrequency) + return fullContainers; + if (huAnnualAmount > (double) maxAnnualFrequency) return maxAnnualFrequency; -- 2.45.3 From 6add528c02f89b8c020235cf98e9a82acc7de891 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 17 Dec 2025 16:06:59 +0100 Subject: [PATCH 06/88] Add import/export functionality for apps, including client-side file handling and backend encryption/decryption logic --- .../src/components/UI/AppListItem.vue | 26 +++- .../src/components/layout/config/Apps.vue | 83 ++++++++++- src/frontend/src/store/apps.js | 12 ++ .../configuration/AppsController.java | 13 ++ .../configuration/apps/AppExchangeDTO.java | 10 ++ .../avatic/lcc/service/apps/AppsService.java | 134 +++++++++++++++++- .../transformer/apps/AppTransformer.java | 23 ++- 7 files changed, 287 insertions(+), 14 deletions(-) diff --git a/src/frontend/src/components/UI/AppListItem.vue b/src/frontend/src/components/UI/AppListItem.vue index fc022a2..eb41c46 100644 --- a/src/frontend/src/components/UI/AppListItem.vue +++ b/src/frontend/src/components/UI/AppListItem.vue @@ -1,10 +1,20 @@