Refactor container rate handling and enhance calculation logic
Replaced `ContainerRateType` with `TransportType` for streamlined rate handling across multiple services. Introduced new `ContainerCalculationResult` logic, integrated weight/volume constraints, and added container type-specific properties. Updated schemas to include geolocation and country ID data for premises and destinations. Improved consistency and correctness in service logic.
This commit is contained in:
parent
d2e271ed36
commit
9bbc648757
35 changed files with 1189 additions and 136 deletions
|
|
@ -1,9 +1,251 @@
|
|||
package de.avatic.lcc.calculationmodel;
|
||||
|
||||
import de.avatic.lcc.dto.generic.ContainerType;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
|
||||
/**
|
||||
* Represents the result of a container calculation process, including details
|
||||
* about the utilized packaging dimensions, container type, and various metrics
|
||||
* such as layer count, structural arrangement, and utilization.
|
||||
* <p>
|
||||
* This class provides methods to retrieve metrics related to container
|
||||
* utilization, such as the total number of handling units and whether the
|
||||
* allowed weight limit has been exceeded.
|
||||
*/
|
||||
public class ContainerCalculationResult {
|
||||
|
||||
|
||||
private Integer maxContainerWeight;
|
||||
|
||||
/**
|
||||
* The dimensions of the handling unit (HU), including weight,
|
||||
* height, width, and length specifications. Includes a tolerance according
|
||||
* to system property
|
||||
*/
|
||||
private PackagingDimension hu;
|
||||
|
||||
/**
|
||||
* The number of layers in the container calculation result,
|
||||
* representing the vertical stacking level.
|
||||
*/
|
||||
private int layer;
|
||||
|
||||
/**
|
||||
* The structural arrangement of the handling units within
|
||||
* the container, represented as a descriptive string.
|
||||
*/
|
||||
private String structure;
|
||||
|
||||
/**
|
||||
* Total count of handling units (HU) that are recognized
|
||||
* based on the calculation.
|
||||
*/
|
||||
private int huUnitCount;
|
||||
|
||||
/**
|
||||
* Indicates whether the calculated weight of the container exceeds
|
||||
* the permitted limit for the container type.
|
||||
*/
|
||||
private boolean weightExceeded;
|
||||
|
||||
/**
|
||||
* Type of container used in the calculation, e.g., TEU/FEU.
|
||||
*/
|
||||
private ContainerType containerType;
|
||||
|
||||
/**
|
||||
* Constructs a new ContainerCalculationResult with the provided parameters.
|
||||
*
|
||||
* @param huUnitCount The total number of handling units.
|
||||
* @param layers The total number of layers to stack.
|
||||
* @param structure A textual description of the container's arrangement.
|
||||
* @param weightExceeded Boolean indicating if the allowed weight is exceeded.
|
||||
* @param containerType The type of container being used in the process.
|
||||
* @param hu The packaging dimensions of the handling unit (incl. tolerance).
|
||||
*/
|
||||
public ContainerCalculationResult(int huUnitCount, int layers, String structure, boolean weightExceeded, ContainerType containerType, PackagingDimension hu, Integer maxContainerWeight) {
|
||||
this.huUnitCount = huUnitCount;
|
||||
this.layer = layers;
|
||||
this.structure = structure;
|
||||
this.weightExceeded = weightExceeded;
|
||||
this.containerType = containerType;
|
||||
this.maxContainerWeight = maxContainerWeight;
|
||||
this.hu = hu;
|
||||
}
|
||||
|
||||
public Integer getMaxContainerWeight() {
|
||||
return maxContainerWeight;
|
||||
}
|
||||
|
||||
public void setMaxContainerWeight(Integer maxContainerWeight) {
|
||||
this.maxContainerWeight = maxContainerWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PackagingDimension object representing the handling unit attributes.
|
||||
*
|
||||
* @param hu A PackagingDimension object containing weight and size information of the handling unit.
|
||||
*/
|
||||
public void setHu(PackagingDimension hu) {
|
||||
this.hu = hu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the PackagingDimension object representing the handling unit attributes.
|
||||
*
|
||||
* @return A PackagingDimension object containing weight and size information of the handling unit.
|
||||
*/
|
||||
public PackagingDimension getHu() {
|
||||
return hu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for ContainerCalculationResult.
|
||||
* Initializes an empty object with no predefined values.
|
||||
*/
|
||||
public ContainerCalculationResult() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of container used in this calculation.
|
||||
*
|
||||
* @return The container type (e.g., TEU, FEU).
|
||||
*/
|
||||
public ContainerType getContainerType() {
|
||||
return containerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of container used in this calculation.
|
||||
*
|
||||
* @param containerType The container type (e.g., TEU, FEU).
|
||||
*/
|
||||
public void setContainerType(ContainerType containerType) {
|
||||
this.containerType = containerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the weight limit is exceeded for the container.
|
||||
*
|
||||
* @return True if the weight limit is exceeded, false otherwise.
|
||||
*/
|
||||
public boolean isWeightExceeded() {
|
||||
return weightExceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of whether the weight limit is exceeded.
|
||||
*
|
||||
* @param weightExceeded A boolean indicating if the weight limit is exceeded.
|
||||
*/
|
||||
public void setWeightExceeded(boolean weightExceeded) {
|
||||
this.weightExceeded = weightExceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of layers in the container.
|
||||
*
|
||||
* @return The number of layers.
|
||||
*/
|
||||
public int getLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of layers in the container.
|
||||
*
|
||||
* @param layer The number of layers to set.
|
||||
*/
|
||||
public void setLayer(int layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the structural arrangement of handling units in the container.
|
||||
*
|
||||
* @return A string specifying the arrangement structure.
|
||||
*/
|
||||
public String getStructure() {
|
||||
return structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the structural arrangement of handling units in the container.
|
||||
*
|
||||
* @param structure A string specifying the arrangement structure.
|
||||
*/
|
||||
public void setStructure(String structure) {
|
||||
this.structure = structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of handling units in the container calculation result.
|
||||
*
|
||||
* @return The total count of handling units.
|
||||
*/
|
||||
public int getHuUnitCount() {
|
||||
return huUnitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of handling units in the container calculation result.
|
||||
*
|
||||
* @param huUnitCount The total count of handling units to set.
|
||||
*/
|
||||
public void setHuUnitCount(int huUnitCount) {
|
||||
this.huUnitCount = huUnitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the total utilization of the container based on either
|
||||
* weight or volume, depending on whether the weight limit is exceeded.
|
||||
*
|
||||
* @return Total container utilization as a percentage.
|
||||
*/
|
||||
public double getTotalUtilization() {
|
||||
return weightExceeded ? getTotalUtilizationByWeight() : getTotalUtilizationByVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total utilization of the container,
|
||||
* factoring in the HU utilization and count.
|
||||
*
|
||||
* @return The total utilization value for the container.
|
||||
*/
|
||||
public double getTotalUtilizationByVolume() {
|
||||
return getHuUtilizationByVolume() * huUnitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the utilization of a single handling unit (HU)
|
||||
* as a proportion of the container volume.
|
||||
*
|
||||
* @return The volume utilization ratio of one HU to container volume.
|
||||
*/
|
||||
public double getHuUtilizationByVolume() {
|
||||
return hu.getVolume(DimensionUnit.M) / containerType.getVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total utilization of the container based on weight,
|
||||
* factoring in the handling unit utilization and count.
|
||||
*
|
||||
* @return Total weight utilization of the container as a percentage.
|
||||
*/
|
||||
public double getTotalUtilizationByWeight() {
|
||||
return getHuUtilizationByWeight() * huUnitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the utilization of a single handling unit (HU) as a proportion
|
||||
* of the container weight capacity.
|
||||
*
|
||||
* @return Weight utilization ratio of one HU to the container's maximum weight.
|
||||
*/
|
||||
public double getHuUtilizationByWeight() {
|
||||
return WeightUnit.KG.convertFromG(hu.getWeight()).doubleValue() / maxContainerWeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public class PremiseController {
|
|||
return ResponseEntity.ok(premisesServices.updatePackaging(packagingDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/material")
|
||||
@PostMapping("/material")
|
||||
public ResponseEntity<HashMap<String, String>> updateMaterial(@RequestBody MaterialUpdateDTO materialUpdateDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updateMaterial(materialUpdateDTO));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,40 @@
|
|||
package de.avatic.lcc.dto.generic;
|
||||
|
||||
public enum ContainerType {
|
||||
FEU, TEU, HQ
|
||||
FEU(12030, 2350, 2390, 67.7, 24,21), TEU(5890 ,2350,2390, 33.0, 11,10), HQ(12030, 2350, 2690, 76.4, 24,21), TRUCK(13600,2450, 2650, 88.3, 34, 33);
|
||||
|
||||
private final int length;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final double volume;
|
||||
private final int euroPalletCount;
|
||||
private final int industrialPalletCount;
|
||||
|
||||
ContainerType(int length, int width, int height, double volume, int euroPalletCount, int industrialPalletCount) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.volume = volume;
|
||||
this.euroPalletCount = euroPalletCount;
|
||||
this.industrialPalletCount = industrialPalletCount;
|
||||
|
||||
}
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public int getPalletCount(PalletType palletType) {
|
||||
return palletType == PalletType.EURO_PALLET ? euroPalletCount : industrialPalletCount;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
src/main/java/de/avatic/lcc/dto/generic/PalletType.java
Normal file
29
src/main/java/de/avatic/lcc/dto/generic/PalletType.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package de.avatic.lcc.dto.generic;
|
||||
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
|
||||
public enum PalletType {
|
||||
INDUSTRIAL_PALLET(1200,1000), EURO_PALLET(1200,800);
|
||||
|
||||
private final int width;
|
||||
private final int length;
|
||||
|
||||
PalletType(int length, int width) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public boolean fitsOn(PackagingDimension dimension) {
|
||||
return (dimension.getLength() <= this.length && dimension.getWidth() <= this.width) || (dimension.getLength() <= this.width && dimension.getWidth() <= this.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package de.avatic.lcc.model.packaging;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PackagingDimension {
|
||||
|
||||
|
||||
|
|
@ -122,4 +123,12 @@ public class PackagingDimension {
|
|||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
public double getVolume(DimensionUnit unit) {
|
||||
var lengthInM = unit.convertFromMM(length);
|
||||
var widthInM = unit.convertFromMM(width);
|
||||
var heightInM = unit.convertFromMM(height);
|
||||
|
||||
return lengthInM.doubleValue() * widthInM.doubleValue() * heightInM.doubleValue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.model.premises;
|
||||
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
import jakarta.validation.constraints.Digits;
|
||||
|
|
@ -68,10 +69,37 @@ public class Premise {
|
|||
|
||||
private Integer userSupplierNodeId;
|
||||
|
||||
private Location location;
|
||||
|
||||
private Integer countryId;
|
||||
|
||||
private Integer packagingId;
|
||||
|
||||
private Integer userId;
|
||||
|
||||
public DimensionUnit getHuDisplayedDimensionUnit() {
|
||||
return huDisplayedDimensionUnit;
|
||||
}
|
||||
|
||||
public WeightUnit getHuDisplayedWeightUnit() {
|
||||
return huDisplayedWeightUnit;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(Location location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Integer getCountryId() {
|
||||
return countryId;
|
||||
}
|
||||
|
||||
public void setCountryId(Integer countryId) {
|
||||
this.countryId = countryId;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
|
|
@ -177,18 +205,10 @@ public class Premise {
|
|||
this.individualHuWeight = individualHuWeight;
|
||||
}
|
||||
|
||||
public DimensionUnit getDimensionUnit() {
|
||||
return huDisplayedDimensionUnit;
|
||||
}
|
||||
|
||||
public void setHuDisplayedDimensionUnit(DimensionUnit huDisplayedDimensionUnit) {
|
||||
this.huDisplayedDimensionUnit = huDisplayedDimensionUnit;
|
||||
}
|
||||
|
||||
public WeightUnit getWeightUnit() {
|
||||
return huDisplayedWeightUnit;
|
||||
}
|
||||
|
||||
public void setHuDisplayedWeightUnit(WeightUnit huDisplayedWeightUnit) {
|
||||
this.huDisplayedWeightUnit = huDisplayedWeightUnit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class Destination {
|
|||
|
||||
private Integer premiseId;
|
||||
|
||||
private BigDecimal annualAmount;
|
||||
private Integer annualAmount;
|
||||
|
||||
private Integer destinationNodeId;
|
||||
|
||||
|
|
@ -27,6 +27,16 @@ public class Destination {
|
|||
|
||||
private BigDecimal geoLng;
|
||||
|
||||
private Integer countryId;
|
||||
|
||||
public Integer getCountryId() {
|
||||
return countryId;
|
||||
}
|
||||
|
||||
public void setCountryId(Integer countryId) {
|
||||
this.countryId = countryId;
|
||||
}
|
||||
|
||||
public BigDecimal getGeoLat() {
|
||||
return geoLat;
|
||||
}
|
||||
|
|
@ -59,11 +69,11 @@ public class Destination {
|
|||
this.premiseId = premiseId;
|
||||
}
|
||||
|
||||
public BigDecimal getAnnualAmount() {
|
||||
public Integer getAnnualAmount() {
|
||||
return annualAmount;
|
||||
}
|
||||
|
||||
public void setAnnualAmount(BigDecimal annualAmount) {
|
||||
public void setAnnualAmount(Integer annualAmount) {
|
||||
this.annualAmount = annualAmount;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package de.avatic.lcc.model.properties;
|
||||
|
||||
public enum CountryPropertyMappingId {
|
||||
UNION("Customs Union", "NONE"),
|
||||
SAFETY_STOCK("Safety Stock [working days]", "30"),
|
||||
AIR_SHARE("Air freight share [%]", "2"),
|
||||
WAGE("Wage factor [%]", "100%");
|
||||
|
||||
private final String description;
|
||||
private final String defaultValue;
|
||||
|
||||
CountryPropertyMappingId(String description, String defaultValue) {
|
||||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.avatic.lcc.model.properties;
|
||||
|
||||
public enum CustomUnionType {
|
||||
EU, NONE
|
||||
}
|
||||
|
|
@ -1,5 +1,23 @@
|
|||
package de.avatic.lcc.model.properties;
|
||||
|
||||
import org.springframework.jmx.export.naming.IdentityNamingStrategy;
|
||||
|
||||
public enum PackagingPropertyMappingId {
|
||||
STACKABLE, MIXABLE, RUST_PREVENTION
|
||||
STACKABLE("Stackable", "true"), MIXABLE("Mixable", "true"), RUST_PREVENTION("Needs rust prevention", "false");
|
||||
|
||||
private final String defaultValue;
|
||||
private final String description;
|
||||
|
||||
PackagingPropertyMappingId(String description, String defaultValue) {
|
||||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,19 +20,21 @@ public enum SystemPropertyMappingId {
|
|||
TRUCK_UTIL("Truck utilization road transport EMEA [%]", "70%"),
|
||||
VALID_DAYS("Max validity of container freight rates [days]", "60"),
|
||||
RADIUS_REGION("Metropolition region size (diameter) [km]", "20"),
|
||||
FREQ_MIN("Min delivery frequency / year for containtrer transports", "3"),
|
||||
FREQ_MAX("Max delivery frequency / year for containtrer transports", "50"),
|
||||
FREQ_MIN("Min delivery frequency / year for container transports", "3"),
|
||||
FREQ_MAX("Max delivery frequency / year for container transports", "50"),
|
||||
TEU_LOAD("Max load of 20' container [kg]", "20000"),
|
||||
FEU_LOAD("Max load of 40' container [kg]", "21000"),
|
||||
TRUCK_LOAD("Maximum truck load [kg]", "25000"),
|
||||
|
||||
|
||||
AIR_PRECARRIAGE("Pre-carriage [EUR/kg]", "0,10 €"),
|
||||
AIR_HANDLING("Pre-carriage handling [EUR]", "80,00 €"),
|
||||
AIR_MAINCARRIAGE("Main carriage [EUR/kg]", "3,50 €"),
|
||||
AIR_HANDOVER_FEE("Hand over fee [EUR]", "35,00 €"),
|
||||
AIR_CUSTOM_FEE("Customs clearance fee [EUR]", "45,00 €"),
|
||||
|
||||
AIR_PRECARRIAGE("Pre-carriage [EUR/kg]", "0,10"),
|
||||
AIR_HANDLING("Pre-carriage handling [EUR]", "80,00"),
|
||||
AIR_MAINCARRIAGE("Main carriage [EUR/kg]", "3,50"),
|
||||
AIR_HANDOVER_FEE("Hand over fee [EUR]", "35,00"),
|
||||
AIR_CUSTOM_FEE("Customs clearance fee [EUR]", "45,00"),
|
||||
AIR_ONCARRIAGE("On-carriage [EUR/kg]", "0,20 €"),
|
||||
AIR_TERMINAL_FEE("Terminal handling fee [EUR/kg]", "0,20 €"),
|
||||
AIR_TERMINAL_FEE("Terminal handling fee [EUR/kg]", "0,20"),
|
||||
|
||||
|
||||
KLT_HANDLING("GR handling KLT [EUR/HU]", "0,71 €"),
|
||||
|
|
@ -41,7 +43,7 @@ public enum SystemPropertyMappingId {
|
|||
GLT_RELEASE("GLT release from storage [EUR/GLT release]", "2,23 €"),
|
||||
KLT_RELEASE("KLT release from storage [EUR/KLT release]", "1,12 €"),
|
||||
GLT_DISPATCH("GLT dispatch [EUR/GLT dispatch]", "1,61 €"),
|
||||
KLT_DISPATCH("KLT dispacth [EUR/KLT dispatch]", "0,33 €"),
|
||||
KLT_DISPATCH("KLT dispatch [EUR/KLT dispatch]", "0,33 €"),
|
||||
KLT_REPACK_S("Repacking KLT, HU <15kg [EUR/HU]", "2,08 €"),
|
||||
KLT_REPACK_M("Repacking KLT, HU >=15kg [EUR/HU]", "3,02 €"),
|
||||
GLT_REPACK_S("Repacking GLT, HU <15kg [EUR/HU]", "3,02 €"),
|
||||
|
|
@ -50,14 +52,9 @@ public enum SystemPropertyMappingId {
|
|||
DISPOSAL("GLT disposal [EUR/GLT]", "6,00 €"),
|
||||
|
||||
|
||||
SPACE_COST("Space costs / m3 per night [EUR/m3]", "0,26 €"),
|
||||
SPACE_COST("Space costs / m3 per night [EUR/m3]", "0,26 €");
|
||||
|
||||
|
||||
UNION("Customs Union", ""),
|
||||
SAFTY_STOCK("Safety Stock [working days]", "30"),
|
||||
AIR_SHARE("Air freight share [%]", "2"),
|
||||
WAGE("Wage factor [%]", "100%");
|
||||
|
||||
private final String description;
|
||||
private final String defaultValue;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.model.rates;
|
||||
|
||||
import de.avatic.lcc.dto.generic.TransportType;
|
||||
import jakarta.validation.constraints.Digits;
|
||||
import jdk.jfr.Unsigned;
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ public class ContainerRate {
|
|||
|
||||
private Integer id;
|
||||
|
||||
private ContainerRateType type;
|
||||
private TransportType type;
|
||||
|
||||
@Digits(integer = 15, fraction = 2)
|
||||
private BigDecimal rateTeu;
|
||||
|
|
@ -65,11 +66,11 @@ public class ContainerRate {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public ContainerRateType getType() {
|
||||
public TransportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ContainerRateType type) {
|
||||
public void setType(TransportType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package de.avatic.lcc.model.rates;
|
||||
|
||||
public enum ContainerRateType {
|
||||
RAIL, SEA, POST_RUN, ROAD
|
||||
}
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
package de.avatic.lcc.repositories.country;
|
||||
|
||||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
||||
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class CountryPropertyRepository {
|
||||
|
|
@ -29,6 +35,26 @@ public class CountryPropertyRepository {
|
|||
jdbcTemplate.update(query, value, countryId, typeId, setId, value);
|
||||
}
|
||||
|
||||
public Optional<PropertyDTO> getByMappingIdAndCountryId(CountryPropertyMappingId mappingId, Integer countryId) {
|
||||
String query = """
|
||||
SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule,
|
||||
draft.property_value as draftValue, valid.property_value as validValue
|
||||
FROM country_property_type AS type
|
||||
LEFT JOIN country_property AS draft ON draft.country_property_type_id = type.id
|
||||
LEFT JOIN country_property AS valid ON valid.country_property_type_id = type.id
|
||||
LEFT JOIN property_set AS draftSet ON draftSet.id = draft.property_set_id
|
||||
LEFT JOIN property_set AS validSet ON validSet.id = valid.property_set_id
|
||||
WHERE draftSet.state = 'DRAFT' AND validSet.state = 'VALID' AND draft.country_id = ? AND valid.country_id = ?
|
||||
AND type.external_mapping_id = ?
|
||||
""";
|
||||
|
||||
var property = jdbcTemplate.query(query, new PropertyMapper(), countryId, countryId, mappingId.name());
|
||||
|
||||
if(property.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(property.getFirst());
|
||||
}
|
||||
|
||||
private Integer getTypeIdByMappingId(String mappingId) {
|
||||
String query = "SELECT id FROM country_property_type WHERE external_mapping_id = ?";
|
||||
|
|
@ -49,19 +75,7 @@ public class CountryPropertyRepository {
|
|||
WHERE draftSet.state = 'DRAFT' AND validSet.state = 'VALID' AND draft.country_id = ? AND valid.country_id = ?
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, (rs, rowNum) -> {
|
||||
var dto = new PropertyDTO();
|
||||
|
||||
dto.setName(rs.getString("name"));
|
||||
dto.setDraftValue(rs.getString("draftValue"));
|
||||
dto.setCurrentValue(rs.getString("validValue"));
|
||||
dto.setValidationRule(rs.getString("validationRule"));
|
||||
dto.setExternalMappingId(rs.getString("externalMappingId"));
|
||||
dto.setRequired(true);
|
||||
dto.setDataType(rs.getString("dataType"));
|
||||
|
||||
return dto;
|
||||
}, id, id);
|
||||
return jdbcTemplate.query(query, new PropertyMapper(), id, id);
|
||||
}
|
||||
|
||||
public Collection<PropertyDTO> listPropertiesByCountryIdAndPropertySetId(Integer id, Integer propertySetId) {
|
||||
|
|
@ -75,19 +89,7 @@ public class CountryPropertyRepository {
|
|||
WHERE propertySet.state = 'EXPIRED' AND property.country_id = ? AND propertySet.id = ?
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, (rs, rowNum) -> {
|
||||
var dto = new PropertyDTO();
|
||||
|
||||
dto.setName(rs.getString("name"));
|
||||
dto.setDraftValue(rs.getString("draftValue"));
|
||||
dto.setCurrentValue(rs.getString("validValue"));
|
||||
dto.setValidationRule(rs.getString("validationRule"));
|
||||
dto.setExternalMappingId(rs.getString("externalMappingId"));
|
||||
dto.setRequired(true);
|
||||
dto.setDataType(rs.getString("dataType"));
|
||||
|
||||
return dto;
|
||||
}, id, propertySetId);
|
||||
return jdbcTemplate.query(query, new PropertyMapper(), id, propertySetId);
|
||||
}
|
||||
|
||||
public void fillDraft(Integer setId) {
|
||||
|
|
@ -105,4 +107,23 @@ public class CountryPropertyRepository {
|
|||
|
||||
|
||||
}
|
||||
|
||||
private static class PropertyMapper implements RowMapper<PropertyDTO>
|
||||
{
|
||||
|
||||
@Override
|
||||
public PropertyDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
var dto = new PropertyDTO();
|
||||
|
||||
dto.setName(rs.getString("name"));
|
||||
dto.setDraftValue(rs.getString("draftValue"));
|
||||
dto.setCurrentValue(rs.getString("validValue"));
|
||||
dto.setValidationRule(rs.getString("validationRule"));
|
||||
dto.setExternalMappingId(rs.getString("externalMappingId"));
|
||||
dto.setRequired(true);
|
||||
dto.setDataType(rs.getString("dataType"));
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class DestinationRepository {
|
|||
|
||||
jdbcTemplate.update(connection -> {
|
||||
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setBigDecimal(1, destination.getAnnualAmount());
|
||||
ps.setInt(1, destination.getAnnualAmount());
|
||||
ps.setInt(2, destination.getPremiseId());
|
||||
ps.setInt(3, destination.getDestinationNodeId());
|
||||
ps.setBigDecimal(4, destination.getRateD2d());
|
||||
|
|
@ -112,7 +112,7 @@ public class DestinationRepository {
|
|||
Destination entity = new Destination();
|
||||
|
||||
entity.setId(rs.getInt("id"));
|
||||
entity.setAnnualAmount(rs.getBigDecimal("annual_amount"));
|
||||
entity.setAnnualAmount(rs.getInt("annual_amount"));
|
||||
entity.setPremiseId(rs.getInt("premise_id"));
|
||||
entity.setDestinationNodeId(rs.getInt("destination_node_id"));
|
||||
entity.setRateD2d(rs.getBigDecimal("rate_d2d"));
|
||||
|
|
@ -122,6 +122,7 @@ public class DestinationRepository {
|
|||
entity.setDisposalCost(rs.getBigDecimal("disposal_cost"));
|
||||
entity.setGeoLat(rs.getBigDecimal("geo_lat"));
|
||||
entity.setGeoLng(rs.getBigDecimal("geo_lng"));
|
||||
entity.setCountryId(rs.getInt("country_id"));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package de.avatic.lcc.repositories.premise;
|
||||
|
||||
import de.avatic.lcc.model.materials.Material;
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.PremiseListEntry;
|
||||
|
|
@ -301,14 +303,14 @@ public class PremiseRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public void setSupplierId(List<Integer> premiseId, Integer supplierId, boolean userSupplierNode) {
|
||||
public void setSupplierId(List<Integer> premiseId, Node supplier, boolean userSupplierNode) {
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
|
||||
String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ? WHERE id IN ("+placeholders+")";
|
||||
String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ?, geo_lat = ?, geo_lng = ?, country_id = ? WHERE id IN ("+placeholders+")";
|
||||
|
||||
if(userSupplierNode) {
|
||||
jdbcTemplate.update(sql, 0, supplierId, premiseId.toArray());
|
||||
jdbcTemplate.update(sql, 0, supplier.getId(), supplier.getGeoLat(), supplier.getGeoLng(), supplier.getCountryId(), premiseId.toArray());
|
||||
} else {
|
||||
jdbcTemplate.update(sql, supplierId, 0, premiseId.toArray());
|
||||
jdbcTemplate.update(sql, supplier.getId(), 0, supplier.getGeoLat(), supplier.getGeoLng(), supplier.getCountryId(), premiseId.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +377,7 @@ public class PremiseRepository {
|
|||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, createdAt, updatedAt)" +
|
||||
"INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, created_at, updated_at)" +
|
||||
" VALUES (?, ?, ?, ?, 'DRAFT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||
Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
|
|
@ -576,6 +578,9 @@ public class PremiseRepository {
|
|||
entity.setSupplierNodeId(rs.getInt("supplier_node_id"));
|
||||
entity.setUserSupplierNodeId(rs.getInt("user_supplier_node_id"));
|
||||
|
||||
entity.setLocation(new Location(rs.getBigDecimal("geo_lng").doubleValue(), rs.getBigDecimal("geo_lat").doubleValue()));
|
||||
entity.setCountryId(rs.getInt("country_id"));
|
||||
|
||||
entity.setUserId(rs.getInt("user_id"));
|
||||
entity.setMaterialCost(rs.getBigDecimal("material_cost"));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import org.springframework.jdbc.support.GeneratedKeyHolder;
|
|||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.swing.text.html.Option;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
|
@ -14,6 +15,7 @@ import java.sql.Statement;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class RouteRepository {
|
||||
|
|
@ -29,6 +31,21 @@ public class RouteRepository {
|
|||
return jdbcTemplate.query(query, new RouteMapper(), id);
|
||||
}
|
||||
|
||||
public Optional<Route> getSelectedByDestinationId(Integer id) {
|
||||
String query = "SELECT * FROM premise_route WHERE premise_destination_id = ?";
|
||||
var route = jdbcTemplate.query(query, new RouteMapper(), id);
|
||||
|
||||
if(route.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* if(1 < route.size())
|
||||
TODO throw something */
|
||||
|
||||
|
||||
return Optional.of(route.getFirst());
|
||||
}
|
||||
|
||||
public void deleteAllById(List<Integer> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package de.avatic.lcc.repositories.rates;
|
||||
|
||||
import de.avatic.lcc.dto.generic.TransportType;
|
||||
import de.avatic.lcc.model.rates.ContainerRate;
|
||||
import de.avatic.lcc.model.rates.ContainerRateType;
|
||||
import de.avatic.lcc.model.rates.ValidityPeriodState;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
|
|
@ -78,8 +78,8 @@ public class ContainerRateRepository {
|
|||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(ValidityPeriodState.VALID.name());
|
||||
params.add(ContainerRateType.SEA.name());
|
||||
params.add(ContainerRateType.RAIL.name());
|
||||
params.add(TransportType.SEA.name());
|
||||
params.add(TransportType.RAIL.name());
|
||||
params.add(startNodeId);
|
||||
params.addAll(destinationCountryIds);
|
||||
|
||||
|
|
@ -108,10 +108,10 @@ public class ContainerRateRepository {
|
|||
WHERE validity_period.state = ?
|
||||
AND container_rate.from_node_id = ? AND container_rate.container_rate_type = ?""";
|
||||
|
||||
return jdbcTemplate.query(query, new ContainerRateMapper(true), ValidityPeriodState.VALID.name(), mainRun.getToNodeId(), ContainerRateType.POST_RUN.name());
|
||||
return jdbcTemplate.query(query, new ContainerRateMapper(true), ValidityPeriodState.VALID.name(), mainRun.getToNodeId(), TransportType.POST_RUN.name());
|
||||
}
|
||||
|
||||
public Optional<ContainerRate> findRoute(Integer fromNodeId, Integer toNodeId, ContainerRateType type) {
|
||||
public Optional<ContainerRate> findRoute(Integer fromNodeId, Integer toNodeId, TransportType type) {
|
||||
String query = """
|
||||
SELECT * FROM container_rate WHERE from_node_id = ? AND to_node_id = ? AND container_rate_type = ?
|
||||
""";
|
||||
|
|
@ -144,7 +144,7 @@ public class ContainerRateRepository {
|
|||
entity.setValidityPeriodId(rs.getInt("validity_period_id"));
|
||||
entity.setFromNodeId(rs.getInt("from_node_id"));
|
||||
entity.setToNodeId(rs.getInt("to_node_id"));
|
||||
entity.setType(ContainerRateType.valueOf(rs.getString("container_rate_type")));
|
||||
entity.setType(TransportType.valueOf(rs.getString("container_rate_type")));
|
||||
entity.setLeadTime(rs.getInt("lead_time"));
|
||||
entity.setRateFeu(rs.getBigDecimal("rate_feu"));
|
||||
entity.setRateTeu(rs.getBigDecimal("rate_teu"));
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class DestinationService {
|
|||
var destination = new Destination();
|
||||
destination.setDestinationNodeId(dto.getDestinationNodeId());
|
||||
destination.setPremiseId(premise.getId());
|
||||
destination.setAnnualAmount(BigDecimal.ZERO);
|
||||
destination.setAnnualAmount(0);
|
||||
destination.setD2d(false);
|
||||
destination.setDisposalCost(null);
|
||||
destination.setHandlingCost(null);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,63 @@
|
|||
package de.avatic.lcc.service.calculation;
|
||||
|
||||
import de.avatic.lcc.model.country.Country;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.route.Destination;
|
||||
import de.avatic.lcc.model.premises.route.RouteSection;
|
||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||
import de.avatic.lcc.repositories.premise.DestinationRepository;
|
||||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteSectionRepository;
|
||||
import de.avatic.lcc.service.calculation.steps.CustomCostCalculationService;
|
||||
import de.avatic.lcc.service.calculation.steps.RouteSectionCostCalculationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CalculationExecutionService {
|
||||
|
||||
|
||||
private final PremiseRepository premiseRepository;
|
||||
private final DestinationRepository destinationRepository;
|
||||
private final RouteRepository routeRepository;
|
||||
private final RouteSectionRepository routeSectionRepository;
|
||||
private final CustomCostCalculationService customCostCalculationService;
|
||||
private final CountryRepository countryRepository;
|
||||
private final RouteSectionCostCalculationService routeSectionCostCalculationService;
|
||||
|
||||
public CalculationExecutionService(PremiseRepository premiseRepository) {
|
||||
public CalculationExecutionService(PremiseRepository premiseRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, CustomCostCalculationService customCostCalculationService, CountryRepository countryRepository, RouteSectionCostCalculationService routeSectionCostCalculationService) {
|
||||
this.premiseRepository = premiseRepository;
|
||||
this.destinationRepository = destinationRepository;
|
||||
this.routeRepository = routeRepository;
|
||||
this.routeSectionRepository = routeSectionRepository;
|
||||
this.customCostCalculationService = customCostCalculationService;
|
||||
this.countryRepository = countryRepository;
|
||||
this.routeSectionCostCalculationService = routeSectionCostCalculationService;
|
||||
}
|
||||
|
||||
public void doCalculation(Integer premiseId) {
|
||||
var premise = premiseRepository.getPremiseById(premiseId);
|
||||
Premise premise = premiseRepository.getPremiseById(premiseId).orElseThrow();
|
||||
|
||||
List<Destination> destinations = destinationRepository.getByPremiseId(premiseId);
|
||||
destinations.forEach(destination -> doDestinationCalculation(destination, premise));
|
||||
|
||||
}
|
||||
|
||||
private void doDestinationCalculation(Destination destination, Premise premise) {
|
||||
var route = routeRepository.getSelectedByDestinationId(destination.getId()).orElseThrow();
|
||||
List<RouteSection> section = routeSectionRepository.getByRouteId(route.getId());
|
||||
|
||||
routeSectionCostCalculationService.doCalculation();
|
||||
|
||||
Country supplierCountry = countryRepository.getById(premise.getCountryId()).orElseThrow();
|
||||
Country destinationCountry = countryRepository.getById(destination.getCountryId()).orElseThrow();
|
||||
customCostCalculationService.doCalculation(premise.getHsCode(), supplierCountry, destinationCountry, route, section);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ public class ChangeSupplierService {
|
|||
}
|
||||
|
||||
// actually update supplier supplierNodeId.
|
||||
premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplierNodeId, dto.isUserSupplierNode());
|
||||
premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplier, dto.isUserSupplierNode());
|
||||
|
||||
|
||||
//delete all conflicting premises:
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
package de.avatic.lcc.service.calculation;
|
||||
|
||||
import de.avatic.lcc.calculationmodel.ContainerCalculationResult;
|
||||
import de.avatic.lcc.model.packaging.Packaging;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ContainerCalculationService {
|
||||
|
||||
private static final double DIMENSION_TOLERANCE = 0.02;
|
||||
|
||||
private static final int TEU_LENGTH = 5890;
|
||||
private static final int TEU_WIDTH = 2350;
|
||||
private static final int TEU_HEIGHT = 2390;
|
||||
|
||||
private static final int FEU_LENGTH = 12030;
|
||||
private static final int FEU_WIDTH = 2350;
|
||||
private static final int FEU_HEIGHT = 2390;
|
||||
|
||||
private static final int HQ_LENGTH = 12030;
|
||||
private static final int HQ_WIDTH = 2350;
|
||||
private static final int HQ_HEIGHT = 2690;
|
||||
|
||||
|
||||
|
||||
public ContainerCalculationResult doCalculation(PackagingDimension hu) {
|
||||
var dimensions = hu.withTolerance(DIMENSION_TOLERANCE);
|
||||
}
|
||||
|
||||
private int calculateLayer(PackagingDimension dimension, ) {
|
||||
|
||||
}
|
||||
|
||||
private static class Container {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package de.avatic.lcc.service.calculation;
|
||||
|
||||
import de.avatic.lcc.model.country.DetourIndex;
|
||||
import de.avatic.lcc.model.country.IsoCode;
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.DistanceMatrixRepository;
|
||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||
|
|
@ -28,6 +30,23 @@ public class DistanceService {
|
|||
|
||||
}
|
||||
|
||||
public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest ) {
|
||||
double srcLatitudeRadians = Math.toRadians(src.getLatitude());
|
||||
double srcLongitudeRad = Math.toRadians(src.getLongitude());
|
||||
double destLatitudeRadians = Math.toRadians(dest.getLatitude());
|
||||
double destLongitudeRadians = Math.toRadians(dest.getLongitude());
|
||||
|
||||
double latitudeDifference = destLatitudeRadians - srcLatitudeRadians;
|
||||
double longitudeDifference = destLongitudeRadians - srcLongitudeRad;
|
||||
|
||||
double a = Math.sin(latitudeDifference / 2) * Math.sin(latitudeDifference / 2) + Math.cos(srcLatitudeRadians) * Math.cos(destLatitudeRadians) * Math.sin(longitudeDifference / 2) * Math.sin(longitudeDifference / 2);
|
||||
|
||||
IsoCode isoSrc = countryRepository.getById(srcCountryId).orElseThrow().getIsoCode();
|
||||
IsoCode isoDest = countryRepository.getById(destCountryId).orElseThrow().getIsoCode();
|
||||
|
||||
return (getDetourIndex(isoSrc, isoDest) * (EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))));
|
||||
}
|
||||
|
||||
private double getDistanceFast(Node src, Node dest) {
|
||||
|
||||
double srcLatitudeRadians = Math.toRadians(src.getGeoLat().doubleValue());
|
||||
|
|
@ -53,4 +72,14 @@ public class DistanceService {
|
|||
return (srcDetour.getAverageDetourIndex() + destDetour.getAverageDetourIndex()) / 2;
|
||||
}
|
||||
|
||||
private double getDetourIndex(IsoCode src, IsoCode dest) {
|
||||
DetourIndex srcDetour = DetourIndex.getDetourIndex(src);
|
||||
|
||||
if (src.equals(dest)) return srcDetour.getAverageDetourIndex();
|
||||
|
||||
DetourIndex destDetour = DetourIndex.getDetourIndex(dest);
|
||||
|
||||
return (srcDetour.getAverageDetourIndex() + destDetour.getAverageDetourIndex()) / 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import de.avatic.lcc.model.nodes.Node;
|
|||
import de.avatic.lcc.model.premises.route.*;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.rates.ContainerRate;
|
||||
import de.avatic.lcc.model.rates.ContainerRateType;
|
||||
import de.avatic.lcc.model.rates.MatrixRate;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
|
|
@ -485,7 +484,7 @@ public class RoutingService {
|
|||
if (container.getRates().contains(matrixRateObj))
|
||||
return container.getRates().stream().filter(r -> r.equals(matrixRateObj)).findFirst().orElseThrow();
|
||||
|
||||
Optional<ContainerRate> containerRate = containerRateRepository.findRoute(startNode.getId(), endNode.getId(), ContainerRateType.ROAD);
|
||||
Optional<ContainerRate> containerRate = containerRateRepository.findRoute(startNode.getId(), endNode.getId(), TransportType.ROAD);
|
||||
|
||||
if (containerRate.isPresent()) {
|
||||
containerRateObj.setRate(containerRate.get());
|
||||
|
|
@ -787,7 +786,7 @@ public class RoutingService {
|
|||
return type;
|
||||
}
|
||||
|
||||
public ContainerRateType getContainerRateTye() {
|
||||
public TransportType getContainerRateTye() {
|
||||
return containerRate.getType();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AirfreightCalculationService {
|
||||
|
||||
private final PropertyRepository propertyRepository;
|
||||
|
||||
public AirfreightCalculationService(PropertyRepository propertyRepository) {
|
||||
this.propertyRepository = propertyRepository;
|
||||
}
|
||||
|
||||
public double doCalculation(PackagingDimension hu, double annualHuAmount) {
|
||||
|
||||
var preCarriage = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_PRECARRIAGE).orElseThrow().getCurrentValue());
|
||||
var mainCarriage = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_MAINCARRIAGE).orElseThrow().getCurrentValue());
|
||||
var postCarriage = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_ONCARRIAGE).orElseThrow().getCurrentValue());
|
||||
var terminalFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_TERMINAL_FEE).orElseThrow().getCurrentValue());
|
||||
var preCarriageFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_HANDLING).orElseThrow().getCurrentValue());
|
||||
var customsClearanceFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_CUSTOM_FEE).orElseThrow().getCurrentValue());
|
||||
var handOverFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.AIR_HANDOVER_FEE).orElseThrow().getCurrentValue());
|
||||
|
||||
return 1.08*((getShippingWeight(hu)* preCarriage+mainCarriage+postCarriage+terminalFee)+preCarriageFee+customsClearanceFee+handOverFee);
|
||||
|
||||
}
|
||||
|
||||
private double getShippingWeight(PackagingDimension hu) {
|
||||
return Math.max(getWeightInKg(hu), getVolumeWeight(hu));
|
||||
}
|
||||
|
||||
private double getVolumeWeight(PackagingDimension hu) {
|
||||
return 0.0002 * getDimensionInCm(hu.getLength(), hu.getDimensionUnit()) * getDimensionInCm(hu.getWidth(), hu.getDimensionUnit()) * getDimensionInCm(hu.getHeight(), hu.getDimensionUnit()) ;
|
||||
}
|
||||
|
||||
private double getDimensionInCm(Number dimension, DimensionUnit unit) {
|
||||
return DimensionUnit.CM.convertFromMM(dimension).doubleValue();
|
||||
}
|
||||
|
||||
private double getWeightInKg(PackagingDimension hu) {
|
||||
return WeightUnit.KG.convertFromG(hu.getWeight()).doubleValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AirfreightShareCalculationService {
|
||||
|
||||
private final CountryPropertyRepository propertyRepository;
|
||||
|
||||
public AirfreightShareCalculationService(CountryPropertyRepository propertyRepository) {
|
||||
this.propertyRepository = propertyRepository;
|
||||
}
|
||||
|
||||
public double doCalculation(double overseaShare, Integer countryId) {
|
||||
var maxAirfreightShare = Double.parseDouble(propertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.AIR_SHARE, countryId).orElseThrow().getCurrentValue());
|
||||
|
||||
// Ensure oversea share is within valid range
|
||||
if (overseaShare < 0 || overseaShare > 100) {
|
||||
throw new IllegalArgumentException("Oversea share must be between 0 and 100");
|
||||
}
|
||||
|
||||
// First segment: from (0,0) to (50, 20% of max)
|
||||
if (overseaShare <= 50) {
|
||||
// Linear interpolation: y = mx + b
|
||||
// m = (0.2 * maxAirfreightShare - 0) / (50 - 0) = 0.004 * maxAirfreightShare
|
||||
// b = 0
|
||||
return 0.004 * maxAirfreightShare * overseaShare;
|
||||
}
|
||||
// Second segment: from (50, 20% of max) to (100, max)
|
||||
else {
|
||||
// Linear interpolation: y = mx + b
|
||||
// m = (maxAirfreightShare - 0.2 * maxAirfreightShare) / (100 - 50) = 0.016 * maxAirfreightShare
|
||||
// b = 0.2 * maxAirfreightShare - 50 * 0.016 * maxAirfreightShare = -0.6 * maxAirfreightShare
|
||||
return 0.016 * maxAirfreightShare * overseaShare - 0.6 * maxAirfreightShare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.calculationmodel.ContainerCalculationResult;
|
||||
import de.avatic.lcc.dto.generic.ContainerType;
|
||||
import de.avatic.lcc.dto.generic.PalletType;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ContainerCalculationService {
|
||||
|
||||
private static final double DIMENSION_TOLERANCE = 0.02;
|
||||
|
||||
private final PropertyRepository propertyRepository;
|
||||
|
||||
public ContainerCalculationService(PropertyRepository propertyRepository) {
|
||||
this.propertyRepository = propertyRepository;
|
||||
}
|
||||
|
||||
public ContainerCalculationResult doCalculation(PackagingDimension hu, ContainerType containerType) {
|
||||
|
||||
var weightInKg = WeightUnit.KG.convertFromG(hu.getWeight()).intValue();
|
||||
var maxContainerLoad = getMaxContainerLoad(containerType);
|
||||
var maxUnitByWeight = maxContainerLoad / weightInKg;
|
||||
|
||||
var dimensions = hu.withTolerance(DIMENSION_TOLERANCE);
|
||||
|
||||
var solutionHorizontal = solveLayer(dimensions, containerType.getLength(), containerType.getWidth());
|
||||
var solutionVertical = solveLayer(dimensions, containerType.getWidth(), containerType.getLength());
|
||||
var bestSolution = solutionHorizontal.getTotal() < solutionVertical.getTotal() ? solutionVertical : solutionHorizontal;
|
||||
int layers = getLayerCount(dimensions, containerType);
|
||||
|
||||
if(PalletType.EURO_PALLET.fitsOn(dimensions) && bestSolution.getTotal() < containerType.getPalletCount(PalletType.EURO_PALLET)) {
|
||||
return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.EURO_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.EURO_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad);
|
||||
} else if(PalletType.INDUSTRIAL_PALLET.fitsOn(dimensions) && bestSolution.getTotal() < containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET)) {
|
||||
return new ContainerCalculationResult(Math.min(containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET)*layers,maxUnitByWeight), layers, null, containerType.getPalletCount(PalletType.INDUSTRIAL_PALLET) > maxUnitByWeight, containerType, dimensions, maxContainerLoad);
|
||||
}
|
||||
|
||||
return toContainerCalculationResult(bestSolution, layers, maxUnitByWeight, containerType, dimensions, maxContainerLoad);
|
||||
}
|
||||
|
||||
private static Solution solveLayer(PackagingDimension dimension,
|
||||
int containerWidth, int containerLength) {
|
||||
|
||||
var horizontalColumn = new Column(new Block(dimension, BlockType.HORIZONTAL), containerWidth);
|
||||
var verticalColumn = new Column(new Block(dimension, BlockType.VERTICAL), containerWidth);
|
||||
var circularColumn = new Column(new Block(dimension, BlockType.CIRCULAR), containerWidth);
|
||||
|
||||
Solution bestSolution = new Solution(0, 0, 0, horizontalColumn, verticalColumn, circularColumn);
|
||||
|
||||
int maxHorizontalColumns = containerLength / horizontalColumn.getColumnWidth();
|
||||
int maxVerticalColumns = containerLength / verticalColumn.getColumnWidth();
|
||||
int maxCircularColumns = containerLength / circularColumn.getColumnWidth();
|
||||
|
||||
for (int horizontalColumnCount = 0; horizontalColumnCount <= maxHorizontalColumns; horizontalColumnCount++) {
|
||||
for (int verticalColumnCount = 0; verticalColumnCount <= maxVerticalColumns; verticalColumnCount++) {
|
||||
for (int circularColumnCount = 0; circularColumnCount <= maxCircularColumns; circularColumnCount++) {
|
||||
// Check constraints
|
||||
if (horizontalColumn.getColumnWidth() * horizontalColumnCount
|
||||
+ horizontalColumn.getColumnWidth() * verticalColumnCount
|
||||
+ horizontalColumn.getColumnWidth() * circularColumnCount <= containerLength) {
|
||||
var current = new Solution(horizontalColumnCount, verticalColumnCount, circularColumnCount, horizontalColumn, verticalColumn, circularColumn);
|
||||
if (current.getTotal() > bestSolution.getTotal()) {
|
||||
bestSolution = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestSolution;
|
||||
}
|
||||
|
||||
private int getMaxContainerLoad(ContainerType containerType) {
|
||||
|
||||
int load = 0;
|
||||
|
||||
if(containerType == ContainerType.FEU)
|
||||
{
|
||||
load = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FEU_LOAD).orElseThrow().getCurrentValue());
|
||||
} else if(containerType == ContainerType.TRUCK) {
|
||||
load = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TRUCK_LOAD).orElseThrow().getCurrentValue());
|
||||
} else if(containerType == ContainerType.TEU) {
|
||||
load = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TEU_LOAD).orElseThrow().getCurrentValue());
|
||||
}
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
private int getLayerCount(PackagingDimension dimensions, ContainerType containerType) {
|
||||
return containerType.getHeight()/dimensions.getHeight();
|
||||
}
|
||||
|
||||
private ContainerCalculationResult toContainerCalculationResult(Solution solution, int layers, int maxUnitByWeight, ContainerType containerType, PackagingDimension hu, int maxContainerLoad) {
|
||||
ContainerCalculationResult result = new ContainerCalculationResult();
|
||||
|
||||
result.setLayer(layers);
|
||||
result.setHuUnitCount(Math.min(solution.getTotal()*layers, maxUnitByWeight));
|
||||
result.setWeightExceeded(solution.getTotal() > maxUnitByWeight);
|
||||
result.setStructure(null);
|
||||
result.setContainerType(containerType);
|
||||
result.setHu(hu);
|
||||
result.setMaxContainerWeight(maxContainerLoad);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private enum BlockType {
|
||||
HORIZONTAL(1), VERTICAL(1), CIRCULAR(4);
|
||||
|
||||
private final int resultValue;
|
||||
|
||||
BlockType(int resultValue) {
|
||||
this.resultValue = resultValue;
|
||||
}
|
||||
|
||||
public int getBlockColumnLength(PackagingDimension dimension) {
|
||||
return switch (this) {
|
||||
case HORIZONTAL -> dimension.getLength();
|
||||
case VERTICAL -> dimension.getWidth();
|
||||
case CIRCULAR -> dimension.getWidth() + dimension.getLength();
|
||||
};
|
||||
}
|
||||
|
||||
public int getColumnWidth(PackagingDimension dimension) {
|
||||
return switch (this) {
|
||||
case VERTICAL -> dimension.getLength();
|
||||
case HORIZONTAL -> dimension.getWidth();
|
||||
case CIRCULAR -> dimension.getWidth() + dimension.getLength();
|
||||
};
|
||||
}
|
||||
|
||||
public int getResultValue() {
|
||||
return resultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Solution {
|
||||
|
||||
Map<BlockType, Integer> rowCount = new HashMap<>();
|
||||
Map<BlockType, Column> columns = new HashMap<>();
|
||||
|
||||
|
||||
public Solution(int horizontalCount, int verticalCount, int circularCount, Column horizontalColumn, Column verticalColumn, Column circularColumn) {
|
||||
rowCount.put(BlockType.HORIZONTAL, horizontalCount);
|
||||
rowCount.put(BlockType.VERTICAL, verticalCount);
|
||||
rowCount.put(BlockType.CIRCULAR, circularCount);
|
||||
columns.put(BlockType.HORIZONTAL, horizontalColumn);
|
||||
columns.put(BlockType.VERTICAL, verticalColumn);
|
||||
columns.put(BlockType.CIRCULAR, circularColumn);
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return rowCount.get(BlockType.HORIZONTAL) * columns.get(BlockType.HORIZONTAL).getResultValue()
|
||||
+ rowCount.get(BlockType.VERTICAL) * columns.get(BlockType.VERTICAL).getResultValue()
|
||||
+ rowCount.get(BlockType.CIRCULAR) * columns.get(BlockType.CIRCULAR).getResultValue();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Column {
|
||||
|
||||
private final int blocksPerColumn;
|
||||
private final Block block;
|
||||
|
||||
public Column(Block block, int columnLength) {
|
||||
this.block = block;
|
||||
this.blocksPerColumn = block.calculateBlockCount(columnLength);
|
||||
}
|
||||
|
||||
public int getResultValue() {
|
||||
return block.getResultValue() * blocksPerColumn;
|
||||
}
|
||||
|
||||
public int getColumnWidth() {
|
||||
return block.getBlockWidth();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Block {
|
||||
|
||||
private final PackagingDimension dimension;
|
||||
private final BlockType type;
|
||||
|
||||
public Block(PackagingDimension dimension, BlockType type) {
|
||||
this.dimension = dimension;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int calculateBlockCount(int containerColumnLength) {
|
||||
return containerColumnLength / type.getBlockColumnLength(dimension);
|
||||
}
|
||||
|
||||
public int getResultValue() {
|
||||
return type.getResultValue();
|
||||
}
|
||||
|
||||
public int getBlockWidth() {
|
||||
return type.getColumnWidth(dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.model.country.Country;
|
||||
import de.avatic.lcc.model.premises.route.Route;
|
||||
import de.avatic.lcc.model.premises.route.RouteSection;
|
||||
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
|
||||
import de.avatic.lcc.model.properties.CustomUnionType;
|
||||
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CustomCostCalculationService {
|
||||
|
||||
private final CustomApiService customApiService;
|
||||
private final CountryPropertyRepository countryPropertyRepository;
|
||||
private final RouteNodeRepository routeNodeRepository;
|
||||
|
||||
public CustomCostCalculationService(CustomApiService customApiService, CountryPropertyRepository countryPropertyRepository, RouteNodeRepository routeNodeRepository) {
|
||||
this.customApiService = customApiService;
|
||||
this.countryPropertyRepository = countryPropertyRepository;
|
||||
this.routeNodeRepository = routeNodeRepository;
|
||||
}
|
||||
|
||||
public double doCalculation(String hsCode, Country supplierCountry, Country destinationCountry, Route route, List<RouteSection> section) {
|
||||
|
||||
var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destinationCountry.getId()).orElseThrow();
|
||||
var sourceUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, supplierCountry.getId()).orElseThrow();
|
||||
|
||||
if (CustomUnionType.EU.name().equals(destUnion.getCurrentValue()) && CustomUnionType.NONE.name().equals(sourceUnion.getCurrentValue())) {
|
||||
var tariffRate = customApiService.getTariffRate(hsCode, supplierCountry.getId());
|
||||
|
||||
getCustomRelevantRouteSections(section);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
private List<RouteSection> getCustomRelevantRouteSections(List<RouteSection> sections) {
|
||||
|
||||
List<RouteSection> customSections = new ArrayList<>();
|
||||
|
||||
for(RouteSection section : sections) {
|
||||
if(CustomUnionType.EU == getCustomUnionByRouteNodeId(section.getFromRouteNodeId()) &&
|
||||
CustomUnionType.EU ==getCustomUnionByRouteNodeId(section.getToRouteNodeId())) {
|
||||
customSections.add(section);
|
||||
}
|
||||
}
|
||||
|
||||
return customSections;
|
||||
}
|
||||
|
||||
private CustomUnionType getCustomUnionByRouteNodeId(Integer routeNodeId) {
|
||||
var node = routeNodeRepository.getById(routeNodeId).orElseThrow();
|
||||
return getCustomUnionByCountryId(node.getCountryId());
|
||||
}
|
||||
|
||||
private CustomUnionType getCustomUnionByCountryId(Integer countryId) {
|
||||
var property = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, countryId).orElseThrow();
|
||||
return CustomUnionType.valueOf(property.getCurrentValue().toUpperCase());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class Door2DoorCostCalculationService {
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class HandlingCostCalculationService {
|
||||
|
||||
|
||||
public void doCalculation() {
|
||||
|
||||
}
|
||||
|
||||
private void calculateHandlingCost() {
|
||||
}
|
||||
|
||||
private void calculateInventoryCost() {
|
||||
|
||||
}
|
||||
private void calculateCapitalCost() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.dto.generic.ContainerType;
|
||||
import de.avatic.lcc.dto.generic.RateType;
|
||||
import de.avatic.lcc.dto.generic.TransportType;
|
||||
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.packaging.PackagingType;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.route.Destination;
|
||||
import de.avatic.lcc.model.premises.route.RouteNode;
|
||||
import de.avatic.lcc.model.premises.route.RouteSection;
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.model.rates.ContainerRate;
|
||||
import de.avatic.lcc.model.rates.MatrixRate;
|
||||
import de.avatic.lcc.model.utils.DimensionUnit;
|
||||
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
|
||||
import de.avatic.lcc.repositories.rates.MatrixRateRepository;
|
||||
import de.avatic.lcc.service.calculation.DistanceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Service
|
||||
public class RouteSectionCostCalculationService {
|
||||
|
||||
|
||||
private final ContainerCalculationService containerCalculationService;
|
||||
private final ContainerRateRepository containerRateRepository;
|
||||
private final MatrixRateRepository matrixRateRepository;
|
||||
private final RouteNodeRepository routeNodeRepository;
|
||||
private final DistanceService distanceService;
|
||||
private final PropertyRepository propertyRepository;
|
||||
|
||||
public RouteSectionCostCalculationService(ContainerCalculationService containerCalculationService, ContainerRateRepository containerRateRepository, MatrixRateRepository matrixRateRepository, RouteNodeRepository routeNodeRepository, DistanceService distanceService, PropertyRepository propertyRepository) {
|
||||
this.containerCalculationService = containerCalculationService;
|
||||
this.containerRateRepository = containerRateRepository;
|
||||
this.matrixRateRepository = matrixRateRepository;
|
||||
this.routeNodeRepository = routeNodeRepository;
|
||||
this.distanceService = distanceService;
|
||||
this.propertyRepository = propertyRepository;
|
||||
}
|
||||
|
||||
public CalculationJobRouteSection doCalculation(Premise premise, Destination destination, RouteSection section, ContainerType containerType) {
|
||||
CalculationJobRouteSection calculationJobRouteSection = new CalculationJobRouteSection();
|
||||
|
||||
double utilization = getUtilization(section);
|
||||
RouteNode fromNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
|
||||
RouteNode toNode = routeNodeRepository.getById(section.getFromRouteNodeId()).orElseThrow();
|
||||
double distance = getDistance(fromNode, toNode);
|
||||
|
||||
int annualAmount = destination.getAnnualAmount();
|
||||
var containerCalculationResult = containerCalculationService.doCalculation(createHu(premise), containerType);
|
||||
var annualVolume = annualAmount * containerCalculationResult.getHu().getVolume(DimensionUnit.M);
|
||||
var transitTime = 3;
|
||||
|
||||
ContainerRate containerRate = containerRateRepository.findRoute(section.getFromRouteNodeId(), section.getToRouteNodeId(), TransportType.valueOf(section.getTransportType().name())).orElseThrow();
|
||||
MatrixRate matrixRate = matrixRateRepository.getByCountryIds(fromNode.getCountryId(), toNode.getCountryId()).orElseThrow();
|
||||
|
||||
var rate = 0.0;
|
||||
if (RateType.CONTAINER == section.getRateType()) {
|
||||
rate = getContainerRate(containerRate, containerType);
|
||||
transitTime = containerRate.getLeadTime();
|
||||
} else if (RateType.MATRIX == section.getRateType()) {
|
||||
rate = getTruckRate(matrixRate, distance);
|
||||
}
|
||||
|
||||
double volumePrice;
|
||||
double weightPrice;
|
||||
|
||||
if (premise.getHuMixable()) {
|
||||
volumePrice = (rate / containerType.getVolume()) / utilization;
|
||||
weightPrice = (rate / containerCalculationResult.getMaxContainerWeight()) / utilization;
|
||||
|
||||
} else {
|
||||
volumePrice = (rate / containerType.getVolume()) / containerCalculationResult.getTotalUtilizationByVolume();
|
||||
weightPrice = (rate / containerCalculationResult.getMaxContainerWeight()) / containerCalculationResult.getTotalUtilizationByVolume();
|
||||
}
|
||||
|
||||
double annualCost = annualVolume * (containerCalculationResult.isWeightExceeded() ? weightPrice : volumePrice);
|
||||
|
||||
calculationJobRouteSection.setCbmPrice(!containerCalculationResult.isWeightExceeded());
|
||||
calculationJobRouteSection.setWeightPrice(containerCalculationResult.isWeightExceeded());
|
||||
calculationJobRouteSection.setCbmPrice(BigDecimal.valueOf(volumePrice));
|
||||
calculationJobRouteSection.setWeightPrice(BigDecimal.valueOf(weightPrice));
|
||||
|
||||
calculationJobRouteSection.setDistance(BigDecimal.valueOf(distance));
|
||||
calculationJobRouteSection.setRate(BigDecimal.valueOf(rate));
|
||||
|
||||
calculationJobRouteSection.setMainRun(section.getMainRun());
|
||||
calculationJobRouteSection.setPostRun(section.getPostRun());
|
||||
calculationJobRouteSection.setPreRun(section.getPreRun());
|
||||
|
||||
calculationJobRouteSection.setStacked(premise.getHuStackable());
|
||||
calculationJobRouteSection.setUnmixedPrice(premise.getHuMixable());
|
||||
|
||||
calculationJobRouteSection.setTransportType(TransportType.valueOf(section.getTransportType().name()));
|
||||
calculationJobRouteSection.setRateType(RateType.valueOf(section.getRateType().name()));
|
||||
|
||||
calculationJobRouteSection.setRate(BigDecimal.valueOf(rate));
|
||||
calculationJobRouteSection.setAnnualCost(BigDecimal.valueOf(annualCost));
|
||||
|
||||
calculationJobRouteSection.setTransitTime(transitTime);
|
||||
|
||||
return calculationJobRouteSection;
|
||||
}
|
||||
|
||||
private double getUtilization(RouteSection section) {
|
||||
double utilization = 0;
|
||||
if (section.getRateType() == RateType.CONTAINER) {
|
||||
utilization = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CONTAINER_UTIL).orElseThrow().getCurrentValue());
|
||||
}
|
||||
else if (section.getRateType() == RateType.MATRIX) {
|
||||
utilization = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TRUCK_UTIL).orElseThrow().getCurrentValue());
|
||||
} else
|
||||
throw new IllegalArgumentException("Unknown rate type");
|
||||
|
||||
return utilization;
|
||||
}
|
||||
|
||||
private PackagingDimension createHu(Premise premise) {
|
||||
var hu = new PackagingDimension();
|
||||
|
||||
hu.setWeight(premise.getIndividualHuWeight());
|
||||
hu.setWeightUnit(premise.getHuDisplayedWeightUnit());
|
||||
|
||||
hu.setHeight(premise.getIndividualHuHeight());
|
||||
hu.setWidth(premise.getIndividualHuWidth());
|
||||
hu.setLength(premise.getIndividualHuLength());
|
||||
hu.setDimensionUnit(premise.getHuDisplayedDimensionUnit());
|
||||
|
||||
hu.setContentUnitCount(premise.getHuUnitCount());
|
||||
hu.setType(PackagingType.HU);
|
||||
|
||||
return hu;
|
||||
}
|
||||
|
||||
|
||||
private double getContainerRate(ContainerRate rate, ContainerType containerType) {
|
||||
|
||||
switch (containerType) {
|
||||
case HQ -> {
|
||||
return rate.getRateHc().doubleValue();
|
||||
}
|
||||
case FEU -> {
|
||||
return rate.getRateFeu().doubleValue();
|
||||
}
|
||||
case TEU -> {
|
||||
return rate.getRateTeu().doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown container type");
|
||||
}
|
||||
|
||||
private double getDistance(RouteNode fromNode, RouteNode toNode) {
|
||||
return distanceService.getDistanceFast(
|
||||
fromNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()),
|
||||
toNode.getCountryId(), new Location(fromNode.getGeoLng().doubleValue(), fromNode.getGeoLat().doubleValue()));
|
||||
}
|
||||
|
||||
private double getTruckRate(MatrixRate rate, double distance) {
|
||||
|
||||
return rate.getRate().doubleValue() * distance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package de.avatic.lcc.service.calculation.steps;
|
||||
|
||||
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ShippingFrequencyCalculationService {
|
||||
|
||||
private final PropertyRepository propertyRepository;
|
||||
|
||||
public ShippingFrequencyCalculationService(PropertyRepository propertyRepository) {
|
||||
this.propertyRepository = propertyRepository;
|
||||
}
|
||||
|
||||
public int doCalculation(int huAnnualAmount) {
|
||||
var minAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MIN).orElseThrow().getCurrentValue());
|
||||
var maxAnnualFrequency = Integer.parseInt(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FREQ_MAX).orElseThrow().getCurrentValue());
|
||||
|
||||
if (huAnnualAmount > maxAnnualFrequency)
|
||||
return maxAnnualFrequency;
|
||||
|
||||
return Math.max(huAnnualAmount, minAnnualFrequency);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package de.avatic.lcc.service.excelMapper;
|
||||
|
||||
import de.avatic.lcc.dto.generic.TransportType;
|
||||
import de.avatic.lcc.model.bulk.ContainerRateHeader;
|
||||
import de.avatic.lcc.model.bulk.HiddenNodeHeader;
|
||||
import de.avatic.lcc.model.bulk.HiddenTableType;
|
||||
import de.avatic.lcc.model.rates.ContainerRate;
|
||||
import de.avatic.lcc.model.rates.ContainerRateType;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
|
||||
import de.avatic.lcc.service.bulk.helper.ConstraintGenerator;
|
||||
|
|
@ -56,7 +56,7 @@ public class ContainerRateExcelMapper {
|
|||
constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.FROM_NODE.ordinal(), namedRange.getNameName());
|
||||
constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.TO_NODE.ordinal(), namedRange.getNameName());
|
||||
|
||||
constraintGenerator.createEnumConstraint(sheet, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), ContainerRateType.class);
|
||||
constraintGenerator.createEnumConstraint(sheet, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), TransportType.class);
|
||||
constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_FEU.ordinal(), 0.0, 1000000.0);
|
||||
constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_TEU.ordinal(),0.0, 1000000.0);
|
||||
constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_HC.ordinal(),0.0, 1000000.0);
|
||||
|
|
@ -78,9 +78,9 @@ public class ContainerRateExcelMapper {
|
|||
|
||||
entity.setFromNodeId(nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.FROM_NODE.ordinal()).getStringCellValue()).orElseThrow().getId());
|
||||
entity.setFromNodeId(nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.TO_NODE.ordinal()).getStringCellValue()).orElseThrow().getId());
|
||||
entity.setType(ContainerRateType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
|
||||
entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
|
||||
entity.setLeadTime(Double.valueOf(row.getCell(ContainerRateHeader.LEAD_TIME.ordinal()).getNumericCellValue()).intValue());
|
||||
entity.setType(ContainerRateType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
|
||||
entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
|
||||
entity.setRateFeu(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_FEU.ordinal()).getNumericCellValue()));
|
||||
entity.setRateTeu(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_TEU.ordinal()).getNumericCellValue()));
|
||||
entity.setRateHc(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_HC.ordinal()).getNumericCellValue()));
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class ExcelReportingService {
|
|||
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();
|
||||
List<ReportFlattener> flatterers = reports.stream().map(ReportFlattener::new).toList();
|
||||
|
||||
|
||||
while(true) {
|
||||
|
|
@ -53,7 +53,7 @@ public class ExcelReportingService {
|
|||
|
||||
var row = sheet.createRow(sheet.getLastRowNum() + 1);
|
||||
int cellIdx = 1 /* 0 is the header column */;
|
||||
for(ReportFlattener flattener : flatteners) {
|
||||
for(ReportFlattener flattener : flatterers) {
|
||||
|
||||
Cell cell = row.createCell(cellIdx);
|
||||
|
||||
|
|
|
|||
|
|
@ -329,6 +329,9 @@ CREATE TABLE IF NOT EXISTS premise
|
|||
material_id INT NOT NULL,
|
||||
supplier_node_id INT,
|
||||
user_supplier_node_id INT,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
packaging_id INT DEFAULT NULL,
|
||||
user_id INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
|
@ -378,9 +381,11 @@ CREATE TABLE IF NOT EXISTS premise_destination
|
|||
repacking_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
handling_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),
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
FOREIGN KEY (premise_id) REFERENCES premise (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
FOREIGN KEY (destination_node_id) REFERENCES node (id),
|
||||
INDEX idx_destination_node_id (destination_node_id),
|
||||
INDEX idx_premise_id (premise_id)
|
||||
|
|
@ -502,7 +507,7 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination
|
|||
transportation_type CHAR(8) CHECK (transportation_type IN
|
||||
('TEU', 'FEU', 'HC', 'TRUCK')),
|
||||
hu_per_layer INT UNSIGNED NOT NULL COMMENT 'number of handling units per layer',
|
||||
layer_structure JSON NOT NULL COMMENT 'json representation of a single layer',
|
||||
layer_structure JSON COMMENT 'json representation of a single layer',
|
||||
layer_count INT UNSIGNED NOT NULL COMMENT 'number of layers per full container or truck',
|
||||
transport_weight_exceeded BOOLEAN DEFAULT FALSE COMMENT 'limiting factor: TRUE if weight limited or FALSE if volume limited',
|
||||
transports_per_year DECIMAL(15, 2) NOT NULL COMMENT 'TODO: what is this?!',
|
||||
|
|
@ -523,8 +528,8 @@ CREATE TABLE IF NOT EXISTS calculation_job_route_section
|
|||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_route_section_id INT NOT NULL,
|
||||
calculation_job_destination_id INT NOT NULL,
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')),
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')),
|
||||
is_unmixed_price BOOLEAN DEFAULT FALSE,
|
||||
is_cbm_price BOOLEAN DEFAULT FALSE,
|
||||
is_weight_price BOOLEAN DEFAULT FALSE,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue