Merge pull request 'Reworked excel reporting' (#97) from feature/reporting into dev
Reviewed-on: #97
This commit is contained in:
commit
a83c49bc70
15 changed files with 1093 additions and 308 deletions
|
|
@ -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 [€]</div>
|
<div class="report-content-data-header-cell">total [€]</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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -39,26 +41,110 @@ 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)) {
|
||||||
|
|
||||||
|
if (usesOrdinals == null) usesOrdinals = header.useOrdinal();
|
||||||
|
|
||||||
|
if (usesOrdinals) {
|
||||||
Cell cell = row.createCell(header.ordinal());
|
Cell cell = row.createCell(header.ordinal());
|
||||||
cell.setCellValue(header.getHeader());
|
cell.setCellValue(header.getHeader());
|
||||||
cell.setCellStyle(style);
|
cell.setCellStyle(style);
|
||||||
worksheet.autoSizeColumn(header.ordinal());
|
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++) {
|
||||||
|
|
||||||
|
if (headers[idx] != null || !mergeCells) {
|
||||||
|
String header = headers[idx];
|
||||||
|
|
||||||
Cell cell = row.createCell(idx);
|
Cell cell = row.createCell(idx);
|
||||||
cell.setCellValue(header);
|
cell.setCellValue(header);
|
||||||
cell.setCellStyle(style);
|
cell.setCellStyle(style);
|
||||||
worksheet.autoSizeColumn(idx++);
|
|
||||||
|
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) {
|
||||||
|
|
@ -69,9 +155,18 @@ public class HeaderGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
|
if (usesOrdinals == null) usesOrdinals = header.useOrdinal();
|
||||||
|
|
||||||
|
if (usesOrdinals) {
|
||||||
sheet.autoSizeColumn(header.ordinal());
|
sheet.autoSizeColumn(header.ordinal());
|
||||||
sheet.setColumnWidth(header.ordinal(), sheet.getColumnWidth(header.ordinal()) + ADD_COLUMN_SIZE);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName)));
|
|
||||||
report.getOverview().keySet().forEach(riskName -> addData(riskName, report.getOverview().get(riskName)));
|
|
||||||
|
|
||||||
commonPremisses(report.getPremises());
|
|
||||||
|
|
||||||
report.getDestinations().forEach(this::flattenDestination);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void commonPremisses(ReportPremisesDTO premises) {
|
|
||||||
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());
|
|
||||||
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 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 void addData(String header, String data) {
|
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) {
|
||||||
this.dataHeader.add(header);
|
var material = materialRepository.getById(materialId).orElseThrow();
|
||||||
this.data.add(data);
|
var reports = reportingService.getReport(materialId, nodeIds, userNodeIds);
|
||||||
|
|
||||||
|
Sheet sheet = workbook.createSheet(material.getPartNumber());
|
||||||
|
|
||||||
|
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 addData(String header, ReportEntryDTO data) {
|
headerGenerator.fixWidth(sheet, headers.toArray(String[]::new), true);
|
||||||
this.dataHeader.add(header);
|
|
||||||
this.data.add(data.getTotal().toString()); // + " (" + data.getPercentage().doubleValue()*100 + "%)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCell(int rowIdx) {
|
private List<NodeDTO> collectDestinations(List<ReportDTO> reports) {
|
||||||
return data.get(rowIdx);
|
Map<Integer, NodeDTO> 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHeader(int rowIdx) {
|
private enum RowCaption {
|
||||||
return dataHeader.get(rowIdx);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasData(int index) {
|
|
||||||
return data.size() > index;
|
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 int getLastIndex() {
|
||||||
|
return rowSequence.size() + (destinations.size() * destinationRowSequence.size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int getDestinationIdx(int rowIndex) {
|
||||||
|
|
||||||
|
if (rowIndex < rowSequence.size()) return -1;
|
||||||
|
|
||||||
|
return (rowIndex - rowSequence.size()) / destinationRowSequence.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RowInfo getRowInfo(int rowIndex) {
|
||||||
|
if (rowIndex < rowSequence.size()) return rowSequence.get(rowIndex);
|
||||||
|
|
||||||
|
return destinationRowSequence.get((rowIndex - rowSequence.size()) % destinationRowSequence.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue