Add Python script for converting container rates from Excel to SQL
Bugfixing Destination Creation
This commit is contained in:
parent
ad30f00492
commit
c03cbfb774
25 changed files with 3471 additions and 2042 deletions
6
pom.xml
6
pom.xml
|
|
@ -64,6 +64,12 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>26.0.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package de.avatic.lcc.controller.calculation;
|
|||
import de.avatic.lcc.dto.calculation.CalculationStatus;
|
||||
import de.avatic.lcc.dto.calculation.DestinationDTO;
|
||||
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
||||
import de.avatic.lcc.dto.calculation.create.CreatePremiseDTO;
|
||||
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
||||
|
|
@ -12,7 +13,6 @@ import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
|
|||
import de.avatic.lcc.dto.calculation.edit.masterData.MaterialUpdateDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.masterData.PackagingUpdateDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO;
|
||||
import de.avatic.lcc.dto.generic.LocationDTO;
|
||||
import de.avatic.lcc.service.access.DestinationService;
|
||||
import de.avatic.lcc.service.access.PremisesService;
|
||||
import de.avatic.lcc.service.calculation.ChangeMaterialService;
|
||||
|
|
@ -20,6 +20,7 @@ import de.avatic.lcc.service.calculation.ChangeSupplierService;
|
|||
import de.avatic.lcc.service.calculation.PremiseCreationService;
|
||||
import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService;
|
||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
|
@ -75,46 +76,23 @@ public class PremiseController {
|
|||
}
|
||||
|
||||
@PostMapping({"/create", "/create/"})
|
||||
public ResponseEntity<List<PremiseDetailDTO>> createPremises(@RequestParam("material") List<String> materialIds,
|
||||
@RequestParam(value = "supplier", required = false) List<String> supplierIds,
|
||||
@RequestParam(name = "user_supplier", required = false) List<String> userSupplierIds,
|
||||
@RequestParam(name = "from_scratch", defaultValue = "true") boolean createEmpty) {
|
||||
public ResponseEntity<List<PremiseDetailDTO>> createPremises(@RequestBody @Valid CreatePremiseDTO dto) {
|
||||
|
||||
List<Integer> suppliers;
|
||||
List<Integer> userSuppliers;
|
||||
List<Integer> materials;
|
||||
|
||||
try {
|
||||
suppliers = supplierIds == null ? Collections.emptyList() : supplierIds.stream().map(Integer::parseInt).toList();
|
||||
userSuppliers = userSupplierIds == null ? Collections.emptyList() : userSupplierIds.stream().map(Integer::parseInt).toList();
|
||||
materials = materialIds.stream().map(Integer::parseInt).toList();
|
||||
|
||||
if(suppliers.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("Supplier ID must be greater than or equal to 1");
|
||||
|
||||
if(userSuppliers.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("User supplier ID must be greater than or equal to 1");
|
||||
|
||||
if(materials.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("Material ID must be greater than or equal to 1");
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidArgumentException("Invalid material or supplier ID provided. Suppliers: "
|
||||
+ supplierIds + ", userSuppliers: " + userSupplierIds + ", materials: " + materialIds + ".");
|
||||
}
|
||||
|
||||
if (suppliers.isEmpty() && userSuppliers.isEmpty()) {
|
||||
if ((dto.getSupplierIds() == null || dto.getSupplierIds().isEmpty()) && (dto.getUserSupplierIds() == null || dto.getUserSupplierIds().isEmpty())) {
|
||||
throw new InvalidArgumentException("Either suppliers or userSuppliers must be provided. Suppliers: "
|
||||
+ supplierIds + ", userSuppliers: " + userSupplierIds + ".");
|
||||
+ dto.getSupplierIds() + ", userSuppliers: " + dto.getUserSupplierIds() + ".");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(premiseCreationService.createPremises(materials, suppliers, userSuppliers, createEmpty));
|
||||
return ResponseEntity.ok(premiseCreationService.createPremises(
|
||||
dto.getMaterialIds(),
|
||||
dto.getSupplierIds() == null ? Collections.emptyList() : dto.getSupplierIds(),
|
||||
dto.getUserSupplierIds() == null ? Collections.emptyList() : dto.getUserSupplierIds(),
|
||||
dto.createEmpty()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping({"/edit", "/edit/"})
|
||||
public ResponseEntity<List<PremiseDetailDTO>> getPremises(@RequestParam("premiss_ids") List<Integer> premissIds) {
|
||||
public ResponseEntity<List<PremiseDetailDTO>> getPremises(@RequestBody List<Integer> premissIds) {
|
||||
return ResponseEntity.ok(premisesServices.getPremises(premissIds));
|
||||
}
|
||||
|
||||
|
|
@ -135,19 +113,22 @@ public class PremiseController {
|
|||
}
|
||||
|
||||
|
||||
@PutMapping({"/status/{processing_id}", "/status/{processing_id}/"})
|
||||
public ResponseEntity<HashMap<String, String>> updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updatePackaging(packagingDTO));
|
||||
@PostMapping({"/packaging", "/packaging/"})
|
||||
public ResponseEntity<Void> updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) {
|
||||
premisesServices.updatePackaging(packagingDTO);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping({"/material", "/material/"})
|
||||
public ResponseEntity<HashMap<String, String>> updateMaterial(@RequestBody MaterialUpdateDTO materialUpdateDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updateMaterial(materialUpdateDTO));
|
||||
public ResponseEntity<Void> updateMaterial(@RequestBody @Valid MaterialUpdateDTO materialUpdateDTO) {
|
||||
premisesServices.updateMaterial(materialUpdateDTO);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PutMapping({"/price", "/price/"})
|
||||
public ResponseEntity<HashMap<String, String>> updatePrice(@RequestBody PriceUpdateDTO priceUpdateDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updatePrice(priceUpdateDTO));
|
||||
@PostMapping({"/price", "/price/"})
|
||||
public ResponseEntity<Void> updatePrice(@RequestBody @Valid PriceUpdateDTO priceUpdateDTO) {
|
||||
premisesServices.updatePrice(priceUpdateDTO);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping({"/destination", "/destination/"})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package de.avatic.lcc.dto.calculation.create;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CreatePremiseDTO {
|
||||
|
||||
@Valid
|
||||
@JsonProperty("material")
|
||||
private List<@Min(1) Integer> materialIds;
|
||||
|
||||
@Valid
|
||||
@JsonProperty("supplier")
|
||||
private List<@Min(1) Integer> supplierIds;
|
||||
|
||||
@JsonProperty("user_supplier")
|
||||
private List<@Min(1) Integer> userSupplierIds;
|
||||
|
||||
@JsonProperty("from_scratch")
|
||||
private boolean createEmpty;
|
||||
|
||||
public List<Integer> getMaterialIds() {
|
||||
return materialIds;
|
||||
}
|
||||
|
||||
public void setMaterialIds(List<Integer> materialIds) {
|
||||
this.materialIds = materialIds;
|
||||
}
|
||||
|
||||
public List<Integer> getSupplierIds() {
|
||||
return supplierIds;
|
||||
}
|
||||
|
||||
public void setSupplierIds(List<Integer> supplierIds) {
|
||||
this.supplierIds = supplierIds;
|
||||
}
|
||||
|
||||
public List<Integer> getUserSupplierIds() {
|
||||
return userSupplierIds;
|
||||
}
|
||||
|
||||
public void setUserSupplierIds(List<Integer> userSupplierIds) {
|
||||
this.userSupplierIds = userSupplierIds;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean createEmpty() {
|
||||
return this.createEmpty;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setCreateEmpty(boolean createEmpty) {
|
||||
this.createEmpty = createEmpty;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package de.avatic.lcc.dto.calculation.edit.masterData;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -11,9 +12,13 @@ public class MaterialUpdateDTO {
|
|||
private List<Integer> premiseIds;
|
||||
|
||||
@JsonProperty("hs_code")
|
||||
@Size(min = 8, max = 11, message = "HS code must be between 8 and 11 characters")
|
||||
@Pattern(regexp = "^\\d+$", message = "HS code must contain only digits")
|
||||
private String hsCode;
|
||||
|
||||
@JsonProperty("tariff_rate")
|
||||
@DecimalMin(value = "0.00", message = "Tariff_rate must be greater than or equal 0")
|
||||
@Digits(integer = 4, fraction = 4, message = "Tariff rate must have at most 4 decimal places")
|
||||
private Number tariffRate;
|
||||
|
||||
public String getHsCode() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.dto.calculation.edit.masterData;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.avatic.lcc.dto.generic.DimensionDTO;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ public class PackagingUpdateDTO {
|
|||
@JsonProperty("is_stackable")
|
||||
private Boolean isStackable;
|
||||
|
||||
private List<Integer> premiseIds;
|
||||
private List<@Min(1) Integer> premiseIds;
|
||||
|
||||
|
||||
public DimensionDTO getDimensions() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package de.avatic.lcc.dto.calculation.edit.masterData;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.Digits;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -8,16 +12,21 @@ import java.util.List;
|
|||
public class PriceUpdateDTO {
|
||||
|
||||
@JsonProperty("premise_ids")
|
||||
private List<Integer> premiseIds;
|
||||
private List<@Min(1) Integer> premiseIds;
|
||||
|
||||
@DecimalMin(value = "0.01", message = "Amount must be greater than 0")
|
||||
@Digits(integer = 13, fraction = 2, message = "Amount must have at most 2 decimal places")
|
||||
private Number price;
|
||||
|
||||
@JsonProperty("oversea_share")
|
||||
@DecimalMin(value = "0.00", message = "Amount must be greater than or equal 0")
|
||||
@Digits(integer = 4, fraction = 4, message = "Amount must have at most 4 decimal places")
|
||||
private Number overseaShare;
|
||||
|
||||
@JsonProperty("fca_fee_included")
|
||||
private Boolean includeFcaFee;
|
||||
|
||||
|
||||
public Number getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.model.nodes;
|
||||
|
||||
import org.jetbrains.annotations.Debug.Renderer;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Renderer(text = "\"Node: \" + externalMappingId")
|
||||
@Validated
|
||||
public class Node {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class Destination {
|
|||
|
||||
private Boolean isD2d;
|
||||
|
||||
private int leadTimeD2d;
|
||||
private Integer leadTimeD2d;
|
||||
|
||||
private BigDecimal repackingCost;
|
||||
|
||||
|
|
@ -31,11 +31,11 @@ public class Destination {
|
|||
|
||||
private Integer countryId;
|
||||
|
||||
public int getLeadTimeD2d() {
|
||||
public Integer getLeadTimeD2d() {
|
||||
return leadTimeD2d;
|
||||
}
|
||||
|
||||
public void setLeadTimeD2d(int leadTimeD2d) {
|
||||
public void setLeadTimeD2d(Integer leadTimeD2d) {
|
||||
this.leadTimeD2d = leadTimeD2d;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.model.utils;
|
|||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* An enumeration representing the units of dimension measurement.
|
||||
|
|
@ -32,8 +33,8 @@ public enum DimensionUnit {
|
|||
return displayedName;
|
||||
}
|
||||
|
||||
public Number convertToMM(Number value) {
|
||||
return DimensionUnit.MM.convertFrom(value, this).intValue();
|
||||
public Integer convertToMM(Number value) {
|
||||
return Math.toIntExact(Math.round(DimensionUnit.MM.convertFrom(value, this)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,9 +45,9 @@ public enum DimensionUnit {
|
|||
* @return the converted value
|
||||
* @throws IllegalArgumentException if value or fromUnit is null
|
||||
*/
|
||||
public Number convertFrom(Number value, DimensionUnit fromUnit) {
|
||||
public Double convertFrom(Number value, DimensionUnit fromUnit) {
|
||||
if (value == null || fromUnit == null) {
|
||||
throw new IllegalArgumentException("Value and fromUnit must not be null");
|
||||
throw new InvalidArgumentException("Value and fromUnit must not be null");
|
||||
}
|
||||
|
||||
// Convert to base unit (millimeters)
|
||||
|
|
@ -63,7 +64,7 @@ public enum DimensionUnit {
|
|||
* @return the converted value in the target unit
|
||||
* @throws IllegalArgumentException if the provided value is null
|
||||
*/
|
||||
public Number convertFromMM(Number value) {
|
||||
public Double convertFromMM(Number value) {
|
||||
return convertFrom(value, MM);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public enum WeightUnit {
|
|||
* @return the converted value
|
||||
* @throws IllegalArgumentException if value or fromUnit is null
|
||||
*/
|
||||
public Number convertFrom(Number value, WeightUnit fromUnit) {
|
||||
public Double convertFrom(Number value, WeightUnit fromUnit) {
|
||||
if (value == null || fromUnit == null) {
|
||||
throw new IllegalArgumentException("Value and fromUnit must not be null");
|
||||
}
|
||||
|
|
@ -50,11 +50,11 @@ public enum WeightUnit {
|
|||
return valueInBaseUnit / this.baseFactor;
|
||||
}
|
||||
|
||||
public Number convertFromG(Number value) {
|
||||
public Double convertFromG(Number value) {
|
||||
return convertFrom(value, G);
|
||||
}
|
||||
|
||||
public Number convertToG(Double weight) {
|
||||
return WeightUnit.G.convertFrom(weight, this);
|
||||
public Integer convertToG(Double weight) {
|
||||
return Math.toIntExact(Math.round(WeightUnit.G.convertFrom(weight, this)));
|
||||
}
|
||||
}
|
||||
|
|
@ -289,7 +289,7 @@ public class NodeRepository {
|
|||
|
||||
chains.forEach(chain -> {
|
||||
|
||||
var currentChain = new ArrayList<Node>();
|
||||
var currentChain = new ArrayList<Node>(Collections.nCopies(chain.size(), null));
|
||||
resolvedChains.add(currentChain);
|
||||
|
||||
/*
|
||||
|
|
@ -302,7 +302,7 @@ public class NodeRepository {
|
|||
if (predecessor.isEmpty()) {
|
||||
throw new RuntimeException("Predecessor not found for chain " + chain + " and sequence number " + sequenceNumber);
|
||||
}
|
||||
currentChain.add(sequenceNumber, predecessor.get());
|
||||
currentChain.set(sequenceNumber - 1, predecessor.get());
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -329,7 +329,7 @@ public class NodeRepository {
|
|||
) <= ?
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, new NodeMapper(), node.getGeoLat(), node.getGeoLng(), node.getGeoLat());
|
||||
return jdbcTemplate.query(query, new NodeMapper(), node.getGeoLat(), node.getGeoLng(), node.getGeoLat(),regionRadius);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -345,14 +345,13 @@ public class NodeRepository {
|
|||
*/
|
||||
public List<Node> getAllOutboundFor(Integer countryId) {
|
||||
String query = """
|
||||
SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source,
|
||||
node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required
|
||||
SELECT node.*
|
||||
FROM node
|
||||
LEFT JOIN outbound_country_mapping ON outbound_country_mapping.node_id = node.id
|
||||
WHERE node.is_deprecated = FALSE AND (outbound_country_mapping.country_id = ? OR (node.is_intermediate = TRUE AND node.country_id = ?))
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(query, new NodeMapper(), countryId);
|
||||
return jdbcTemplate.query(query, new NodeMapper(), countryId, countryId);
|
||||
}
|
||||
|
||||
public List<Node> findNodeListsForReportingByPeriodId(Integer materialId, Integer periodId) {
|
||||
|
|
@ -386,7 +385,6 @@ public class NodeRepository {
|
|||
|
||||
}
|
||||
|
||||
|
||||
private class NodeMapper implements RowMapper<Node> {
|
||||
|
||||
@Override
|
||||
|
|
@ -407,6 +405,8 @@ public class NodeRepository {
|
|||
|
||||
data.setDeprecated(rs.getBoolean("is_deprecated"));
|
||||
|
||||
data.setExternalMappingId(rs.getString("external_mapping_id"));
|
||||
|
||||
data.setNodePredecessors(getPredecessorsOf(data.getId()));
|
||||
data.setOutboundCountries(getOutboundCountriesOf(data.getId()));
|
||||
|
||||
|
|
|
|||
|
|
@ -95,13 +95,24 @@ public class DestinationRepository {
|
|||
|
||||
jdbcTemplate.update(connection -> {
|
||||
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setInt(1, destination.getAnnualAmount());
|
||||
|
||||
if(destination.getAnnualAmount() == null)
|
||||
ps.setNull(1, java.sql.Types.INTEGER);
|
||||
else
|
||||
ps.setInt(1, destination.getAnnualAmount());
|
||||
|
||||
ps.setInt(2, destination.getPremiseId());
|
||||
ps.setInt(3, destination.getDestinationNodeId());
|
||||
ps.setInt(4, destination.getCountryId());
|
||||
ps.setBigDecimal(5, destination.getRateD2d());
|
||||
ps.setInt(6, destination.getLeadTimeD2d());
|
||||
|
||||
if(destination.getLeadTimeD2d() == null)
|
||||
ps.setNull(6, java.sql.Types.INTEGER);
|
||||
else
|
||||
ps.setInt(6, destination.getLeadTimeD2d());
|
||||
|
||||
ps.setBoolean(7, destination.getD2d());
|
||||
|
||||
ps.setBigDecimal(8, destination.getRepackingCost());
|
||||
ps.setBigDecimal(9, destination.getHandlingCost());
|
||||
ps.setBigDecimal(10, destination.getDisposalCost());
|
||||
|
|
|
|||
|
|
@ -203,95 +203,191 @@ public class PremiseRepository {
|
|||
@Transactional
|
||||
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension hu, Boolean stackable, Boolean mixable) {
|
||||
|
||||
if (premiseIds == null || premiseIds.isEmpty() || userId == null || hu == null) {
|
||||
|
||||
if (premiseIds == null || premiseIds.isEmpty() || userId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isStackable = stackable != null ? stackable : false;
|
||||
boolean isMixable = mixable != null ? mixable : false;
|
||||
|
||||
MapSqlParameterSource params = new MapSqlParameterSource();
|
||||
params.addValue("length", hu.getLength());
|
||||
params.addValue("height", hu.getHeight());
|
||||
params.addValue("width", hu.getWidth());
|
||||
params.addValue("weight", hu.getWeight());
|
||||
params.addValue("dimensionUnit", hu.getDimensionUnit().name());
|
||||
params.addValue("weightUnit", hu.getWeightUnit().name());
|
||||
params.addValue("unitCount", hu.getContentUnitCount());
|
||||
params.addValue("stackable", isStackable);
|
||||
params.addValue("mixable", isMixable);
|
||||
params.addValue("userId", userId);
|
||||
params.addValue("premiseIds", premiseIds);
|
||||
|
||||
String sql = """
|
||||
UPDATE premise
|
||||
SET individual_hu_length = :length,
|
||||
individual_hu_height = :height,
|
||||
individual_hu_width = :width,
|
||||
individual_hu_weight = :weight,
|
||||
hu_displayed_dimension_unit = :dimensionUnit,
|
||||
hu_displayed_weight_unit = :weightUnit,
|
||||
hu_unit_count = :unitCount,
|
||||
hu_stackable = :stackable,
|
||||
hu_mixable = :mixable
|
||||
WHERE user_id = :userId AND id IN (:premiseIds)
|
||||
""";
|
||||
StringBuilder sqlBuilder = new StringBuilder("UPDATE premise SET ");
|
||||
List<String> setClauses = new ArrayList<>();
|
||||
|
||||
namedParameterJdbcTemplate.update(sql, params);
|
||||
// Handle PackagingDimension hu fields
|
||||
if (hu != null) {
|
||||
if (hu.getLength() != null) {
|
||||
setClauses.add("individual_hu_length = :length");
|
||||
params.addValue("length", hu.getLength());
|
||||
}
|
||||
|
||||
if (hu.getHeight() != null) {
|
||||
setClauses.add("individual_hu_height = :height");
|
||||
params.addValue("height", hu.getHeight());
|
||||
}
|
||||
|
||||
if (hu.getWidth() != null) {
|
||||
setClauses.add("individual_hu_width = :width");
|
||||
params.addValue("width", hu.getWidth());
|
||||
}
|
||||
|
||||
if (hu.getWeight() != null) {
|
||||
setClauses.add("individual_hu_weight = :weight");
|
||||
params.addValue("weight", hu.getWeight());
|
||||
}
|
||||
|
||||
if (hu.getDimensionUnit() != null) {
|
||||
setClauses.add("hu_displayed_dimension_unit = :dimensionUnit");
|
||||
params.addValue("dimensionUnit", hu.getDimensionUnit().name());
|
||||
}
|
||||
|
||||
if (hu.getWeightUnit() != null) {
|
||||
setClauses.add("hu_displayed_weight_unit = :weightUnit");
|
||||
params.addValue("weightUnit", hu.getWeightUnit().name());
|
||||
}
|
||||
|
||||
if (hu.getContentUnitCount() != null) {
|
||||
setClauses.add("hu_unit_count = :unitCount");
|
||||
params.addValue("unitCount", hu.getContentUnitCount());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle stackable
|
||||
if (stackable != null) {
|
||||
setClauses.add("hu_stackable = :stackable");
|
||||
params.addValue("stackable", stackable);
|
||||
}
|
||||
|
||||
// Handle mixable
|
||||
if (mixable != null) {
|
||||
setClauses.add("hu_mixable = :mixable");
|
||||
params.addValue("mixable", mixable);
|
||||
}
|
||||
|
||||
// If no fields to update, return early
|
||||
if (setClauses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the complete SQL
|
||||
sqlBuilder.append(String.join(", ", setClauses));
|
||||
sqlBuilder.append(" WHERE user_id = :userId AND id IN (:premiseIds)");
|
||||
|
||||
String sql = sqlBuilder.toString();
|
||||
var affectedRows = namedParameterJdbcTemplate.update(sql, params);
|
||||
|
||||
if(affectedRows != premiseIds.size())
|
||||
throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateMaterial(List<Integer> premiseIds, Integer userId, String hsCode, BigDecimal tariffRate) {
|
||||
|
||||
// Build the SET clause dynamically based on non-null parameters
|
||||
List<String> setClauses = new ArrayList<>();
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
|
||||
if (hsCode != null) {
|
||||
setClauses.add("hs_code = ?");
|
||||
parameters.add(hsCode);
|
||||
}
|
||||
|
||||
if (tariffRate != null) {
|
||||
setClauses.add("tariff_rate = ?");
|
||||
parameters.add(tariffRate);
|
||||
}
|
||||
|
||||
// If no fields to update, return early
|
||||
if (setClauses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add userId to parameters
|
||||
parameters.add(userId);
|
||||
|
||||
// Add premiseIds to parameters
|
||||
parameters.addAll(premiseIds);
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
|
||||
String setClause = String.join(", ", setClauses);
|
||||
|
||||
String query = """
|
||||
UPDATE premise
|
||||
SET hs_code = ?,
|
||||
tariff_rate = ?
|
||||
WHERE user_id = ? AND id IN (""" + placeholders + ")";
|
||||
String query = "UPDATE premise SET " + setClause +
|
||||
" WHERE user_id = ? AND id IN (" + placeholders + ")";
|
||||
|
||||
jdbcTemplate.update(
|
||||
var affectedRows = jdbcTemplate.update(
|
||||
query,
|
||||
ps -> {
|
||||
|
||||
ps.setString(1, hsCode);
|
||||
ps.setBigDecimal(2, tariffRate);
|
||||
ps.setInt(3, userId);
|
||||
|
||||
for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
|
||||
ps.setInt(parameterIndex + 4, premiseIds.get(parameterIndex));
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
Object param = parameters.get(i);
|
||||
if (param instanceof String) {
|
||||
ps.setString(i + 1, (String) param);
|
||||
} else if (param instanceof BigDecimal) {
|
||||
ps.setBigDecimal(i + 1, (BigDecimal) param);
|
||||
} else if (param instanceof Integer) {
|
||||
ps.setInt(i + 1, (Integer) param);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if(affectedRows != premiseIds.size())
|
||||
throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updatePrice(List<Integer> premiseIds, int userId, BigDecimal price, Boolean includeFcaFee, BigDecimal overseaShare) {
|
||||
// Build dynamic SET clause based on non-null parameters
|
||||
List<String> setClauses = new ArrayList<>();
|
||||
if (price != null) {
|
||||
setClauses.add("material_cost = ?");
|
||||
}
|
||||
if (includeFcaFee != null) {
|
||||
setClauses.add("is_fca_enabled = ?");
|
||||
}
|
||||
if (overseaShare != null) {
|
||||
setClauses.add("oversea_share = ?");
|
||||
}
|
||||
|
||||
// If no parameters to update, return early
|
||||
if (setClauses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
|
||||
String setClause = String.join(", ", setClauses);
|
||||
|
||||
String query = """
|
||||
UPDATE premise
|
||||
SET material_cost = ?,
|
||||
is_fca_enabled = ?,
|
||||
oversea_share = ?
|
||||
WHERE user_id = ? AND id IN (""" + placeholders + ")";
|
||||
String query = "UPDATE premise SET " + setClause + " WHERE user_id = ? AND id IN (" + placeholders + ")";
|
||||
|
||||
jdbcTemplate.update(
|
||||
var affectedRows = jdbcTemplate.update(
|
||||
query,
|
||||
ps -> {
|
||||
int parameterIndex = 1;
|
||||
|
||||
ps.setBigDecimal(1, price);
|
||||
ps.setBoolean(2, includeFcaFee);
|
||||
ps.setBigDecimal(3, overseaShare);
|
||||
ps.setInt(4, userId);
|
||||
// Set the dynamic parameters in the same order as SET clauses
|
||||
if (price != null) {
|
||||
ps.setBigDecimal(parameterIndex++, price);
|
||||
}
|
||||
if (includeFcaFee != null) {
|
||||
ps.setBoolean(parameterIndex++, includeFcaFee);
|
||||
}
|
||||
if (overseaShare != null) {
|
||||
ps.setBigDecimal(parameterIndex++, overseaShare);
|
||||
}
|
||||
|
||||
for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
|
||||
ps.setInt(parameterIndex + 5, premiseIds.get(parameterIndex));
|
||||
// Set user_id parameter
|
||||
ps.setInt(parameterIndex++, userId);
|
||||
|
||||
// Set premise ID parameters
|
||||
for (Integer premiseId : premiseIds) {
|
||||
ps.setInt(parameterIndex++, premiseId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if(affectedRows != premiseIds.size())
|
||||
throw new DatabaseException("Premise update failed for " + premiseIds.size() + " premises. Affected rows: " + affectedRows);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public class ContainerRateRepository {
|
|||
LEFT JOIN validity_period ON validity_period.id = container_rate.validity_period_id
|
||||
WHERE validity_period.state = ?
|
||||
AND (container_rate.container_rate_type = ? OR container_rate.container_rate_type = ?)
|
||||
AND container_rate.from_node_id = = ? AND to_node.country_id IN (%s)""".formatted(
|
||||
AND container_rate.from_node_id = ? AND to_node.country_id IN (%s)""".formatted(
|
||||
destinationCountryPlaceholders);
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
|
@ -122,7 +122,7 @@ public class ContainerRateRepository {
|
|||
SELECT * FROM container_rate WHERE from_node_id = ? AND to_node_id = ? AND container_rate_type = ?
|
||||
""";
|
||||
|
||||
var route = jdbcTemplate.query(query, new ContainerRateMapper(), fromNodeId, toNodeId, type);
|
||||
var route = jdbcTemplate.query(query, new ContainerRateMapper(), fromNodeId, toNodeId, type.name());
|
||||
|
||||
if(route.isEmpty())
|
||||
return Optional.empty();
|
||||
|
|
|
|||
|
|
@ -71,14 +71,15 @@ public class DestinationService {
|
|||
destination.setPremiseId(premise.getId());
|
||||
destination.setAnnualAmount(0);
|
||||
destination.setD2d(false);
|
||||
destination.setLeadTimeD2d(0);
|
||||
destination.setRateD2d(BigDecimal.ZERO);
|
||||
destination.setLeadTimeD2d(null);
|
||||
destination.setRateD2d(null);
|
||||
destination.setDisposalCost(null);
|
||||
destination.setHandlingCost(null);
|
||||
destination.setRepackingCost(null);
|
||||
destination.setId(destinationRepository.insert(destination));
|
||||
destination.setCountryId(destinationNode.getCountryId());
|
||||
destination.setGeoLat(destinationNode.getGeoLat());
|
||||
destination.setGeoLng(destinationNode.getGeoLng());
|
||||
destination.setId(destinationRepository.insert(destination));
|
||||
|
||||
Node source = premise.getSupplierNodeId() == null ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow();
|
||||
findRouteAndSave(destination.getId(), destinationNode, source, premise.getSupplierNodeId() == null);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ public class PremisesService {
|
|||
//TODO check values. and return errors if needed
|
||||
var userId = 1; // todo get id from current user.
|
||||
|
||||
premiseRepository.updatePackaging(packagingDTO.getPremiseIds(), userId, dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions()), packagingDTO.getStackable(), packagingDTO.getMixable());
|
||||
var dimensions = packagingDTO.getDimensions() == null ? null : dimensionTransformer.toDimensionEntity(packagingDTO.getDimensions());
|
||||
|
||||
premiseRepository.updatePackaging(packagingDTO.getPremiseIds(), userId, dimensions, packagingDTO.getStackable(), packagingDTO.getMixable());
|
||||
|
||||
|
||||
return null;
|
||||
|
|
@ -118,8 +120,8 @@ public class PremisesService {
|
|||
//TODO check values. and return errors if needed
|
||||
var userId = 1; // todo get id from current user.
|
||||
|
||||
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), userId, materialUpdateDTO.getHsCode(), BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue()));
|
||||
|
||||
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
|
||||
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), userId, materialUpdateDTO.getHsCode(), tariffRate);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -128,7 +130,10 @@ public class PremisesService {
|
|||
//TODO check values. and return errors if needed
|
||||
var userId = 1; // todo get id from current user.
|
||||
|
||||
premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), userId, BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue()), priceUpdateDTO.getIncludeFcaFee(), BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue()));
|
||||
var price = priceUpdateDTO.getPrice() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue());
|
||||
var overseaShare = priceUpdateDTO.getOverseaShare() == null ? null : BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue());
|
||||
|
||||
premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), userId, price, priceUpdateDTO.getIncludeFcaFee(),overseaShare);
|
||||
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.service.calculation;
|
|||
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import org.jetbrains.annotations.Debug.Renderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -91,6 +92,7 @@ public class ChainResolver {
|
|||
* chain processing including the current position and validity status.
|
||||
* </p>
|
||||
*/
|
||||
@Renderer(text = "getFullChainView()")
|
||||
private static class ChainValidationObject {
|
||||
|
||||
/** Current position in the chain being processed */
|
||||
|
|
@ -155,6 +157,14 @@ public class ChainResolver {
|
|||
var nextCandidates = new ArrayList<List<Node>>();
|
||||
boolean shortChainFound = false;
|
||||
|
||||
/*
|
||||
* if we are at the end of the chain and all foreign chains are empty, there is no need to check any further
|
||||
* the chain object itself is a solution -> return empty.
|
||||
*/
|
||||
|
||||
if(chainPointer == chain.size() - 1 && foreignChains.stream().allMatch(List::isEmpty))
|
||||
return Collections.emptyList();
|
||||
|
||||
int foreignIdx = 0;
|
||||
for (int localIdx = chainPointer + 1; localIdx < chain.size(); localIdx++, foreignIdx++) {
|
||||
var localNode = chain.get(localIdx);
|
||||
|
|
@ -175,12 +185,22 @@ public class ChainResolver {
|
|||
candidates.clear();
|
||||
candidates.addAll(nextCandidates);
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
if (!shortChainFound)
|
||||
this.chainValid = false;
|
||||
if (candidates.isEmpty())
|
||||
break;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
/*
|
||||
* if there are no candidates left, there is no need to check any further.
|
||||
* -> set chain to invalid and return empty
|
||||
* (if any of the chains ended before check was finished (== shortChainFound), the chain object itself is a
|
||||
* solution -> so keep the chain valid and return empty)
|
||||
*/
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
if (!shortChainFound)
|
||||
this.chainValid = false;
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return mergeCandidates(candidates, foreignIdx);
|
||||
|
|
@ -216,7 +236,7 @@ public class ChainResolver {
|
|||
* @return true if there are more nodes in the chain, false otherwise
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return chainPointer < chain.size() - 1;
|
||||
return chainPointer < chain.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -258,7 +278,24 @@ public class ChainResolver {
|
|||
return (chain.stream().map(Node::getId)
|
||||
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
|
||||
.entrySet().stream()
|
||||
.noneMatch(entry -> entry.getValue() > 1));
|
||||
.anyMatch(entry -> entry.getValue() > 1));
|
||||
}
|
||||
|
||||
|
||||
private String getFullChainView() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(chainValid ? "✓ " : "✗ ");
|
||||
sb.append("Chain[").append(chainPointer).append("|").append(chain.size()).append("]: ");
|
||||
|
||||
for (int i = 0; i < Math.min(chain.size(), 5); i++) {
|
||||
if (i > 0) sb.append(" -> ");
|
||||
if (i == chainPointer) sb.append("[");
|
||||
sb.append(chain.get(i).getExternalMappingId());
|
||||
if (i == chainPointer) sb.append("]");
|
||||
}
|
||||
|
||||
if (chain.size() > 5) sb.append(" ...");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import de.avatic.lcc.repositories.NodeRepository;
|
|||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
|
||||
import de.avatic.lcc.repositories.rates.MatrixRateRepository;
|
||||
import org.jetbrains.annotations.Debug.Renderer;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -416,6 +417,8 @@ public class RoutingService {
|
|||
* - check if chain is routable
|
||||
* - add post run and main run
|
||||
*/
|
||||
var mainruns = container.getMainRuns();
|
||||
|
||||
for (var mainRun : container.getMainRuns()) {
|
||||
|
||||
Node mainRunEndNode = nodeRepository.getById(mainRun.getToNodeId()).orElseThrow();
|
||||
|
|
@ -423,50 +426,99 @@ public class RoutingService {
|
|||
|
||||
TemporaryRateObject mainRunObj = new TemporaryRateObject(mainRunStartNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.MAIN_RUN, mainRun);
|
||||
|
||||
// var postRuns = container.getPostRuns().get(mainRun.getId());
|
||||
//
|
||||
// var sortedChains = sortByQuality(destinationChains)
|
||||
|
||||
|
||||
for (var postRun : container.getPostRuns().get(mainRun.getId())) {
|
||||
|
||||
Node postRunEndNode = nodeRepository.getById(postRun.getToNodeId()).orElseThrow();
|
||||
TemporaryRateObject postRunObj = new TemporaryRateObject(postRunEndNode, mainRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
|
||||
TemporaryRateObject postRunObj = new TemporaryRateObject(mainRunEndNode,postRunEndNode, TemporaryRateObject.TemporaryRateObjectType.POST_RUN, postRun);
|
||||
|
||||
for (var chain : destinationChains) {
|
||||
ChainQuality quality = getChainQuality(chain, postRun, mainRun);
|
||||
var sortedChains = sortByQuality(destinationChains, postRun, mainRun);
|
||||
|
||||
for(ChainQuality quality : ChainQuality.values())
|
||||
{
|
||||
boolean qualityRoutable = false;
|
||||
|
||||
/* if connection quality is bad, do not try to route this and continue. */
|
||||
if (quality == ChainQuality.FALLBACK) continue;
|
||||
|
||||
boolean routable = true;
|
||||
TemporaryRouteObject routeObj = new TemporaryRouteObject();
|
||||
if(sortedChains.get(quality) == null) continue;
|
||||
|
||||
for (int idx = 1; idx < chain.size() - quality.getSizeOffset(); idx++) {
|
||||
Node startNode = chain.get(idx);
|
||||
Node endNode = chain.get(idx - 1);
|
||||
for(var chain : sortedChains.get(quality)) {
|
||||
boolean routable = true;
|
||||
TemporaryRouteObject routeObj = new TemporaryRouteObject();
|
||||
|
||||
var rate = connectNodes(startNode, endNode, container);
|
||||
for (int idx = 1; idx < chain.size() - quality.getSizeOffset(); idx++) {
|
||||
Node startNode = chain.get(idx);
|
||||
Node endNode = chain.get(idx - 1);
|
||||
|
||||
if (rate != null) {
|
||||
routeObj.addSection(rate);
|
||||
} else {
|
||||
// chain is not routable -> discard
|
||||
routable = false;
|
||||
break;
|
||||
var rate = connectNodes(startNode, endNode, container);
|
||||
|
||||
if (rate != null) {
|
||||
routeObj.addSection(rate);
|
||||
} else {
|
||||
// chain is not routable -> discard
|
||||
routable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(routable && quality == ChainQuality.LOW) {
|
||||
var rate = connectNodes(postRunEndNode, chain.getLast(), container);
|
||||
|
||||
if (rate != null) {
|
||||
routeObj.addSection(rate);
|
||||
} else {
|
||||
routable = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (routable) {
|
||||
qualityRoutable = true;
|
||||
routeObj.setQuality(quality);
|
||||
routeObj.addPostRunSection(postRunObj);
|
||||
routeObj.addMainRunSection(mainRunObj);
|
||||
container.addRoute(routeObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (routable) {
|
||||
routeObj.addPostRunSection(postRunObj);
|
||||
routeObj.addMainRunSection(mainRunObj);
|
||||
container.addRoute(routeObj);
|
||||
}
|
||||
/* if higher quality is routable, do not calculate lower qualities. */
|
||||
if(qualityRoutable)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<ChainQuality, List<List<Node>>> sortByQuality(List<List<Node>> chains, ContainerRate postRun, ContainerRate mainRun) {
|
||||
|
||||
Map<ChainQuality, List<List<Node>>> sortedChains = new HashMap<>();
|
||||
|
||||
for(var chain : chains) {
|
||||
ChainQuality chainQuality = getChainQuality(chain, postRun, mainRun);
|
||||
sortedChains.putIfAbsent(chainQuality, new ArrayList<>());
|
||||
sortedChains.get(chainQuality).add(chain);
|
||||
}
|
||||
|
||||
return sortedChains;
|
||||
}
|
||||
|
||||
|
||||
private ChainQuality getChainQuality(List<Node> chain, ContainerRate postRun, ContainerRate mainRun) {
|
||||
if (chain.getLast().getId().equals(postRun.getToNodeId())) {
|
||||
return ChainQuality.MEDIUM;
|
||||
} else if (chain.getLast().getId().equals(postRun.getFromNodeId()) && chain.get(chain.size() - 2).getId().equals(postRun.getToNodeId())) {
|
||||
} else if (chain.size() > 2
|
||||
&& chain.getLast().getId().equals(mainRun.getFromNodeId())
|
||||
&& chain.get(chain.size() - 2).getId().equals(postRun.getFromNodeId())
|
||||
&& chain.get(chain.size() - 3).getId().equals(postRun.getToNodeId())) {
|
||||
return ChainQuality.SUPERIOR;
|
||||
} else if (chain.size() > 1
|
||||
&& chain.getLast().getId().equals(postRun.getFromNodeId())
|
||||
&& chain.get(chain.size() - 2).getId().equals(postRun.getToNodeId())) {
|
||||
return ChainQuality.HIGH;
|
||||
} else if (chain.getLast().getCountryId().equals(postRun.getToCountryId())) {
|
||||
return ChainQuality.LOW;
|
||||
|
|
@ -505,7 +557,7 @@ public class RoutingService {
|
|||
}
|
||||
|
||||
private enum ChainQuality {
|
||||
HIGH(1), MEDIUM(0), LOW(0), FALLBACK(0);
|
||||
SUPERIOR(2), HIGH(1), MEDIUM(0), LOW(0), FALLBACK(0);
|
||||
|
||||
private final int sizeOffset;
|
||||
|
||||
|
|
@ -534,10 +586,14 @@ public class RoutingService {
|
|||
private final Node source;
|
||||
private final Node destination;
|
||||
/*
|
||||
* mainRuns and postRuns retrieved from database.
|
||||
* mainRuns (maps start node to rate) retrieved from the database.
|
||||
*/
|
||||
private Map<Integer, List<ContainerRate>> mainRuns;
|
||||
/*
|
||||
* postRuns (maps main_run container rate id to post_run container rate id) retrieved from the database.
|
||||
*/
|
||||
private Map<Integer, List<ContainerRate>> postRuns;
|
||||
|
||||
private MatrixRate sourceMatrixRate;
|
||||
|
||||
public TemporaryContainer(Node source, Node destination) {
|
||||
|
|
@ -603,6 +659,7 @@ public class RoutingService {
|
|||
}
|
||||
}
|
||||
|
||||
@Renderer(text = "getFullDebugText()")
|
||||
private static class TemporaryRouteObject {
|
||||
|
||||
private final List<TemporaryRateObject> sections;
|
||||
|
|
@ -613,6 +670,7 @@ public class RoutingService {
|
|||
private boolean nearBy = false;
|
||||
private boolean isCheapest = false;
|
||||
private boolean isFastest = false;
|
||||
private ChainQuality quality;
|
||||
|
||||
public TemporaryRouteObject() {
|
||||
sections = new ArrayList<>();
|
||||
|
|
@ -688,9 +746,32 @@ public class RoutingService {
|
|||
public Boolean isFastest() {
|
||||
return isFastest;
|
||||
}
|
||||
|
||||
public String getFullDebugText() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!sections.isEmpty()) {
|
||||
sb.append(sections.getLast().getFromNode().getExternalMappingId());
|
||||
for (var section : sections.reversed()) {
|
||||
sb.append(" -");
|
||||
sb.append(section.getType().debugText());
|
||||
sb.append("-> ");
|
||||
sb.append(section.getToNode().getExternalMappingId());
|
||||
}
|
||||
} else sb.append("Empty");
|
||||
|
||||
|
||||
return String.format("Route: [%s][%s]", quality.name(), sb.toString());
|
||||
}
|
||||
|
||||
public void setQuality(ChainQuality quality) {
|
||||
this.quality = quality;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Renderer(text = "getFullDebugText()")
|
||||
private static class TemporaryRateObject {
|
||||
|
||||
private final Node fromNode;
|
||||
|
|
@ -791,7 +872,24 @@ public class RoutingService {
|
|||
}
|
||||
|
||||
private enum TemporaryRateObjectType {
|
||||
MATRIX, CONTAINER, POST_RUN, MAIN_RUN;
|
||||
MATRIX("\uD83D\uDDA9"), CONTAINER("\uD83D\uDCE6"), POST_RUN("\uD83D\uDE9B"), MAIN_RUN("\uD83D\uDEA2");
|
||||
|
||||
private final String debugText;
|
||||
|
||||
TemporaryRateObjectType(String debugText) {
|
||||
this.debugText = debugText;
|
||||
}
|
||||
|
||||
public String debugText() {
|
||||
return debugText;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFullDebugText() {
|
||||
if(type.equals(TemporaryRateObjectType.MATRIX)) {
|
||||
return String.format("Rate [%s --> %s [%s, %.2f km]", fromNode.getExternalMappingId(), toNode.getExternalMappingId(), type, approxDistance);
|
||||
}
|
||||
return String.format("Rate [%s --> %s [%s]", fromNode.getExternalMappingId(), toNode.getExternalMappingId(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import de.avatic.lcc.dto.generic.DimensionDTO;
|
|||
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.util.exception.badrequest.InvalidArgumentException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
|
|
@ -27,14 +28,24 @@ public class DimensionTransformer {
|
|||
}
|
||||
|
||||
public PackagingDimension toDimensionEntity(DimensionDTO dto) {
|
||||
|
||||
if(dto.getDimensionUnit() == null) {
|
||||
throw new InvalidArgumentException("dimension_unit", "null");
|
||||
}
|
||||
|
||||
if(dto.getWeightUnit() == null) {
|
||||
throw new InvalidArgumentException("weight_unit", "null");
|
||||
}
|
||||
|
||||
var entity = new PackagingDimension();
|
||||
entity.setId(dto.getId());
|
||||
entity.setType(dto.getType());
|
||||
entity.setLength(dto.getDimensionUnit().convertToMM(dto.getLength()).intValue());
|
||||
entity.setWidth(dto.getDimensionUnit().convertToMM(dto.getWidth()).intValue());
|
||||
entity.setHeight(dto.getDimensionUnit().convertToMM(dto.getHeight()).intValue());
|
||||
|
||||
entity.setLength(dto.getDimensionUnit().convertToMM(dto.getLength()));
|
||||
entity.setWidth(dto.getDimensionUnit().convertToMM(dto.getWidth()));
|
||||
entity.setHeight( dto.getDimensionUnit().convertToMM(dto.getHeight()));
|
||||
entity.setDimensionUnit(dto.getDimensionUnit());
|
||||
entity.setWeight(dto.getWeightUnit().convertToG(dto.getWeight()).intValue());
|
||||
entity.setWeight(dto.getWeightUnit().convertToG(dto.getWeight()));
|
||||
entity.setWeightUnit(dto.getWeightUnit());
|
||||
entity.setContentUnitCount(dto.getContentUnitCount());
|
||||
entity.setDeprecated(dto.getDeprecated());
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
139
src/main/resources/master_data/excel_to_sql_converter_rate.py
Normal file
139
src/main/resources/master_data/excel_to_sql_converter_rate.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Excel zu SQL Konverter für Container Rates
|
||||
Konvertiert Excel-Datei mit Container-Raten in SQL INSERT-Statements
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def load_excel_data(file_path):
|
||||
"""
|
||||
Lädt die Excel-Datei und gibt die Daten als DataFrame zurück
|
||||
"""
|
||||
try:
|
||||
df = pd.read_excel(file_path)
|
||||
print(f"✓ Excel-Datei geladen: {len(df)} Zeilen")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"✗ Fehler beim Laden der Excel-Datei: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def group_and_process_data(df):
|
||||
"""
|
||||
Gruppiert die Daten nach From, To, Type und extrahiert die verschiedenen Raten
|
||||
"""
|
||||
# Nach From, To, Type gruppieren
|
||||
grouped = df.groupby(['From', 'To', 'Type'])
|
||||
|
||||
sql_statements = []
|
||||
|
||||
for (from_port, to_port, transport_type), group in grouped:
|
||||
# Initialisiere Werte
|
||||
rate_teu = 'NULL'
|
||||
rate_feu = 'NULL'
|
||||
rate_hc = 'NULL'
|
||||
lead_time = 'NULL'
|
||||
|
||||
# Lead time aus der ersten Zeile nehmen (sollte in allen Zeilen gleich sein)
|
||||
if not group.empty:
|
||||
lead_time = group.iloc[0]['lead_time']
|
||||
|
||||
# Raten nach container_type extrahieren
|
||||
for _, row in group.iterrows():
|
||||
container_type = row['container_type']
|
||||
rate = row['rate']
|
||||
|
||||
if container_type == "20'":
|
||||
rate_teu = f"{float(rate):.2f}"
|
||||
elif container_type == "40'":
|
||||
rate_feu = f"{float(rate):.2f}"
|
||||
elif container_type == "40'HC":
|
||||
rate_hc = f"{float(rate):.2f}"
|
||||
|
||||
# SQL INSERT Statement erstellen
|
||||
sql_statement = f"""INSERT INTO container_rate (
|
||||
from_node_id,
|
||||
to_node_id,
|
||||
container_rate_type,
|
||||
rate_teu,
|
||||
rate_feu,
|
||||
rate_hc,
|
||||
lead_time,
|
||||
validity_period_id
|
||||
) VALUES (
|
||||
(SELECT id FROM node WHERE external_mapping_id = '{from_port}'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = '{to_port}'),
|
||||
'{transport_type}',
|
||||
{rate_teu},
|
||||
{rate_feu},
|
||||
{rate_hc},
|
||||
{lead_time},
|
||||
@validity_period_id
|
||||
);"""
|
||||
|
||||
sql_statements.append(sql_statement)
|
||||
|
||||
return sql_statements
|
||||
|
||||
def write_sql_file(sql_statements, output_file):
|
||||
"""
|
||||
Schreibt die SQL-Statements in eine Datei
|
||||
"""
|
||||
try:
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
# Header schreiben
|
||||
f.write("-- Container Rate INSERT Statements\n")
|
||||
f.write("-- Generiert aus Excel-Datei\n\n")
|
||||
|
||||
# SQL Statements schreiben
|
||||
for statement in sql_statements:
|
||||
f.write(statement)
|
||||
f.write("\n\n")
|
||||
|
||||
print(f"✓ SQL-Datei erstellt: {output_file}")
|
||||
print(f"✓ {len(sql_statements)} INSERT-Statements generiert")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Fehler beim Schreiben der SQL-Datei: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
"""
|
||||
Hauptfunktion
|
||||
"""
|
||||
# Dateipfade
|
||||
excel_file = "container_rates.xlsx"
|
||||
sql_file = "08-data-containerrate.sql"
|
||||
|
||||
# Prüfen ob Excel-Datei existiert
|
||||
if not Path(excel_file).exists():
|
||||
print(f"✗ Excel-Datei nicht gefunden: {excel_file}")
|
||||
print("Bitte stellen Sie sicher, dass die Datei im aktuellen Verzeichnis liegt.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Container Rates Excel zu SQL Konverter")
|
||||
print("=" * 40)
|
||||
|
||||
# 1. Excel-Datei laden
|
||||
df = load_excel_data(excel_file)
|
||||
|
||||
# 2. Datenstruktur anzeigen
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
print(f"Eindeutige From-Werte: {df['From'].nunique()}")
|
||||
print(f"Eindeutige To-Werte: {df['To'].nunique()}")
|
||||
print(f"Eindeutige Type-Werte: {df['Type'].unique()}")
|
||||
print(f"Container Types: {df['container_type'].unique()}")
|
||||
|
||||
# 3. Daten gruppieren und SQL-Statements erstellen
|
||||
print("\n⚙️ Verarbeite Daten...")
|
||||
sql_statements = group_and_process_data(df)
|
||||
|
||||
# 4. SQL-Datei schreiben
|
||||
write_sql_file(sql_statements, sql_file)
|
||||
|
||||
print(f"\n✅ Fertig! SQL-Datei wurde erstellt: {sql_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -254,7 +254,7 @@ CREATE TABLE IF NOT EXISTS material
|
|||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
part_number CHAR(12) NOT NULL,
|
||||
normalized_part_number CHAR(12) NOT NULL,
|
||||
hs_code CHAR(8),
|
||||
hs_code CHAR(11),
|
||||
name VARCHAR(500) NOT NULL,
|
||||
is_deprecated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT `uq_normalized_part_number` UNIQUE (`normalized_part_number`)
|
||||
|
|
@ -342,7 +342,7 @@ CREATE TABLE IF NOT EXISTS premise
|
|||
material_cost DECIMAL(15, 2) DEFAULT NULL COMMENT 'aka MEK_A in EUR',
|
||||
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
||||
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
||||
hs_code CHAR(8) DEFAULT NULL,
|
||||
hs_code CHAR(11) DEFAULT NULL,
|
||||
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
||||
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
||||
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
|
|
@ -381,7 +381,7 @@ CREATE TABLE IF NOT EXISTS premise_destination
|
|||
destination_node_id INT NOT NULL,
|
||||
is_d2d BOOLEAN DEFAULT FALSE,
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL CHECK (rate_d2d >= 0),
|
||||
lead_time_d2d INT UNSIGNED DEFAULT NULL,
|
||||
lead_time_d2d INT UNSIGNED DEFAULT NULL CHECK (lead_time_d2d >= 0),
|
||||
repacking_cost DECIMAL(15, 2) DEFAULT NULL CHECK (repacking_cost >= 0),
|
||||
handling_cost DECIMAL(15, 2) DEFAULT NULL CHECK (handling_cost >= 0),
|
||||
disposal_cost DECIMAL(15, 2) DEFAULT NULL CHECK (disposal_cost >= 0),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -53,7 +53,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
|||
'G',
|
||||
2,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
|
||||
SET @premise_id_1 = LAST_INSERT_ID();
|
||||
|
|
@ -103,7 +103,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
|||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
DATE_SUB(NOW(), INTERVAL 3 MONTH));
|
||||
SET @premiseId2 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
|||
'G',
|
||||
3,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId3 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
|||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId4 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
|||
'KG',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId5 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -301,7 +301,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '3064540201'),
|
|||
'KG',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId6 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -351,7 +351,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '3064540201'),
|
|||
'KG',
|
||||
4,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId7 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -401,7 +401,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
|||
'G',
|
||||
8,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId8 = LAST_INSERT_ID();
|
||||
|
||||
|
|
@ -451,7 +451,7 @@ VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
|||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
NOW());
|
||||
SET @premiseId9 = LAST_INSERT_ID();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue