Refactor report generation for improved structure and clarity

Reorganize report-related DTOs, transforming and flattening logic for better modularity and maintainability. Introduce new enums and fields related to transport and route types, ensuring seamless replacement of deprecated ones like `RouteType`. Extend database schema to support new fields, improving flexibility in data handling.
This commit is contained in:
Jan 2025-05-09 08:32:11 +02:00
parent ced60b74d2
commit 46fd70a2c8
34 changed files with 790 additions and 550 deletions

View file

@ -2,7 +2,7 @@ package de.avatic.lcc.dto.calculation;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.TransportType;
import java.util.List; import java.util.List;
@ -10,7 +10,7 @@ public class RouteDTO {
private Integer id; private Integer id;
private RouteType type; private TransportType type;
@JsonProperty("is_selected") @JsonProperty("is_selected")
private Boolean isSelected; private Boolean isSelected;
@ -32,11 +32,11 @@ public class RouteDTO {
this.id = id; this.id = id;
} }
public RouteType getType() { public TransportType getType() {
return type; return type;
} }
public void setType(RouteType type) { public void setType(TransportType type) {
this.type = type; this.type = type;
} }

View file

@ -2,7 +2,7 @@ package de.avatic.lcc.dto.configuration.rates;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO; import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import java.util.HashMap; import java.util.HashMap;
@ -14,7 +14,7 @@ public class ContainerRateDTO {
private NodeDTO origin; private NodeDTO origin;
private NodeDTO destination; private NodeDTO destination;
private RouteType type; private TransportType type;
private HashMap<String, Number> rates; private HashMap<String, Number> rates;
@ -47,11 +47,11 @@ public class ContainerRateDTO {
this.destination = destination; this.destination = destination;
} }
public RouteType getType() { public TransportType getType() {
return type; return type;
} }
public void setType(RouteType type) { public void setType(TransportType type) {
this.type = type; this.type = type;
} }

View file

@ -0,0 +1,5 @@
package de.avatic.lcc.dto.generic;
public enum RateType {
MATRIX, CONTAINER, D2D
}

View file

@ -1,5 +0,0 @@
package de.avatic.lcc.dto.generic;
public enum RouteType {
RAIL, SEA, D2D, ROAD, POST_RUN
}

View file

@ -0,0 +1,5 @@
package de.avatic.lcc.dto.generic;
public enum TransportType {
RAIL, SEA, ROAD, POST_RUN
}

View file

@ -1,69 +0,0 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.ContainerType;
public class ReportContainerDTO {
private ContainerType type;
private Number rate;
@JsonProperty("unit_count")
private Number unitCount;
@JsonProperty("weight_exceeded")
private Boolean weightExceeded;
private Number utilization;
private Boolean mixed;
public ContainerType getType() {
return type;
}
public void setType(ContainerType type) {
this.type = type;
}
public Number getRate() {
return rate;
}
public void setRate(Number rate) {
this.rate = rate;
}
public Number getUnitCount() {
return unitCount;
}
public void setUnitCount(Number unitCount) {
this.unitCount = unitCount;
}
public Boolean getWeightExceeded() {
return weightExceeded;
}
public void setWeightExceeded(Boolean weightExceeded) {
this.weightExceeded = weightExceeded;
}
public Number getUtilization() {
return utilization;
}
public void setUtilization(Number utilization) {
this.utilization = utilization;
}
public Boolean getMixed() {
return mixed;
}
public void setMixed(Boolean mixed) {
this.mixed = mixed;
}
}

View file

@ -1,11 +1,15 @@
package de.avatic.lcc.dto.report; package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO;
import java.util.List;
import java.util.Map; import java.util.Map;
public class ReportDTO { public class ReportDTO {
NodeDTO supplier;
@JsonProperty("costs") @JsonProperty("costs")
public Map<String, ReportEntryDTO> cost; public Map<String, ReportEntryDTO> cost;
@ -13,8 +17,19 @@ public class ReportDTO {
public Map<String, ReportEntryDTO> risk; public Map<String, ReportEntryDTO> risk;
@JsonProperty("premises") @JsonProperty("premises")
public ReportPremisesDTO premises; public List<ReportDestinationDTO> destinations;
public NodeDTO getSupplier() {
return supplier;
}
public void setSupplier(NodeDTO supplier) {
this.supplier = supplier;
}
public void setDestinations(List<ReportDestinationDTO> destinations) {
this.destinations = destinations;
}
public Map<String, ReportEntryDTO> getCost() { public Map<String, ReportEntryDTO> getCost() {
return cost; return cost;
@ -32,11 +47,11 @@ public class ReportDTO {
this.risk = risk; this.risk = risk;
} }
public ReportPremisesDTO getPremises() { public List<ReportDestinationDTO> getDestinations() {
return premises; return destinations;
} }
public void setPremises(ReportPremisesDTO premises) { public void setDestination(List<ReportDestinationDTO> destinations) {
this.premises = premises; this.destinations = destinations;
} }
} }

View file

@ -0,0 +1,272 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
import java.util.List;
public class ReportDestinationDTO {
private Integer id;
private NodeDTO destination;
private List<ReportSectionDTO> sections;
/* general */
@JsonProperty("annual_quantity")
private Integer annualQuantity;
@JsonProperty("hs_code")
private String hsCode;
@JsonProperty("tariff_rate")
private Number tariffRate;
@JsonProperty("oversea_share")
private Double overseaShare;
@JsonProperty("air_freight_share")
private Double airFreightShare;
@JsonProperty("transport_time")
private Double transportTime;
@JsonProperty("safety_stock")
private Double safetyStock;
/* packaging */
private Double width;
private Double height;
private Double length;
private Double weight;
@JsonProperty("dimension_unit")
private DimensionUnit dimensionUnit;
@JsonProperty("weight_unit")
private WeightUnit weightUnit;
@JsonProperty("hu_unit_count")
private Integer huUnitCount;
private Integer layer;
/* container */
@JsonProperty("unit_count")
private Number unitCount;
private Number utilization;
@JsonProperty("container_type")
private ContainerType type;
@JsonProperty("weight_exceeded")
private Boolean weightExceeded;
@JsonProperty("container_rate")
private Number rate;
private Boolean mixed;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public NodeDTO getDestination() {
return destination;
}
public void setDestination(NodeDTO destination) {
this.destination = destination;
}
public List<ReportSectionDTO> getSections() {
return sections;
}
public void setSections(List<ReportSectionDTO> sections) {
this.sections = sections;
}
public Integer getAnnualQuantity() {
return annualQuantity;
}
public void setAnnualQuantity(Integer annualQuantity) {
this.annualQuantity = annualQuantity;
}
public String getHsCode() {
return hsCode;
}
public void setHsCode(String hsCode) {
this.hsCode = hsCode;
}
public Number getTariffRate() {
return tariffRate;
}
public void setTariffRate(Number tariffRate) {
this.tariffRate = tariffRate;
}
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 Double getSafetyStock() {
return safetyStock;
}
public void setSafetyStock(Double safetyStock) {
this.safetyStock = safetyStock;
}
public Double getWidth() {
return width;
}
public void setWidth(Double width) {
this.width = width;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Double getLength() {
return length;
}
public void setLength(Double length) {
this.length = length;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public DimensionUnit getDimensionUnit() {
return dimensionUnit;
}
public void setDimensionUnit(DimensionUnit dimensionUnit) {
this.dimensionUnit = dimensionUnit;
}
public WeightUnit getWeightUnit() {
return weightUnit;
}
public void setWeightUnit(WeightUnit weightUnit) {
this.weightUnit = weightUnit;
}
public Integer getHuUnitCount() {
return huUnitCount;
}
public void setHuUnitCount(Integer huUnitCount) {
this.huUnitCount = huUnitCount;
}
public Integer getLayer() {
return layer;
}
public void setLayer(Integer layer) {
this.layer = layer;
}
public Number getUnitCount() {
return unitCount;
}
public void setUnitCount(Number unitCount) {
this.unitCount = unitCount;
}
public Number getUtilization() {
return utilization;
}
public void setUtilization(Number utilization) {
this.utilization = utilization;
}
public ContainerType getType() {
return type;
}
public void setType(ContainerType type) {
this.type = type;
}
public Boolean getWeightExceeded() {
return weightExceeded;
}
public void setWeightExceeded(Boolean weightExceeded) {
this.weightExceeded = weightExceeded;
}
public Number getRate() {
return rate;
}
public void setRate(Number rate) {
this.rate = rate;
}
public Boolean getMixed() {
return mixed;
}
public void setMixed(Boolean mixed) {
this.mixed = mixed;
}
}

View file

@ -1,91 +0,0 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
public class ReportPackagingDTO {
private Double width;
private Double height;
private Double length;
private Double weight;
@JsonProperty("dimension_unit")
private DimensionUnit dimensionUnit;
@JsonProperty("weight_unit")
private WeightUnit weightUnit;
@JsonProperty("unit_count")
private Integer unitCount;
private Integer layer;
public Double getWidth() {
return width;
}
public void setWidth(Double width) {
this.width = width;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Double getLength() {
return length;
}
public void setLength(Double length) {
this.length = length;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public DimensionUnit getDimensionUnit() {
return dimensionUnit;
}
public void setDimensionUnit(DimensionUnit dimensionUnit) {
this.dimensionUnit = dimensionUnit;
}
public WeightUnit getWeightUnit() {
return weightUnit;
}
public void setWeightUnit(WeightUnit weightUnit) {
this.weightUnit = weightUnit;
}
public Integer getUnitCount() {
return unitCount;
}
public void setUnitCount(Integer unitCount) {
this.unitCount = unitCount;
}
public Integer getLayer() {
return layer;
}
public void setLayer(Integer layer) {
this.layer = layer;
}
}

View file

@ -1,70 +0,0 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class ReportPremisesDTO {
List<ReportQuantityDTO> quantities;
@JsonProperty("hs_code")
private String hsCode;
@JsonProperty("tariff_rate")
private Number tariffRate;
private ReportContainerDTO container;
private ReportPackagingDTO packaging;
@JsonProperty("quota_share")
private ReportQuotaShareDTO quotaShare;
public List<ReportQuantityDTO> getQuantities() {
return quantities;
}
public void setQuantities(List<ReportQuantityDTO> quantities) {
this.quantities = quantities;
}
public String getHsCode() {
return hsCode;
}
public void setHsCode(String hsCode) {
this.hsCode = hsCode;
}
public Number getTariffRate() {
return tariffRate;
}
public void setTariffRate(Number tariffRate) {
this.tariffRate = tariffRate;
}
public ReportContainerDTO getContainer() {
return container;
}
public void setContainer(ReportContainerDTO container) {
this.container = container;
}
public ReportPackagingDTO getPackaging() {
return packaging;
}
public void setPackaging(ReportPackagingDTO packaging) {
this.packaging = packaging;
}
public ReportQuotaShareDTO getQuotaShare() {
return quotaShare;
}
public void setQuotaShare(ReportQuotaShareDTO quotaShare) {
this.quotaShare = quotaShare;
}
}

View file

@ -1,38 +0,0 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO;
import java.util.List;
public class ReportQuantityDTO {
private NodeDTO destination;
private Number quantity;
private List<ReportRouteEntryDTO> route;
public NodeDTO getDestination() {
return destination;
}
public void setDestination(NodeDTO destination) {
this.destination = destination;
}
public Number getQuantity() {
return quantity;
}
public void setQuantity(Number quantity) {
this.quantity = quantity;
}
public List<ReportRouteEntryDTO> getRoute() {
return route;
}
public void setRoute(List<ReportRouteEntryDTO> route) {
this.route = route;
}
}

View file

@ -1,51 +0,0 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ReportQuotaShareDTO {
@JsonProperty("oversea_share")
private Double overseaShare;
@JsonProperty("air_freight_share")
private Double airFreightShare;
@JsonProperty("transport_time")
private Double transportTime;
@JsonProperty("safety_stock")
private Double safetyStock;
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 Double getSafetyStock() {
return safetyStock;
}
public void setSafetyStock(Double safetyStock) {
this.safetyStock = safetyStock;
}
}

View file

@ -1,36 +0,0 @@
package de.avatic.lcc.dto.report;
import de.avatic.lcc.dto.generic.RouteType;
public class ReportRouteEntryDTO {
private String name;
private RouteType type;
private ReportEntryDTO cost;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RouteType getType() {
return type;
}
public void setType(RouteType type) {
this.type = type;
}
public ReportEntryDTO getCost() {
return cost;
}
public void setCost(ReportEntryDTO cost) {
this.cost = cost;
}
}

View file

@ -0,0 +1,74 @@
package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
public class ReportSectionDTO {
private Integer id;
@JsonProperty("route_type")
private TransportType transportType;
@JsonProperty("rate_type")
private RateType rateType;
@JsonProperty("from_node")
private NodeDTO fromNode;
@JsonProperty("cost")
private ReportEntryDTO cost;
@JsonProperty("duration")
private ReportEntryDTO duration;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public TransportType getTransportType() {
return transportType;
}
public void setTransportType(TransportType transportType) {
this.transportType = transportType;
}
public RateType getRateType() {
return rateType;
}
public void setRateType(RateType rateType) {
this.rateType = rateType;
}
public NodeDTO getFromNode() {
return fromNode;
}
public void setFromNode(NodeDTO fromNode) {
this.fromNode = fromNode;
}
public ReportEntryDTO getCost() {
return cost;
}
public void setCost(ReportEntryDTO cost) {
this.cost = cost;
}
public ReportEntryDTO getDuration() {
return duration;
}
public void setDuration(ReportEntryDTO duration) {
this.duration = duration;
}
}

View file

@ -1,12 +1,16 @@
package de.avatic.lcc.model.calculations; package de.avatic.lcc.model.calculations;
import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import java.math.BigDecimal; import java.math.BigDecimal;
public class CalculationJobRouteSection { public class CalculationJobRouteSection {
private Integer id; private Integer id;
private Integer premiseRouteSectionId; private Integer premiseRouteSectionId;
private Integer calculationJobDestinationId; private Integer calculationJobDestinationId;
private CalculationJobTransportationRuleType usedRule; private TransportType transportType;
private RateType rateType;
private Boolean isUnmixedPrice; private Boolean isUnmixedPrice;
private Boolean isCbmPrice; private Boolean isCbmPrice;
private Boolean isWeightPrice; private Boolean isWeightPrice;
@ -21,6 +25,23 @@
private BigDecimal annualCost; private BigDecimal annualCost;
private Integer transitTime; private Integer transitTime;
public RateType getRateType() {
return rateType;
}
public void setRateType(RateType rateType) {
this.rateType = rateType;
}
public TransportType getTransportType() {
return transportType;
}
public void setTransportType(TransportType transportType) {
this.transportType = transportType;
}
public Integer getId() { public Integer getId() {
return id; return id;
} }
@ -45,14 +66,6 @@
this.calculationJobDestinationId = calculationJobDestinationId; this.calculationJobDestinationId = calculationJobDestinationId;
} }
public CalculationJobTransportationRuleType getUsedRule() {
return usedRule;
}
public void setUsedRule(CalculationJobTransportationRuleType usedRule) {
this.usedRule = usedRule;
}
public Boolean getUnmixedPrice() { public Boolean getUnmixedPrice() {
return isUnmixedPrice; return isUnmixedPrice;
} }

View file

@ -23,6 +23,26 @@ public class Destination {
private BigDecimal disposalCost; private BigDecimal disposalCost;
private BigDecimal geoLat;
private BigDecimal geoLng;
public BigDecimal getGeoLat() {
return geoLat;
}
public void setGeoLat(BigDecimal geoLat) {
this.geoLat = geoLat;
}
public BigDecimal getGeoLng() {
return geoLng;
}
public void setGeoLng(BigDecimal geoLng) {
this.geoLng = geoLng;
}
public Integer getId() { public Integer getId() {
return id; return id;
} }

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.model.premises.route; package de.avatic.lcc.model.premises.route;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
public class RouteSection { public class RouteSection {
@ -10,7 +11,9 @@ public class RouteSection {
private Integer listPosition; private Integer listPosition;
private RouteType transportType; private TransportType transportType;
private RateType rateType;
private Boolean isPreRun; private Boolean isPreRun;
@ -24,6 +27,13 @@ public class RouteSection {
private Integer toRouteNodeId; private Integer toRouteNodeId;
public RateType getRateType() {
return rateType;
}
public void setRateType(RateType rateType) {
this.rateType = rateType;
}
public Integer getId() { public Integer getId() {
return id; return id;
@ -49,11 +59,11 @@ public class RouteSection {
this.listPosition = listPosition; this.listPosition = listPosition;
} }
public RouteType getTransportType() { public TransportType getTransportType() {
return transportType; return transportType;
} }
public void setTransportType(RouteType transportType) { public void setTransportType(TransportType transportType) {
this.transportType = transportType; this.transportType = transportType;
} }

View file

@ -1,5 +1,5 @@
package de.avatic.lcc.model.premises.route; package de.avatic.lcc.model.premises.route;
public enum RouteSectionType { public enum RouteSectionType {
RAIL, SEA, POST_RUN, ROAD, D2D RAIL, SEA, POST_RUN, ROAD, MATRIX
} }

View file

@ -1,7 +1,7 @@
package de.avatic.lcc.repositories.calculation; package de.avatic.lcc.repositories.calculation;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection; import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.model.calculations.CalculationJobTransportationRuleType;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -63,7 +63,7 @@ public class CalculationJobRouteSectionRepository {
entity.setCalculationJobDestinationId(rs.getInt("calculation_job_destination_id")); entity.setCalculationJobDestinationId(rs.getInt("calculation_job_destination_id"));
// Rule and price type flags // Rule and price type flags
entity.setUsedRule(CalculationJobTransportationRuleType.valueOf(rs.getString("used_rule"))); entity.setTransportType(TransportType.valueOf(rs.getString("transport_type")));
entity.setUnmixedPrice(rs.getBoolean("is_unmixed_price")); entity.setUnmixedPrice(rs.getBoolean("is_unmixed_price"));
entity.setCbmPrice(rs.getBoolean("is_cbm_price")); entity.setCbmPrice(rs.getBoolean("is_cbm_price"));
entity.setWeightPrice(rs.getBoolean("is_weight_price")); entity.setWeightPrice(rs.getBoolean("is_weight_price"));

View file

@ -120,6 +120,8 @@ public class DestinationRepository {
entity.setRepackingCost(rs.getBigDecimal("repacking_cost")); entity.setRepackingCost(rs.getBigDecimal("repacking_cost"));
entity.setHandlingCost(rs.getBigDecimal("handling_cost")); entity.setHandlingCost(rs.getBigDecimal("handling_cost"));
entity.setDisposalCost(rs.getBigDecimal("disposal_cost")); entity.setDisposalCost(rs.getBigDecimal("disposal_cost"));
entity.setGeoLat(rs.getBigDecimal("geo_lat"));
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
return entity; return entity;
} }

View file

@ -70,6 +70,28 @@ public class RouteNodeRepository {
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null; return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
} }
public Optional<RouteNode> getFromNodeBySectionId(Integer id) {
String sql = "SELECT * FROM premise_route_section LEFT JOIN premise_route_node ON premise_route_node.id = premise_route_section.from_route_node_id WHERE premise_route_section.id = ?";
var node = jdbcTemplate.query(sql, new RouteNodeMapper(), id);
if(node.isEmpty())
return Optional.empty();
return Optional.of(node.getFirst());
}
public Optional<RouteNode> getToNodeBySectionId(Integer id) {
String sql = "SELECT * FROM premise_route_section LEFT JOIN premise_route_node ON premise_route_node.id = premise_route_section.to_route_node_id WHERE premise_route_section.id = ?";
var node = jdbcTemplate.query(sql, new RouteNodeMapper(), id);
if(node.isEmpty())
return Optional.empty();
return Optional.of(node.getFirst());
}
private static class RouteNodeMapper implements RowMapper<RouteNode> { private static class RouteNodeMapper implements RowMapper<RouteNode> {
@Override @Override

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.repositories.premise; package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.premises.route.RouteSection; import de.avatic.lcc.model.premises.route.RouteSection;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
@ -44,8 +45,8 @@ public class RouteSectionRepository {
} }
public Integer insert(RouteSection premiseRouteSection) { public Integer insert(RouteSection premiseRouteSection) {
String sql = "INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, is_pre_run, is_main_run, is_post_run, is_outdated) " + String sql = "INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder(); KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> { jdbcTemplate.update(connection -> {
@ -55,10 +56,11 @@ public class RouteSectionRepository {
ps.setInt(3, premiseRouteSection.getToRouteNodeId()); ps.setInt(3, premiseRouteSection.getToRouteNodeId());
ps.setInt(4, premiseRouteSection.getListPosition()); ps.setInt(4, premiseRouteSection.getListPosition());
ps.setString(5, premiseRouteSection.getTransportType().name()); ps.setString(5, premiseRouteSection.getTransportType().name());
ps.setBoolean(6, premiseRouteSection.getPreRun()); ps.setString(6, premiseRouteSection.getRateType().name());
ps.setBoolean(7, premiseRouteSection.getMainRun()); ps.setBoolean(7, premiseRouteSection.getPreRun());
ps.setBoolean(8, premiseRouteSection.getPostRun()); ps.setBoolean(8, premiseRouteSection.getMainRun());
ps.setBoolean(9, premiseRouteSection.getOutdated()); ps.setBoolean(9, premiseRouteSection.getPostRun());
ps.setBoolean(10, premiseRouteSection.getOutdated());
return ps; return ps;
}, keyHolder); }, keyHolder);
@ -82,7 +84,8 @@ public class RouteSectionRepository {
entity.setToRouteNodeId(rs.getInt("to_route_node_id")); entity.setToRouteNodeId(rs.getInt("to_route_node_id"));
entity.setListPosition(rs.getInt("list_position")); entity.setListPosition(rs.getInt("list_position"));
entity.setTransportType(RouteType.valueOf(rs.getString("transport_type"))); entity.setTransportType(TransportType.valueOf(rs.getString("transport_type")));
entity.setRateType(RateType.valueOf(rs.getString("rate_type")));
entity.setPreRun(rs.getBoolean("is_pre_run")); entity.setPreRun(rs.getBoolean("is_pre_run"));
entity.setMainRun(rs.getBoolean("is_main_run")); entity.setMainRun(rs.getBoolean("is_main_run"));

View file

@ -61,7 +61,7 @@ public class UserNodeRepository {
AND p.user_supplier_node_id IS NOT NULL AND p.user_supplier_node_id IS NOT NULL
"""; """;
return jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, periodId)); return jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, periodId);
} }

View file

@ -2,7 +2,7 @@ package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO; import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO;
import de.avatic.lcc.dto.generic.ContainerType; import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.rates.ContainerRate; import de.avatic.lcc.model.rates.ContainerRate;
import de.avatic.lcc.repositories.NodeRepository; import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
@ -106,7 +106,7 @@ public class ContainerRateService {
dto.setId(entity.getId()); dto.setId(entity.getId());
dto.setLeadTime(entity.getLeadTime()); dto.setLeadTime(entity.getLeadTime());
dto.setType(RouteType.valueOf(entity.getType().name())); dto.setType(TransportType.valueOf(entity.getType().name()));
dto.setValidityPeriod(validityPeriodTransformer.toValidityPeriodDTO(validityPeriodRepository.getById(entity.getValidityPeriodId()))); dto.setValidityPeriod(validityPeriodTransformer.toValidityPeriodDTO(validityPeriodRepository.getById(entity.getValidityPeriodId())));
dto.setLeadTime(entity.getLeadTime()); dto.setLeadTime(entity.getLeadTime());
dto.setOrigin(nodeTransformer.toNodeDTO(nodeRepository.getById(entity.getFromNodeId()).orElseThrow())); dto.setOrigin(nodeTransformer.toNodeDTO(nodeRepository.getById(entity.getFromNodeId()).orElseThrow()));

View file

@ -76,6 +76,8 @@ public class DestinationService {
destination.setRepackingCost(null); destination.setRepackingCost(null);
destination.setRateD2d(BigDecimal.ZERO); destination.setRateD2d(BigDecimal.ZERO);
destination.setId(destinationRepository.insert(destination)); destination.setId(destinationRepository.insert(destination));
destination.setGeoLat(destinationNode.getGeoLat());
destination.setGeoLng(destinationNode.getGeoLng());
Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow(); Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow();
findRouteAndSave(destination.getId(), destinationNode, source, premise.getSupplierNodeId() == null); findRouteAndSave(destination.getId(), destinationNode, source, premise.getSupplierNodeId() == null);

View file

@ -12,9 +12,14 @@ import org.apache.poi.ss.usermodel.SheetVisibility;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource; import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@Service @Service
public class BulkExportService { public class BulkExportService {
@ -42,54 +47,56 @@ public class BulkExportService {
this.sheetPassword = sheetPassword; this.sheetPassword = sheetPassword;
} }
public InputStreamSource generateExport(BulkFileType bulkFileType) { public InputStreamSource generateExport(BulkFileType bulkFileType) throws IOException {
return generateExport(bulkFileType, validityPeriodRepository.getValidPeriodId()); return generateExport(bulkFileType, validityPeriodRepository.getValidPeriodId());
} }
public InputStreamSource generateExport(BulkFileType bulkFileType, Integer periodId) { public InputStreamSource generateExport(BulkFileType bulkFileType, Integer periodId) throws IOException {
Workbook workbook = new XSSFWorkbook(); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Sheet worksheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); Workbook workbook = new XSSFWorkbook();
Sheet worksheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName());
CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook); CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook);
//TODO: check if a parallel task is needed. //TODO: check if a parallel task is needed.
if (bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) {
var hiddenCountrySheet = workbook.createSheet(HiddenTableType.COUNTRY_HIDDEN_TABLE.getSheetName());
hiddenCountryExcelMapper.fillSheet(hiddenCountrySheet, style);
hiddenCountrySheet.protectSheet(sheetPassword);
workbook.setSheetVisibility(workbook.getSheetIndex(hiddenCountrySheet), SheetVisibility.VERY_HIDDEN);
} else if (bulkFileType.equals(BulkFileType.CONTAINER_RATE) || bulkFileType.equals(BulkFileType.PACKAGING)) {
var hiddenNodeSheet = workbook.createSheet(HiddenTableType.NODE_HIDDEN_TABLE.getSheetName());
hiddenNodeExcelMapper.fillSheet(hiddenNodeSheet, style, BulkFileType.PACKAGING.equals(bulkFileType));
hiddenNodeSheet.protectSheet(sheetPassword);
workbook.setSheetVisibility(workbook.getSheetIndex(hiddenNodeSheet), SheetVisibility.VERY_HIDDEN);
}
if(bulkFileType.equals(BulkFileType.COUNTRY_MATRIX) || bulkFileType.equals(BulkFileType.NODE)) // Create headers based on the bulk file type
{ switch (bulkFileType) {
var hiddenCountrySheet = workbook.createSheet(HiddenTableType.COUNTRY_HIDDEN_TABLE.getSheetName()); case CONTAINER_RATE:
hiddenCountryExcelMapper.fillSheet(hiddenCountrySheet, style); containerRateExcelMapper.fillSheet(worksheet, style, periodId);
hiddenCountrySheet.protectSheet(sheetPassword); break;
workbook.setSheetVisibility(workbook.getSheetIndex(hiddenCountrySheet), SheetVisibility.VERY_HIDDEN); case COUNTRY_MATRIX:
matrixRateExcelMapper.fillSheet(worksheet, style, periodId);
break;
case MATERIAL:
materialExcelMapper.fillSheet(worksheet, style);
break;
case PACKAGING:
packagingExcelMapper.fillSheet(worksheet, style);
break;
case NODE:
nodeExcelMapper.fillSheet(worksheet, style);
break;
}
// Return the Excel file as an InputStreamSource
workbook.write(outputStream);
return new ByteArrayResource(outputStream.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Failed to generate template", e);
} }
else if(bulkFileType.equals(BulkFileType.CONTAINER_RATE) || bulkFileType.equals(BulkFileType.PACKAGING))
{
var hiddenNodeSheet = workbook.createSheet(HiddenTableType.NODE_HIDDEN_TABLE.getSheetName());
hiddenNodeExcelMapper.fillSheet(hiddenNodeSheet, style, BulkFileType.PACKAGING.equals(bulkFileType));
hiddenNodeSheet.protectSheet(sheetPassword);
workbook.setSheetVisibility(workbook.getSheetIndex(hiddenNodeSheet), SheetVisibility.VERY_HIDDEN);
}
// Create headers based on the bulk file type
switch (bulkFileType) {
case CONTAINER_RATE:
containerRateExcelMapper.fillSheet(worksheet, style, periodId);
break;
case COUNTRY_MATRIX:
matrixRateExcelMapper.fillSheet(worksheet, style, periodId);
break;
case MATERIAL:
materialExcelMapper.fillSheet(worksheet, style);
break;
case PACKAGING:
packagingExcelMapper.fillSheet(worksheet, style);
break;
case NODE:
nodeExcelMapper.fillSheet(worksheet, style);
break;
}
return null;
} }
} }

View file

@ -93,6 +93,7 @@ public class TemplateExportService {
} }
// Return the Excel file as an InputStreamSource // Return the Excel file as an InputStreamSource
workbook.write(outputStream);
return new ByteArrayResource(outputStream.toByteArray()); return new ByteArrayResource(outputStream.toByteArray());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to generate template", e); throw new RuntimeException("Failed to generate template", e);

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.service.calculation; package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.generic.RouteType; import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.nodes.Node; import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.premises.route.*; import de.avatic.lcc.model.premises.route.*;
import de.avatic.lcc.model.properties.SystemPropertyMappingId; import de.avatic.lcc.model.properties.SystemPropertyMappingId;
@ -182,6 +183,7 @@ public class RoutingService {
RouteSection routeSection = new RouteSection(); RouteSection routeSection = new RouteSection();
routeSection.setTransportType(mapRouteType(section)); routeSection.setTransportType(mapRouteType(section));
routeSection.setRateType(mapRateType(section));
routeSection.setFromRouteNodeId(section.getFromNode().getId()); routeSection.setFromRouteNodeId(section.getFromNode().getId());
routeSection.setToRouteNodeId(section.getToNode().getId()); routeSection.setToRouteNodeId(section.getToNode().getId());
routeSection.setMainRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN)); routeSection.setMainRun(section.getType().equals(TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN));
@ -194,17 +196,25 @@ public class RoutingService {
} }
private RouteType mapRouteType(TemporaryRateObject rate) { private RateType mapRateType(TemporaryRateObject rate) {
if (Objects.requireNonNull(rate.getType()) == TemporaryRateObject.TemporaryRateObjectType.MATRIX) {
return RateType.MATRIX;
}
return RateType.CONTAINER;
}
private TransportType mapRouteType(TemporaryRateObject rate) {
switch(rate.getType()) { switch(rate.getType()) {
case MATRIX, CONTAINER -> { case CONTAINER, MATRIX -> {
return RouteType.ROAD; return TransportType.ROAD;
} }
case POST_RUN -> { case POST_RUN -> {
return RouteType.POST_RUN; return TransportType.POST_RUN;
} }
case MAIN_RUN -> { case MAIN_RUN -> {
return RouteType.valueOf(rate.getContainerRateTye().name()); return TransportType.valueOf(rate.getContainerRateTye().name());
} }
} }

View file

@ -1,17 +1,184 @@
package de.avatic.lcc.service.report; package de.avatic.lcc.service.report;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.dto.report.ReportDestinationDTO;
import de.avatic.lcc.dto.report.ReportEntryDTO;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Service @Service
public class ExcelReportingService { public class ExcelReportingService {
public ByteArrayInputStream generateExcelReport(Integer materialId, List<Integer> nodeIds) { private final ReportingService reportingService;
private final HeaderCellStyleProvider headerCellStyleProvider;
private final HeaderGenerator headerGenerator;
return null; public ExcelReportingService(ReportingService reportingService, HeaderCellStyleProvider headerCellStyleProvider, HeaderGenerator headerGenerator) {
this.reportingService = reportingService;
this.headerCellStyleProvider = headerCellStyleProvider;
this.headerGenerator = headerGenerator;
}
public ByteArrayResource generateExcelReport(Integer materialId, List<Integer> nodeIds) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
var reports = reportingService.getReport(materialId, nodeIds);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("report");
CellStyle headerStyle = headerCellStyleProvider.createHeaderCellStyle(workbook);
headerGenerator.generateHeader(sheet, reports.stream().map(ReportDTO::getSupplier).map(NodeDTO::getName).toArray(String[]::new), headerStyle);
List<ReportFlattener> flatteners = reports.stream().map(ReportFlattener::new).toList();
int rowIdx = 1;
while(true) {
boolean hasData = false;
var row = sheet.createRow(rowIdx);
int cellIdx = 0;
for(ReportFlattener flattener : flatteners) {
if(cellIdx == 0) {
row.createCell(cellIdx++).setCellValue(flattener.getHeader(rowIdx));
hasData = true;
}
row.createCell(cellIdx++).setCellValue(flattener.getCell(rowIdx));
}
rowIdx++;
if(!hasData) break;
}
// Return the Excel file as an InputStreamSource
workbook.write(outputStream);
return new ByteArrayResource(outputStream.toByteArray());
} catch (
IOException e) {
throw new RuntimeException("Failed to generate template", e);
}
}
private void mapToCell(Sheet sheet, ReportDTO reportDTO, Cell cell, int rowIdx) {
}
private static class ReportFlattener {
private static final String SUPPLIER_NAME = "Supplier";
private static final String SUPPLIER_ADDRESS = "Address";
private static final String DESTINATION_NAME = "Destination";
private static final String DESTINATION_ADDRESS = "Address";
private static final String DESTINATION_QUANTITY = "Annual quantity";
private static final String DESTINATION_HS_CODE = "HS code";
private static final String DESTINATION_TARIFF_RATE = "Tariff rate";
private static final String DESTINATION_OVERSHARE = "Oversea share";
private static final String DESTINATION_AIR_FREIGHT_SHARE = "Air freight share";
private static final String DESTINATION_TRANSPORT_TIME = "Transport time";
private static final String DESTINATION_SAFETY_STOCK = "Safety stock";
private static final String DESTINATION_WIDTH = "HU Width";
private static final String DESTINATION_HEIGHT = "HU Height";
private static final String DESTINATION_LENGTH = "HU Length";
private static final String DESTINATION_WEIGHT = "HU Weight";
private static final String DESTINATION_HU_UNIT_COUNT = "HU Unit count";
private static final String DESTINATION_WEIGHT_UNIT = "HU Weight unit";
private static final String DESTINATION_DIMENSION_UNIT = "HU Unit";
private static final String DESTINATION_CONTAINER_LAYER = "Container layers";
private static final String DESTINATION_CONTAINER_UNIT_COUNT = "Container unit count";
private static final String DESTINATION_CONTAINER_UTILIZATION = "Container utilization";
private static final String DESTINATION_CONTAINER_TYPE = "Container type";
private static final String DESTINATION_CONTAINER_WEIGHT_EXCEEDED = "Container weight exceeded";
private static final String DESTINATION_CONTAINER_RATE = "Container rate";
private static final String DESTINATION_MIXED = "Mixed";
private final ReportDTO report;
private List<String> data = new ArrayList<>();
private List<String> dataHeader = new ArrayList<>();
public ReportFlattener(ReportDTO report) {
this.report = report;
flatten();
}
private void flatten() {
addData(SUPPLIER_NAME, report.getSupplier().getName());
addData(SUPPLIER_ADDRESS, report.getSupplier().getAddress());
report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName)));
report.getRisk().keySet().forEach(riskName -> addData(riskName, report.getRisk().get(riskName)));
report.getDestinations().forEach(this::flattenDestination);
}
private void flattenDestination(ReportDestinationDTO destination) {
addData(DESTINATION_NAME, destination.getDestination().getName());
addData(DESTINATION_ADDRESS, destination.getDestination().getAddress());
addData(DESTINATION_QUANTITY, destination.getAnnualQuantity().toString());
addData(DESTINATION_HS_CODE, destination.getHsCode());
addData(DESTINATION_TARIFF_RATE, destination.getTariffRate().toString());
addData(DESTINATION_OVERSHARE, destination.getOverseaShare().toString());
addData(DESTINATION_AIR_FREIGHT_SHARE, destination.getAirFreightShare().toString());
addData(DESTINATION_TRANSPORT_TIME, destination.getTransportTime().toString());
addData(DESTINATION_SAFETY_STOCK, destination.getSafetyStock().toString());
addData(DESTINATION_WIDTH, destination.getWidth().toString());
addData(DESTINATION_HEIGHT, destination.getHeight().toString());
addData(DESTINATION_LENGTH, destination.getLength().toString());
addData(DESTINATION_DIMENSION_UNIT, destination.getDimensionUnit().toString());
addData(DESTINATION_WEIGHT, destination.getWeight().toString());
addData(DESTINATION_WEIGHT_UNIT, destination.getWeightUnit().toString());
addData(DESTINATION_HU_UNIT_COUNT, destination.getHuUnitCount().toString());
addData(DESTINATION_CONTAINER_LAYER, destination.getLayer().toString());
addData(DESTINATION_CONTAINER_UNIT_COUNT, destination.getUnitCount().toString());
addData(DESTINATION_CONTAINER_UTILIZATION, destination.getUtilization().toString());
addData(DESTINATION_CONTAINER_TYPE, destination.getType().toString());
addData(DESTINATION_CONTAINER_WEIGHT_EXCEEDED, destination.getWeightExceeded().toString());
addData(DESTINATION_CONTAINER_RATE, destination.getRate().toString());
addData(DESTINATION_MIXED, destination.getMixed().toString());
}
private void addData(String header, String data) {
this.dataHeader.add(header);
this.data.add(data);
}
private void addData(String header, ReportEntryDTO data) {
this.dataHeader.add(header);
this.data.add(data.getTotal() + " (" + data.getPercentage().doubleValue()*100 + "%)");
}
public String getCell(int rowIdx) {
return data.get(rowIdx);
}
public String getHeader(int rowIdx) {
return dataHeader.get(rowIdx);
}
} }
} }

View file

@ -2,12 +2,8 @@ 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.report.ReportDTO; import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.model.calculations.CalculationJobDestination;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.repositories.NodeRepository; import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRepository; import de.avatic.lcc.repositories.calculation.CalculationJobRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository; import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository; import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer;
@ -16,7 +12,6 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
@Service @Service
@ -63,7 +58,6 @@ public class ReportingService {
var periodId = period.get().getId(); var periodId = period.get().getId();
var jobs = nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJob(periodId, nodeId,materialId)).filter(Optional::isPresent).map(Optional::get).toList(); var jobs = nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJob(periodId, nodeId,materialId)).filter(Optional::isPresent).map(Optional::get).toList();
return jobs.stream().map(reportTransformer::toReportDTO).toList(); return jobs.stream().map(reportTransformer::toReportDTO).toList();
} }

View file

@ -1,10 +0,0 @@
package de.avatic.lcc.service.transformer.premise;
import org.springframework.stereotype.Service;
@Service
public class RouteSectionTransformer {
public RouteSectionDTO
}

View file

@ -1,8 +1,7 @@
package de.avatic.lcc.service.transformer.premise; package de.avatic.lcc.service.transformer.premise;
import de.avatic.lcc.dto.calculation.RouteDTO; import de.avatic.lcc.dto.calculation.RouteDTO;
import de.avatic.lcc.dto.calculation.TransitNodeDTO; import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.dto.generic.RouteType;
import de.avatic.lcc.model.premises.route.RouteNode; import de.avatic.lcc.model.premises.route.RouteNode;
import de.avatic.lcc.model.premises.route.RouteSection; import de.avatic.lcc.model.premises.route.RouteSection;
import de.avatic.lcc.repositories.premise.RouteNodeRepository; import de.avatic.lcc.repositories.premise.RouteNodeRepository;
@ -39,7 +38,7 @@ public class RouteTransformer {
List<RouteSection> sections = routeSectionRepository.getByRouteId(entity.getId()); List<RouteSection> sections = routeSectionRepository.getByRouteId(entity.getId());
dto.setTransitNodes(getRouteNodes(sections).stream().map(nodeTransformer::toNodeDTO).toList()); dto.setTransitNodes(getRouteNodes(sections).stream().map(nodeTransformer::toNodeDTO).toList());
dto.setType(RouteType.valueOf(sections.stream().filter(RouteSection::getMainRun).findFirst().orElseThrow().getTransportType().name())); dto.setType(TransportType.valueOf(sections.stream().filter(RouteSection::getMainRun).findFirst().orElseThrow().getTransportType().name()));
return dto; return dto;
} }

View file

@ -1,7 +1,12 @@
package de.avatic.lcc.service.transformer.report; package de.avatic.lcc.service.transformer.report;
import de.avatic.lcc.dto.generic.ContainerType; import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.report.*; import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.NodeType;
import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.dto.report.ReportDestinationDTO;
import de.avatic.lcc.dto.report.ReportEntryDTO;
import de.avatic.lcc.dto.report.ReportSectionDTO;
import de.avatic.lcc.model.calculations.CalculationJob; import de.avatic.lcc.model.calculations.CalculationJob;
import de.avatic.lcc.model.calculations.CalculationJobDestination; import de.avatic.lcc.model.calculations.CalculationJobDestination;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection; import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
@ -11,6 +16,7 @@ import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepositor
import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository; import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.premise.DestinationRepository; import de.avatic.lcc.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository; import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
import de.avatic.lcc.repositories.premise.RouteSectionRepository; import de.avatic.lcc.repositories.premise.RouteSectionRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -27,19 +33,17 @@ public class ReportTransformer {
private final CalculationJobDestinationRepository calculationJobDestinationRepository; private final CalculationJobDestinationRepository calculationJobDestinationRepository;
private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository; private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository;
private final PremiseRepository premiseRepository; private final PremiseRepository premiseRepository;
private final DestinationRepository destinationRepository;
private final NodeRepository nodeRepository; private final NodeRepository nodeRepository;
private final RouteSectionRepository routeSectionRepository;
private final NodeTransformer nodeTransformer; private final NodeTransformer nodeTransformer;
private final RouteNodeRepository routeNodeRepository;
public ReportTransformer(CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PremiseRepository premiseRepository, DestinationRepository destinationRepository, NodeRepository nodeRepository, RouteSectionRepository routeSectionRepository, NodeTransformer nodeTransformer) { public ReportTransformer(CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PremiseRepository premiseRepository, NodeRepository nodeRepository, NodeTransformer nodeTransformer, RouteNodeRepository routeNodeRepository) {
this.calculationJobDestinationRepository = calculationJobDestinationRepository; this.calculationJobDestinationRepository = calculationJobDestinationRepository;
this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository; this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository;
this.premiseRepository = premiseRepository; this.premiseRepository = premiseRepository;
this.destinationRepository = destinationRepository;
this.nodeRepository = nodeRepository; this.nodeRepository = nodeRepository;
this.routeSectionRepository = routeSectionRepository;
this.nodeTransformer = nodeTransformer; this.nodeTransformer = nodeTransformer;
this.routeNodeRepository = routeNodeRepository;
} }
public ReportDTO toReportDTO(CalculationJob job) { public ReportDTO toReportDTO(CalculationJob job) {
@ -53,98 +57,84 @@ public class ReportTransformer {
reportDTO.setCost(getCostMap(job, destinations)); reportDTO.setCost(getCostMap(job, destinations));
reportDTO.setRisk(getRisk(job, destinations)); reportDTO.setRisk(getRisk(job, destinations));
reportDTO.setDestination(destinations.stream().map(d -> getDestinationDTO(d, sections.get(d.getId()), premise)).toList());
ReportPremisesDTO premisesDTO = new ReportPremisesDTO(); if(!reportDTO.getDestinations().isEmpty()) {
premisesDTO.setQuantities(destinations.stream().map(q -> getQuantitiesDTO(q, sections.get(q.getId()))).toList()); var source = reportDTO.getDestinations().getFirst().getSections().stream().map(ReportSectionDTO::getFromNode).filter(n -> n.getTypes().contains(NodeType.SOURCE)).findFirst().orElseThrow();
reportDTO.setSupplier(source);
premisesDTO.setContainer(getContainerDTO(destinations, sections, premise)); }
premisesDTO.setPackaging(getPackagingDTO(destinations, premise));
premisesDTO.setQuotaShare(getQuotaShare(destinations, sections, premise));
premisesDTO.setHsCode(premise.getHsCode());
reportDTO.setPremises(premisesDTO);
return reportDTO; return reportDTO;
} }
private ReportQuotaShareDTO getQuotaShare(List<CalculationJobDestination> destinations, Map<Integer, List<CalculationJobRouteSection>> sections, Premise premise) { private ReportDestinationDTO getDestinationDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections, Premise premise) {
ReportQuotaShareDTO quotaShare = new ReportQuotaShareDTO(); var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow();
var safetyStock = destinations.stream().map(CalculationJobDestination::getSafetyStock).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destinations.size()), 2, RoundingMode.HALF_UP);
double transportTime = (double) (destinations.stream().map(CalculationJobDestination::getId).map(sections::get).mapToInt(l -> l.stream().mapToInt(CalculationJobRouteSection::getTransitTime).sum()).sum()) / sections.size();
var airfreight = destinations.stream().map(CalculationJobDestination::getAirFreightShare).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destinations.size()), 2, RoundingMode.HALF_UP);
quotaShare.setOverseaShare(premise.getOverseaShare().doubleValue());
quotaShare.setSafetyStock(safetyStock.doubleValue());
quotaShare.setTransportTime(transportTime);
quotaShare.setAirFreightShare(airfreight.doubleValue());
return quotaShare;
}
private ReportPackagingDTO getPackagingDTO(List<CalculationJobDestination> destination, Premise premise) {
ReportPackagingDTO packaging = new ReportPackagingDTO();
var dimensionUnit = premise.getDimensionUnit(); var dimensionUnit = premise.getDimensionUnit();
var weightUnit = premise.getWeightUnit(); var weightUnit = premise.getWeightUnit();
packaging.setDimensionUnit(dimensionUnit); ReportDestinationDTO destinationDTO = new ReportDestinationDTO();
packaging.setWeightUnit(weightUnit); destinationDTO.setSections(sections.stream().map(this::getSection).toList());
packaging.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()).doubleValue());
packaging.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()).doubleValue());
packaging.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()).doubleValue());
packaging.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()).doubleValue());
packaging.setLayer(destination.stream().mapToInt(CalculationJobDestination::getLayerCount).sum()/destination.size()); var totalAnnualCost = sections.stream().map(CalculationJobRouteSection::getAnnualCost).reduce(BigDecimal.ZERO, BigDecimal::add);
packaging.setUnitCount(premise.getHuUnitCount()); destinationDTO.getSections().forEach(s -> {
s.getCost().setPercentage(s.getCost().getTotal().doubleValue()/totalAnnualCost.doubleValue());
});
destinationDTO.getSections().forEach(s -> {
s.getDuration().setPercentage(s.getDuration().getTotal().doubleValue()/totalAnnualCost.doubleValue());
});
return packaging; destinationDTO.setDestination(nodeTransformer.toNodeDTO(destinationNode));
}
private ReportContainerDTO getContainerDTO(List<CalculationJobDestination> destination, List<CalculationJobRouteSection> sections, Premise premise) { destinationDTO.setDimensionUnit(dimensionUnit);
ReportContainerDTO container = new ReportContainerDTO(); destinationDTO.setWeightUnit(weightUnit);
destinationDTO.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()).doubleValue());
destinationDTO.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()).doubleValue());
destinationDTO.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()).doubleValue());
destinationDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()).doubleValue());
destinationDTO.setLayer(destination.getLayerCount());
destinationDTO.setUnitCount(premise.getHuUnitCount());
destinationDTO.setOverseaShare(premise.getOverseaShare().doubleValue());
destinationDTO.setSafetyStock(destination.getSafetyStock().doubleValue());
destinationDTO.setTransportTime(destination.getTotalTransitTime().doubleValue());
destinationDTO.setAirFreightShare(destination.getAirFreightShare().doubleValue());
CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null); CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null);
container.setMixed(premise.getHuMixable()); destinationDTO.setMixed(premise.getHuMixable());
container.setRate(mainRun == null ? 0 : mainRun.getRate()); destinationDTO.setRate(mainRun == null ? 0 : mainRun.getRate());
container.setType(ContainerType.valueOf(destination.getFirst().getTransportationType())); destinationDTO.setType(ContainerType.valueOf(destination.getTransportationType()));
container.setUtilization(destination.getFirst().getContainerUtilization()); destinationDTO.setUtilization(destination.getContainerUtilization());
container.setUnitCount(premise.getHuUnitCount()); destinationDTO.setUnitCount(premise.getHuUnitCount());
container.setWeightExceeded(destination.getFirst().getTransportWeightExceeded()); destinationDTO.setWeightExceeded(destination.getTransportWeightExceeded());
return container; destinationDTO.setHsCode(premise.getHsCode());
return destinationDTO;
} }
private ReportQuantityDTO getQuantitiesDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections) { private ReportSectionDTO getSection(CalculationJobRouteSection section) {
ReportQuantityDTO quantity = new ReportQuantityDTO(); ReportSectionDTO sectionDTO = new ReportSectionDTO();
var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow(); sectionDTO.setId(section.getId());
sectionDTO.setTransportType(section.getTransportType());
sectionDTO.setFromNode(nodeTransformer.toNodeDTO(routeNodeRepository.getFromNodeBySectionId(section.getPremiseRouteSectionId()).orElseThrow()));
sectionDTO.setRateType(section.getRateType());
quantity.setQuantity(destination.getAnnualAmount()); var duration = new ReportEntryDTO();
quantity.setDestination(nodeTransformer.toNodeDTO(destinationNode)); duration.setTotal(section.getTransitTime());
quantity.setRoute(sections.stream().map(this::getRouteEntryDTO).toList()); sectionDTO.setDuration(duration);
var total = quantity.getRoute().stream().map(ReportRouteEntryDTO::getCost).map(ReportEntryDTO::getTotal).mapToDouble(Number::doubleValue).sum(); var cost = new ReportEntryDTO();
quantity.getRoute().forEach(r -> r.getCost().setPercentage(BigDecimal.valueOf(r.getCost().getTotal().doubleValue() / total).setScale(2, RoundingMode.HALF_UP))); cost.setTotal(section.getAnnualCost());
sectionDTO.setCost(cost);
return quantity; return sectionDTO;
} }
private ReportRouteEntryDTO getRouteEntryDTO(CalculationJobRouteSection routeSection) {
ReportRouteEntryDTO routeEntry = new ReportRouteEntryDTO();
var premiseSection = routeSectionRepository.getById(routeSection.getPremiseRouteSectionId()).orElseThrow();
var sectionNode = nodeRepository.getById(premiseSection.getFromRouteNodeId()).orElseThrow();
routeEntry.setName(sectionNode.getExternalMappingId());
routeEntry.setType(premiseSection.getTransportType());
routeEntry.setCost(new ReportEntryDTO());
routeEntry.getCost().setTotal(routeSection.getAnnualCost());
return routeEntry;
}
private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination) { private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination) {
Map<String, ReportEntryDTO> risk = new HashMap<>(); Map<String, ReportEntryDTO> risk = new HashMap<>();
@ -158,7 +148,7 @@ public class ReportTransformer {
risk.put("air_freight_cost", airfreight); risk.put("air_freight_cost", airfreight);
ReportEntryDTO worst = new ReportEntryDTO(); ReportEntryDTO worst = new ReportEntryDTO();
var worstValue = destination.stream().map(CalculationJobDestination::getAnnualRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add); var worstValue = destination.stream().map(CalculationJobDestination::getAnnualRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add);
worst.setTotal(worstValue); worst.setTotal(worstValue);
worst.setPercentage(worstValue.divide(totalValue, 2, RoundingMode.HALF_UP)); worst.setPercentage(worstValue.divide(totalValue, 2, RoundingMode.HALF_UP));
risk.put("worst_case_cost", worst); risk.put("worst_case_cost", worst);

View file

@ -378,6 +378,8 @@ CREATE TABLE IF NOT EXISTS premise_destination
repacking_cost DECIMAL(15, 2) DEFAULT NULL, repacking_cost DECIMAL(15, 2) DEFAULT NULL,
handling_cost DECIMAL(15, 2) DEFAULT NULL, handling_cost DECIMAL(15, 2) DEFAULT NULL,
disposal_cost DECIMAL(15, 2) DEFAULT NULL, disposal_cost DECIMAL(15, 2) DEFAULT NULL,
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
FOREIGN KEY (premise_id) REFERENCES premise (id), FOREIGN KEY (premise_id) REFERENCES premise (id),
FOREIGN KEY (destination_node_id) REFERENCES node (id), FOREIGN KEY (destination_node_id) REFERENCES node (id),
INDEX idx_destination_node_id (destination_node_id), INDEX idx_destination_node_id (destination_node_id),
@ -424,8 +426,9 @@ CREATE TABLE IF NOT EXISTS premise_route_section
to_route_node_id INT NOT NULL, to_route_node_id INT NOT NULL,
list_position INT NOT NULL, list_position INT NOT NULL,
transport_type CHAR(16) CHECK (transport_type IN transport_type CHAR(16) CHECK (transport_type IN
('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')), ('RAIL', 'SEA', 'ROAD', 'POST-RUN')),
rate_d2d DECIMAL(15, 2) COMMENT 'if transport type is D2D this stores the rate in EUR', rate_type CHAR(16) CHECK (transport_type IN
('CONTAINER', 'MATRIX')),
is_pre_run BOOLEAN DEFAULT FALSE, is_pre_run BOOLEAN DEFAULT FALSE,
is_main_run BOOLEAN DEFAULT FALSE, is_main_run BOOLEAN DEFAULT FALSE,
is_post_run BOOLEAN DEFAULT FALSE, is_post_run BOOLEAN DEFAULT FALSE,
@ -433,10 +436,6 @@ CREATE TABLE IF NOT EXISTS premise_route_section
FOREIGN KEY (premise_route_id) REFERENCES premise_route (id), FOREIGN KEY (premise_route_id) REFERENCES premise_route (id),
FOREIGN KEY (from_route_node_id) REFERENCES premise_route_node (id), FOREIGN KEY (from_route_node_id) REFERENCES premise_route_node (id),
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id), FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),
CONSTRAINT chk_d2d CHECK ((transport_type != 'D2D' AND rate_d2d IS NULL) OR
(transport_type = 'D2D' AND rate_d2d IS NOT NULL)),
CONSTRAINT chk_post_run CHECK ((transport_type = 'POST-RUN' AND is_post_run IS TRUE) OR
(transport_type != 'POST-RUN' AND rate_d2d IS FALSE)),
CONSTRAINT chk_main_run CHECK ((transport_type = 'RAIL' OR transport_type = 'SEA') AND is_main_run IS TRUE), CONSTRAINT chk_main_run CHECK ((transport_type = 'RAIL' OR transport_type = 'SEA') AND is_main_run IS TRUE),
INDEX idx_premise_route_id (premise_route_id), INDEX idx_premise_route_id (premise_route_id),
INDEX idx_from_route_node_id (from_route_node_id), INDEX idx_from_route_node_id (from_route_node_id),
@ -524,8 +523,8 @@ CREATE TABLE IF NOT EXISTS calculation_job_route_section
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
premise_route_section_id INT NOT NULL, premise_route_section_id INT NOT NULL,
calculation_job_destination_id INT NOT NULL, calculation_job_destination_id INT NOT NULL,
used_rule CHAR(8) CHECK (used_rule IN transport_type CHAR(16) CHECK (transport_type IN
('CONTAINER', 'MATRIX')), ('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')),
is_unmixed_price BOOLEAN DEFAULT FALSE, is_unmixed_price BOOLEAN DEFAULT FALSE,
is_cbm_price BOOLEAN DEFAULT FALSE, is_cbm_price BOOLEAN DEFAULT FALSE,
is_weight_price BOOLEAN DEFAULT FALSE, is_weight_price BOOLEAN DEFAULT FALSE,