Reworked excel reporting

This commit is contained in:
Jan 2025-12-16 20:25:17 +01:00
parent 2a77d0ac70
commit e53dd7b920
15 changed files with 1093 additions and 308 deletions

View file

@ -171,16 +171,28 @@
<!-- all time high/low container rate--> <!-- all time high/low container rate-->
<div class="box-gap"> <div class="box-gap">
<collapsible-box :is-collapsable="true" variant="border" title="All time high/low MEK_B" <collapsible-box :is-collapsable="true" variant="border" title="Transport costs fluctuations"
:initially-collapsed="true" :initially-collapsed="true"
:stretch-content="true" size="m"> :stretch-content="true">
<div class="report-content-container--3-col-2"> <div class="report-content-container--3-col-2">
<div class="report-content-row"> <div class="report-content-row">
<div></div> <div></div>
<div class="report-content-data-header-cell">total [&euro;]</div> <div class="report-content-data-header-cell">total [&euro;]</div>
<div class="report-content-data-header-cell">of MEK_B [%]</div> <div class="report-content-data-header-cell">of MEK B [%]</div>
</div>
<div class="report-content-row">
<div class="">Current scenario</div>
<div class="report-content-data-cell">{{
report.overview.mek_b.total.toFixed(2)
}}
</div>
<div class="report-content-data-cell">{{
`${(report.overview.mek_b.percentage * 100).toFixed(2)}`
}}
</div>
</div> </div>
<div class="report-content-row"> <div class="report-content-row">
@ -229,6 +241,20 @@
<div class="report-content-data-cell"> {{ (report.premises.tariff_rate * 100).toFixed(2) }}%</div> <div class="report-content-data-cell"> {{ (report.premises.tariff_rate * 100).toFixed(2) }}%</div>
</div> </div>
<div class="report-content-row">
<div>Oversea share</div>
<div class="report-content-data-cell">{{ (report.premises.oversea_share * 100).toFixed(2) }}%</div>
</div>
<div class="report-content-row" v-if="(report.premises.air_freight_share ?? null) !== null">
<div>Airfreight share</div>
<div class="report-content-data-cell">{{ (report.premises.air_freight_share * 100).toFixed(2) }}%</div>
</div>
<div class="report-content-row">
<div>Safety stock [w-days]</div>
<div class="report-content-data-cell">{{ report.premises.safety_stock }}</div>
</div>
</div> </div>
<div class="report-sub-header">Handling unit</div> <div class="report-sub-header">Handling unit</div>
@ -260,7 +286,7 @@
<div class="report-content-row"> <div class="report-content-row">
<div>Mixed transport</div> <div>Mixed transport</div>
<div class="report-content-data-cell">{{ report.premises.mixed ? 'Yes' : 'No' }}</div> <div class="report-content-data-cell">{{ report.premises.mixable ? 'Yes' : 'No' }}</div>
</div> </div>
</div> </div>
@ -287,25 +313,13 @@
<div class="report-content-data-cell">{{ destination.annual_quantity }}</div> <div class="report-content-data-cell">{{ destination.annual_quantity }}</div>
</div> </div>
<div class="report-content-row">
<div>Oversea share</div>
<div class="report-content-data-cell">{{ (destination.oversea_share * 100).toFixed(2) }}%</div>
</div>
<div class="report-content-row" v-if="(destination.air_freight_share ?? null) !== null">
<div>Airfreight share</div>
<div class="report-content-data-cell">{{ (destination.air_freight_share * 100).toFixed(2) }}%</div>
</div>
<div class="report-content-row"> <div class="report-content-row">
<div>Transit time [days]</div> <div>Transit time [days]</div>
<div class="report-content-data-cell">{{ destination.transport_time }}</div> <div class="report-content-data-cell">{{ destination.transport_time }}</div>
</div> </div>
<div class="report-content-row">
<div>Safety stock [w-days]</div>
<div class="report-content-data-cell">{{ destination.safety_stock }}</div>
</div>
</div> </div>

View file

@ -37,7 +37,7 @@ export const useReportsStore = defineStore('reports', {
return; return;
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('material', this.materialId); params.append('materials', [this.materialId]);
params.append('sources', this.supplierIds); params.append('sources', this.supplierIds);
params.append('userSources', this.userSupplierIds); params.append('userSources', this.userSupplierIds);

View file

@ -81,13 +81,13 @@ public class ReportingController {
/** /**
* Downloads an Excel report for the given material and source nodes. * 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. * @param nodeIds A list of node IDs (sources) to include in the downloaded report.
* @return The Excel file as an attachment in the response. * @return The Excel file as an attachment in the response.
*/ */
@GetMapping({"/download", "/download/"}) @GetMapping({"/download", "/download/"})
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC')") @PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC')")
public ResponseEntity<InputStreamResource> downloadReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources", required = false) List<Integer> nodeIds, @RequestParam(value = "userSources", required = false) List<Integer> userNodeIds) { public ResponseEntity<InputStreamResource> downloadReport(@RequestParam(value = "materials") List<Integer> materialIds, @RequestParam(value = "sources", required = false) List<Integer> nodeIds, @RequestParam(value = "userSources", required = false) List<Integer> userNodeIds) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=lcc_report.xlsx"); headers.add("Content-Disposition", "attachment; filename=lcc_report.xlsx");
@ -96,6 +96,6 @@ public class ReportingController {
.ok() .ok()
.headers(headers) .headers(headers)
.contentType(MediaType.parseMediaType("application/vnd.ms-excel")) .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.body(new InputStreamResource(excelReportingService.generateExcelReport(materialId, nodeIds, userNodeIds))); .body(new InputStreamResource(excelReportingService.generateExcelReport(materialIds, nodeIds, userNodeIds)));
} }
} }

View file

@ -1,10 +1,10 @@
package de.avatic.lcc.dto.generic; package de.avatic.lcc.dto.generic;
public enum ContainerType { public enum ContainerType {
FEU(12030, 2350, 2390, 67.7, 24,21), FEU(12030, 2350, 2390, 67.7, 24,21, "40' GP"),
TEU(5890 ,2350,2390, 33.0, 11,10), TEU(5890 ,2350,2390, 33.0, 11,10, "20' GP"),
HC(12030, 2350, 2690, 76.4, 24,21), HC(12030, 2350, 2690, 76.4, 24,21,"40' HC"),
TRUCK(13600,2450, 2650, 88.3, 34, 33); TRUCK(13600,2450, 2650, 88.3, 34, 33, "Truck");
private final int length; private final int length;
private final int width; private final int width;
@ -12,14 +12,16 @@ public enum ContainerType {
private final double volume; private final double volume;
private final int euroPalletCount; private final int euroPalletCount;
private final int industrialPalletCount; 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.length = length;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.volume = volume; this.volume = volume;
this.euroPalletCount = euroPalletCount; this.euroPalletCount = euroPalletCount;
this.industrialPalletCount = industrialPalletCount; this.industrialPalletCount = industrialPalletCount;
this.description = description;
} }
public int getLength() { public int getLength() {
@ -40,4 +42,8 @@ public enum ContainerType {
return palletType == PalletType.EURO_PALLET ? euroPalletCount : industrialPalletCount; return palletType == PalletType.EURO_PALLET ? euroPalletCount : industrialPalletCount;
} }
public String getDescription() {
return description;
}
} }

View file

@ -16,24 +16,19 @@ public class ReportDestinationDTO {
private List<ReportSectionDTO> sections; private List<ReportSectionDTO> sections;
/* general */ /* general */
@JsonProperty("oversea_share")
private Double overseaShare;
@JsonProperty("air_freight_share")
private Double airFreightShare;
@JsonProperty("transport_time") @JsonProperty("transport_time")
private Double transportTime; private Double transportTime;
@JsonProperty("safety_stock")
private Integer safetyStock;
@JsonProperty("annual_quantity") @JsonProperty("annual_quantity")
private Integer annualQuantity; private Integer annualQuantity;
private Integer layer;
/* container */ /* container */
private Integer layer;
@JsonProperty("unit_count") @JsonProperty("unit_count")
private Number unitCount; private Number unitCount;
@ -48,7 +43,6 @@ public class ReportDestinationDTO {
@JsonProperty("container_rate") @JsonProperty("container_rate")
private Number rate; private Number rate;
private Boolean mixed;
public Integer getId() { public Integer getId() {
return id; return id;
@ -82,38 +76,6 @@ public class ReportDestinationDTO {
this.annualQuantity = annualQuantity; 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() { public Integer getLayer() {
return layer; return layer;
} }
@ -162,11 +124,11 @@ public class ReportDestinationDTO {
this.rate = rate; this.rate = rate;
} }
public Boolean getMixed() { public Double getTransportTime() {
return mixed; return transportTime;
} }
public void setMixed(Boolean mixed) { public void setTransportTime(Double transportTime) {
this.mixed = mixed; this.transportTime = transportTime;
} }
} }

View file

@ -13,6 +13,16 @@ public class ReportPremisesDTO {
@JsonProperty("tariff_rate") @JsonProperty("tariff_rate")
private Number tariffRate; private Number tariffRate;
@JsonProperty("oversea_share")
private Number overseaShare;
@JsonProperty("air_freight_share")
private Number airFreightShare;
@JsonProperty("safety_stock")
private Number safetyStock;
/* packaging */ /* packaging */
@ -33,6 +43,9 @@ public class ReportPremisesDTO {
@JsonProperty("hu_unit_count") @JsonProperty("hu_unit_count")
private Integer huUnitCount; private Integer huUnitCount;
@JsonProperty("mixable")
private Boolean mixable;
public String getHsCode() { public String getHsCode() {
return hsCode; return hsCode;
@ -105,4 +118,38 @@ public class ReportPremisesDTO {
public void setHuUnitCount(Integer huUnitCount) { public void setHuUnitCount(Integer huUnitCount) {
this.huUnitCount = 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;
}
} }

View file

@ -3,4 +3,14 @@ package de.avatic.lcc.model.bulk.header;
public interface HeaderProvider { public interface HeaderProvider {
String getHeader(); String getHeader();
default int occupiedCells() {
return 1;
}
default int getColumn() { throw new UnsupportedOperationException();}
default boolean useOrdinal() {
return true;
}
} }

View file

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

View file

@ -4,8 +4,7 @@ import de.avatic.lcc.dto.bulk.BulkFileType;
import de.avatic.lcc.model.bulk.BulkFileTypes; import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.model.bulk.BulkOperation; import de.avatic.lcc.model.bulk.BulkOperation;
import de.avatic.lcc.model.bulk.HiddenTableType; import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; import de.avatic.lcc.service.bulk.helper.CellStyleProvider;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
import de.avatic.lcc.service.excelMapper.*; import de.avatic.lcc.service.excelMapper.*;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
@ -21,7 +20,7 @@ import java.io.IOException;
@Service @Service
public class BulkExportService { public class BulkExportService {
private final HeaderCellStyleProvider headerCellStyleProvider; private final CellStyleProvider cellStyleProvider;
private final ContainerRateExcelMapper containerRateExcelMapper; private final ContainerRateExcelMapper containerRateExcelMapper;
private final MatrixRateExcelMapper matrixRateExcelMapper; private final MatrixRateExcelMapper matrixRateExcelMapper;
private final PackagingExcelMapper packagingExcelMapper; private final PackagingExcelMapper packagingExcelMapper;
@ -31,8 +30,8 @@ public class BulkExportService {
private final String sheetPassword; private final String sheetPassword;
private final MaterialFastExcelMapper materialFastExcelMapper; 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) { 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.headerCellStyleProvider = headerCellStyleProvider; this.cellStyleProvider = cellStyleProvider;
this.containerRateExcelMapper = containerRateExcelMapper; this.containerRateExcelMapper = containerRateExcelMapper;
this.matrixRateExcelMapper = matrixRateExcelMapper; this.matrixRateExcelMapper = matrixRateExcelMapper;
this.packagingExcelMapper = packagingExcelMapper; this.packagingExcelMapper = packagingExcelMapper;
@ -66,7 +65,7 @@ public class BulkExportService {
Workbook workbook = new XSSFWorkbook(); Workbook workbook = new XSSFWorkbook();
Sheet worksheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); 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)) { if (bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) {

View file

@ -3,7 +3,7 @@ package de.avatic.lcc.service.bulk;
import de.avatic.lcc.dto.bulk.BulkFileType; import de.avatic.lcc.dto.bulk.BulkFileType;
import de.avatic.lcc.model.bulk.*; import de.avatic.lcc.model.bulk.*;
import de.avatic.lcc.model.bulk.header.*; 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.bulk.helper.HeaderGenerator;
import de.avatic.lcc.service.excelMapper.*; import de.avatic.lcc.service.excelMapper.*;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
@ -24,7 +24,7 @@ public class TemplateExportService {
private final HeaderGenerator headerGenerator; private final HeaderGenerator headerGenerator;
private final HeaderCellStyleProvider headerCellStyleProvider; private final CellStyleProvider cellStyleProvider;
private final HiddenNodeExcelMapper hiddenNodeExcelMapper; private final HiddenNodeExcelMapper hiddenNodeExcelMapper;
private final HiddenCountryExcelMapper hiddenCountryExcelMapper; private final HiddenCountryExcelMapper hiddenCountryExcelMapper;
private final String sheetPassword; private final String sheetPassword;
@ -34,9 +34,9 @@ public class TemplateExportService {
private final PackagingExcelMapper packagingExcelMapper; private final PackagingExcelMapper packagingExcelMapper;
private final NodeExcelMapper nodeExcelMapper; 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.headerGenerator = headerGenerator;
this.headerCellStyleProvider = headerCellStyleProvider; this.cellStyleProvider = cellStyleProvider;
this.hiddenNodeExcelMapper = hiddenNodeExcelMapper; this.hiddenNodeExcelMapper = hiddenNodeExcelMapper;
this.hiddenCountryExcelMapper = hiddenCountryExcelMapper; this.hiddenCountryExcelMapper = hiddenCountryExcelMapper;
this.sheetPassword = sheetPassword; this.sheetPassword = sheetPassword;
@ -53,7 +53,7 @@ public class TemplateExportService {
Sheet sheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); 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)) { if (bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) {

View file

@ -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<TextFormat, Map<PredefinedColors, CellStyle>> createCellStyles(Workbook workbook) {
Map<TextFormat, Map<PredefinedColors, CellStyle>> styles = new HashMap<>();
Map<TextFormat, Short> 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<PredefinedColors, CellStyle> 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; }
}
}

View file

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

View file

@ -6,6 +6,8 @@ import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; 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 org.springframework.stereotype.Service;
import java.util.EnumSet; import java.util.EnumSet;
@ -13,13 +15,13 @@ import java.util.EnumSet;
@Service @Service
public class HeaderGenerator { public class HeaderGenerator {
private static final int ADD_COLUMN_SIZE = (10*256); private static final int ADD_COLUMN_SIZE = (10 * 256);
public <H extends Enum<H> & HeaderProvider> void validateHeader(Sheet sheet, Class<H> headers) { public <H extends Enum<H> & HeaderProvider> void validateHeader(Sheet sheet, Class<H> headers) {
Row row = sheet.getRow(0); Row row = sheet.getRow(0);
for(H header : EnumSet.allOf(headers)){ for (H header : EnumSet.allOf(headers)) {
Cell cell = row.getCell(header.ordinal()); 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() + "\""); 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) { public void validateHeader(Sheet sheet, String[] headers) {
Row row = sheet.getRow(0); Row row = sheet.getRow(0);
int idx = 0; int idx = 0;
for(String header : headers){ for (String header : headers) {
Cell cell = row.getCell(idx++); 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 + "\""); 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 <H extends Enum<H> & HeaderProvider> void generateHeader(Sheet worksheet, Class<H> headers, CellStyle style) { public <H extends Enum<H> & HeaderProvider> void generateHeader(Sheet worksheet, Class<H> headers, CellStyle style) {
Row row = worksheet.createRow(0); Row row = worksheet.createRow(0);
Boolean usesOrdinals = null;
for (H header : EnumSet.allOf(headers)) { for (H header : EnumSet.allOf(headers)) {
Cell cell = row.createCell(header.ordinal());
cell.setCellValue(header.getHeader()); if (usesOrdinals == null) usesOrdinals = header.useOrdinal();
cell.setCellStyle(style);
worksheet.autoSizeColumn(header.ordinal()); 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) { 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); Row row = worksheet.createRow(0);
int idx = 0;
for (String header : headers) { for (int idx = 0; idx < headers.length; idx++) {
Cell cell = row.createCell(idx);
cell.setCellValue(header); if (headers[idx] != null || !mergeCells) {
cell.setCellStyle(style); String header = headers[idx];
worksheet.autoSizeColumn(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) { public void fixWidth(Sheet sheet, String[] headers) {
int idx = 0; int idx = 0;
for (String header : headers) { for (String header : headers) {
sheet.autoSizeColumn(idx); sheet.autoSizeColumn(idx);
sheet.setColumnWidth(idx,sheet.getColumnWidth(idx)+ADD_COLUMN_SIZE); sheet.setColumnWidth(idx, sheet.getColumnWidth(idx) + ADD_COLUMN_SIZE);
idx++; idx++;
} }
} }
public <H extends Enum<H> & HeaderProvider> void fixWidth(Sheet sheet, Class<H> headers) { public <H extends Enum<H> & HeaderProvider> void fixWidth(Sheet sheet, Class<H> headers) {
Boolean usesOrdinals = null;
for (H header : EnumSet.allOf(headers)) { for (H header : EnumSet.allOf(headers)) {
sheet.autoSizeColumn(header.ordinal()); if (usesOrdinals == null) usesOrdinals = header.useOrdinal();
sheet.setColumnWidth(header.ordinal(),sheet.getColumnWidth(header.ordinal())+ADD_COLUMN_SIZE);
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);
}
} }
} }

View file

@ -1,215 +1,721 @@
package de.avatic.lcc.service.report; package de.avatic.lcc.service.report;
import de.avatic.lcc.dto.generic.NodeDTO; 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.ReportDTO;
import de.avatic.lcc.dto.report.ReportDestinationDTO; import de.avatic.lcc.dto.report.ReportDestinationDTO;
import de.avatic.lcc.dto.report.ReportEntryDTO; import de.avatic.lcc.dto.report.ReportEntryDTO;
import de.avatic.lcc.dto.report.ReportPremisesDTO; import de.avatic.lcc.dto.report.ReportSectionDTO;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; 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 de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.stream.Stream;
@Service @Service
public class ExcelReportingService { public class ExcelReportingService {
private final ReportingService reportingService; private static final Map<Integer, RowInfo> rowSequence = new HashMap<>() {
private final HeaderCellStyleProvider headerCellStyleProvider; {
private final HeaderGenerator headerGenerator; 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<Integer, RowInfo> 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.reportingService = reportingService;
this.headerCellStyleProvider = headerCellStyleProvider; this.cellStyleProvider = cellStyleProvider;
this.headerGenerator = headerGenerator; this.headerGenerator = headerGenerator;
this.materialRepository = materialRepository;
this.propertyRepository = propertyRepository;
} }
public ByteArrayResource generateExcelReport(Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds ) { public ByteArrayResource generateExcelReport(List<Integer> materialIds, List<Integer> nodeIds, List<Integer> userNodeIds) {
var reportingProperty = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.REPORTING).orElseThrow();
boolean includeAirfreight = reportingProperty.getCurrentValue().equals("MEK_C");
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
var reports = reportingService.getReport(materialId, nodeIds, userNodeIds);
Workbook workbook = new XSSFWorkbook(); Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("report"); CellStyle headerStyle = cellStyleProvider.createHeaderCellStyle(workbook);
var styles = cellStyleProvider.createCellStyles(workbook);
CellStyle headerStyle = headerCellStyleProvider.createHeaderCellStyle(workbook);
ArrayList<String> 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<ReportFlattener> flatterers = reports.stream().map(ReportFlattener::new).toList();
while(true) { fillSummarySheet(workbook, headerStyle, materialIds, nodeIds, userNodeIds, styles);
boolean hasData = false; materialIds.forEach(id -> fillExcelSheet(workbook, headerStyle, id, nodeIds, userNodeIds, styles, includeAirfreight));
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));
// Return the Excel file as an InputStreamSource // Return the Excel file as an InputStreamSource
workbook.write(outputStream); workbook.write(outputStream);
return new ByteArrayResource(outputStream.toByteArray()); return new ByteArrayResource(outputStream.toByteArray());
} catch ( } catch (IOException e) {
IOException e) {
throw new RuntimeException("Failed to generate template", e); throw new RuntimeException("Failed to generate template", e);
} }
} }
private static class ReportFlattener { private void fillSummarySheet(Workbook workbook, CellStyle headerStyle, List<Integer> materialIds, List<Integer> nodeIds, List<Integer> userNodeIds, Map<CellStyleProvider.TextFormat, Map<CellStyleProvider.PredefinedColors, CellStyle>> styles) {
Sheet sheet = workbook.createSheet("Summary");
private static final String SUPPLIER_NAME = "Supplier"; int row = 1;
private static final String SUPPLIER_ADDRESS = "Address";
private static final String DESTINATION_NAME = "Destination"; for (Integer materialId : materialIds) {
private static final String DESTINATION_ADDRESS = "Address"; for (var mapper : reportingService.getReport(materialId, nodeIds, userNodeIds).stream().map(report -> new SummaryMapper(report, styles)).toList()) {
mapper.map(sheet, row++);
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<String> data = new ArrayList<>();
private final List<String> dataHeader = new ArrayList<>();
public ReportFlattener(ReportDTO report) {
this.report = report;
flatten();
} }
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 private void fillExcelSheet(Workbook workbook, CellStyle headerStyle, Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds, Map<CellStyleProvider.TextFormat, Map<CellStyleProvider.PredefinedColors, CellStyle>> styles, boolean includeAirfreight) {
report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName))); var material = materialRepository.getById(materialId).orElseThrow();
report.getOverview().keySet().forEach(riskName -> addData(riskName, report.getOverview().get(riskName))); var reports = reportingService.getReport(materialId, nodeIds, userNodeIds);
commonPremisses(report.getPremises()); Sheet sheet = workbook.createSheet(material.getPartNumber());
report.getDestinations().forEach(this::flattenDestination); ArrayList<String> 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<NodeDTO> 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) { headerGenerator.fixWidth(sheet, headers.toArray(String[]::new), true);
addData(DESTINATION_HS_CODE, premises.getHsCode());
addData(DESTINATION_TARIFF_RATE, premises.getTariffRate().toString());
addData(DESTINATION_WIDTH, premises.getWidth().toString()); }
addData(DESTINATION_HEIGHT, premises.getHeight().toString());
addData(DESTINATION_LENGTH, premises.getLength().toString()); private List<NodeDTO> collectDestinations(List<ReportDTO> reports) {
addData(DESTINATION_DIMENSION_UNIT, premises.getDimensionUnit().toString()); Map<Integer, NodeDTO> destinations = new HashMap<>();
addData(DESTINATION_WEIGHT, premises.getWeight().toString());
addData(DESTINATION_WEIGHT_UNIT, premises.getWeightUnit().toString()); reports.forEach(r -> r.getDestinations().forEach(d -> {
addData(DESTINATION_HU_UNIT_COUNT, premises.getHuUnitCount().toString()); 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<ReportMapper> mapper, List<NodeDTO> destinations,
Map<CellStyleProvider.TextFormat, Map<CellStyleProvider.PredefinedColors, CellStyle>> styles,
Map<Integer, RowInfo> rowSequence, Map<Integer, RowInfo> 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) { private int getLastIndex() {
return rowSequence.size() + (destinations.size() * destinationRowSequence.size()) - 1;
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 void addData(String header, String data) { private CellStyle getStyle(RowInfo info) {
this.dataHeader.add(header); return switch (info.type) {
this.data.add(data); 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) { private int getDestinationIdx(int rowIndex) {
this.dataHeader.add(header);
this.data.add(data.getTotal().toString()); // + " (" + data.getPercentage().doubleValue()*100 + "%)"); if (rowIndex < rowSequence.size()) return -1;
return (rowIndex - rowSequence.size()) / destinationRowSequence.size();
} }
public String getCell(int rowIdx) { private RowInfo getRowInfo(int rowIndex) {
return data.get(rowIdx); if (rowIndex < rowSequence.size()) return rowSequence.get(rowIndex);
}
public String getHeader(int rowIdx) { return destinationRowSequence.get((rowIndex - rowSequence.size()) % destinationRowSequence.size());
return dataHeader.get(rowIdx);
}
public boolean hasData(int index) {
return data.size() > index;
} }
} }
private record ReportMapper(ReportDTO report, List<NodeDTO> destinations,
Map<CellStyleProvider.TextFormat, Map<CellStyleProvider.PredefinedColors, CellStyle>> 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<ReportSectionDTO> 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<CellStyleProvider.TextFormat, Map<CellStyleProvider.PredefinedColors, CellStyle>> 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);
}
}
} }

View file

@ -98,7 +98,7 @@ public class ReportTransformer {
reportDTO.setStartDate(period.startDate); reportDTO.setStartDate(period.startDate);
reportDTO.setEndDate(period.endDate); reportDTO.setEndDate(period.endDate);
reportDTO.setPremises(getPremisesDTO(job, premise)); reportDTO.setPremises(getPremisesDTO(job, premise, destinations, includeAirfreight));
if (!destinations.isEmpty()) { if (!destinations.isEmpty()) {
@ -171,9 +171,11 @@ public class ReportTransformer {
return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalD2D, totalCost); return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalD2D, totalCost);
} }
private ReportPremisesDTO getPremisesDTO(CalculationJob job, Premise premise) { private ReportPremisesDTO getPremisesDTO(CalculationJob job, Premise premise, List<CalculationJobDestination> destinations, boolean includeAirfreight) {
ReportPremisesDTO premisesDTO = new ReportPremisesDTO(); ReportPremisesDTO premisesDTO = new ReportPremisesDTO();
var destination = destinations.getFirst();
var dimensionUnit = premise.getHuDisplayedDimensionUnit(); var dimensionUnit = premise.getHuDisplayedDimensionUnit();
var weightUnit = premise.getHuDisplayedWeightUnit(); var weightUnit = premise.getHuDisplayedWeightUnit();
@ -188,6 +190,14 @@ public class ReportTransformer {
premisesDTO.setHsCode(premise.getHsCode()); premisesDTO.setHsCode(premise.getHsCode());
premisesDTO.setTariffRate(premise.getTariffRate()); 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; return premisesDTO;
} }
@ -217,15 +227,8 @@ public class ReportTransformer {
destinationDTO.setLayer(destination.getLayerCount()); destinationDTO.setLayer(destination.getLayerCount());
destinationDTO.setOverseaShare(premise.getOverseaShare().doubleValue());
destinationDTO.setSafetyStock(destination.getSafetyStockInDays().intValue());
destinationDTO.setTransportTime(destination.getTotalTransitTime().doubleValue()); destinationDTO.setTransportTime(destination.getTotalTransitTime().doubleValue());
if (includeAirfreight)
destinationDTO.setAirFreightShare(destination.getAirFreightShare().doubleValue());
destinationDTO.setMixed(premise.getHuMixable());
destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate()); destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate());
destinationDTO.setType(destination.getContainerType()); destinationDTO.setType(destination.getContainerType());
destinationDTO.setUtilization(destination.getContainerUtilization()); destinationDTO.setUtilization(destination.getContainerUtilization());