Refactor model structure and optimize repository queries

Removed redundant calculation job-related models, simplifying the codebase. Replaced usage of aggregate references with primitive IDs in CalculationJob for better performance. Improved repository methods for efficient node retrieval and updated schema.sql to restructure node references.
This commit is contained in:
Jan 2025-05-04 22:44:25 +02:00
parent fd6e4ea435
commit ced60b74d2
27 changed files with 1370 additions and 1023 deletions

View file

@ -117,7 +117,7 @@ public class CalculationController {
}
@PutMapping("/material")
public ResponseEntity<List<PremiseDetailDTO>> setMaterial(SetDataDTO setMaterialDTO) {
public ResponseEntity<List<PremiseDetailDTO>> setMaterial(@RequestBody SetDataDTO setMaterialDTO) {
return ResponseEntity.ok(changeMaterialService.setMaterial(setMaterialDTO));
}

View file

@ -3,7 +3,6 @@ package de.avatic.lcc.controller.report;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.service.report.ExcelReportingService;
import de.avatic.lcc.service.report.ReportFinderService;
import de.avatic.lcc.service.report.ReportingService;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
@ -23,19 +22,16 @@ public class ReportingController {
private final ReportingService reportingService;
private final ExcelReportingService excelReportingService;
private final ReportFinderService reportFinderService;
/**
* Constructor for ReportingController.
*
* @param reportingService Service used for generating reports.
* @param excelReportingService Service used for generating Excel files for reports.
* @param reportFinderService Service used for finding suppliers for reporting.
*/
public ReportingController(ReportingService reportingService, ExcelReportingService excelReportingService, ReportFinderService reportFinderService) {
public ReportingController(ReportingService reportingService, ExcelReportingService excelReportingService) {
this.reportingService = reportingService;
this.excelReportingService = excelReportingService;
this.reportFinderService = reportFinderService;
}
/**
@ -46,7 +42,7 @@ public class ReportingController {
*/
@GetMapping("/search")
public ResponseEntity<List<List<NodeDTO>>> findSupplierForReporting(@RequestParam(value = "material") Integer materialId) {
return ResponseEntity.ok(reportFinderService.findSupplierForReporting(materialId));
return ResponseEntity.ok(reportingService.findSupplierForReporting(materialId));
}
/**
@ -57,7 +53,7 @@ public class ReportingController {
* @return The generated report details.
*/
@GetMapping("/view")
public ResponseEntity<ReportDTO> getReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources") List<Integer> nodeIds) {
public ResponseEntity<List<ReportDTO>> getReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources") List<Integer> nodeIds) {
return ResponseEntity.ok(reportingService.getReport(materialId, nodeIds));
}

View file

@ -2,33 +2,33 @@ package de.avatic.lcc.dto.report;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
public class ReportDTO {
@JsonProperty("costs")
public HashMap<String, ReportEntryDTO> cost;
public Map<String, ReportEntryDTO> cost;
@JsonProperty("risk")
public HashMap<String, ReportEntryDTO> risk;
public Map<String, ReportEntryDTO> risk;
@JsonProperty("premises")
public ReportPremisesDTO premises;
public HashMap<String, ReportEntryDTO> getCost() {
public Map<String, ReportEntryDTO> getCost() {
return cost;
}
public void setCost(HashMap<String, ReportEntryDTO> cost) {
public void setCost(Map<String, ReportEntryDTO> cost) {
this.cost = cost;
}
public HashMap<String, ReportEntryDTO> getRisk() {
public Map<String, ReportEntryDTO> getRisk() {
return risk;
}
public void setRisk(HashMap<String, ReportEntryDTO> risk) {
public void setRisk(Map<String, ReportEntryDTO> risk) {
this.risk = risk;
}

View file

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

View file

@ -1,44 +1,28 @@
package de.avatic.lcc.model.calculations;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.properties.PropertySet;
import de.avatic.lcc.model.user.SysUser;
import de.avatic.lcc.model.rates.ValidityPeriod;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.Set;
@Table(name = "calculation_job")
public class CalculationJob {
@Id
private Integer id;
@NotNull
private OffsetDateTime calculationDate;
private LocalDateTime calculationDate;
private CalculationJobState jobState;
@NotNull
private AggregateReference<Premise,Integer> premiss;
private Integer premiseId;
@NotNull
private AggregateReference<ValidityPeriod, Integer> validityPeriod;
private Integer validityPeriodId;
@NotNull
private AggregateReference<PropertySet,Integer> propertySet;
private Integer propertySetId;
@NotNull
private AggregateReference<SysUser, Integer> user;
private Integer userId;
@MappedCollection(idColumn = "calculation_job_id")
private Set<CalculationJobSink> sinks;
private BigDecimal weightedTotalCosts;
public Integer getId() {
return id;
@ -48,11 +32,11 @@ public class CalculationJob {
this.id = id;
}
public OffsetDateTime getCalculationDate() {
public LocalDateTime getCalculationDate() {
return calculationDate;
}
public void setCalculationDate(OffsetDateTime calculationDate) {
public void setCalculationDate(LocalDateTime calculationDate) {
this.calculationDate = calculationDate;
}
@ -64,43 +48,43 @@ public class CalculationJob {
this.jobState = jobState;
}
public AggregateReference<Premise, Integer> getPremiss() {
return premiss;
public Integer getPremiseId() {
return premiseId;
}
public void setPremiss(AggregateReference<Premise, Integer> premiss) {
this.premiss = premiss;
public void setPremiseId(Integer premiseId) {
this.premiseId = premiseId;
}
public AggregateReference<ValidityPeriod, Integer> getValidityPeriod() {
return validityPeriod;
public Integer getValidityPeriodId() {
return validityPeriodId;
}
public void setValidityPeriod(AggregateReference<ValidityPeriod, Integer> validityPeriod) {
this.validityPeriod = validityPeriod;
public void setValidityPeriodId(Integer validityPeriodId) {
this.validityPeriodId = validityPeriodId;
}
public AggregateReference<PropertySet, Integer> getPropertySet() {
return propertySet;
public Integer getPropertySetId() {
return propertySetId;
}
public void setPropertySet(AggregateReference<PropertySet, Integer> propertySet) {
this.propertySet = propertySet;
public void setPropertySetId(Integer propertySetId) {
this.propertySetId = propertySetId;
}
public AggregateReference<SysUser, Integer> getUser() {
return user;
public Integer getUserId() {
return userId;
}
public void setUser(AggregateReference<SysUser, Integer> user) {
this.user = user;
public void setUserId(Integer userId) {
this.userId = userId;
}
public Set<CalculationJobSink> getSinks() {
return sinks;
public BigDecimal getWeightedTotalCosts() {
return weightedTotalCosts;
}
public void setSinks(Set<CalculationJobSink> sinks) {
this.sinks = sinks;
public void setWeightedTotalCosts(BigDecimal weightedTotalCosts) {
this.weightedTotalCosts = weightedTotalCosts;
}
}

View file

@ -1,85 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_airfreight")
public class CalculationJobAirfreight {
@Id
private Integer id;
@NotNull
@Digits(integer = 7, fraction = 4)
private BigDecimal airFreightShareMax;
@NotNull
@Digits(integer = 7, fraction = 4)
private BigDecimal airFreightShare;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal airFreightVolumetricWeight;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal airFreightWeight;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualCost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getAirFreightShareMax() {
return airFreightShareMax;
}
public void setAirFreightShareMax(BigDecimal airFreightShareMax) {
this.airFreightShareMax = airFreightShareMax;
}
public BigDecimal getAirFreightShare() {
return airFreightShare;
}
public void setAirFreightShare(BigDecimal airFreightShare) {
this.airFreightShare = airFreightShare;
}
public BigDecimal getAirFreightVolumetricWeight() {
return airFreightVolumetricWeight;
}
public void setAirFreightVolumetricWeight(BigDecimal airFreightVolumetricWeight) {
this.airFreightVolumetricWeight = airFreightVolumetricWeight;
}
public BigDecimal getAirFreightWeight() {
return airFreightWeight;
}
public void setAirFreightWeight(BigDecimal airFreightWeight) {
this.airFreightWeight = airFreightWeight;
}
public BigDecimal getAnnualCost() {
return annualCost;
}
public void setAnnualCost(BigDecimal annualCost) {
this.annualCost = annualCost;
}
}

View file

@ -1,72 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_custom")
public class CalculationJobCustom {
@Id
private Integer id;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal customValue;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal customDuties;
@NotNull
@Digits(integer = 7, fraction = 4)
private BigDecimal customRate;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualCost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getCustomValue() {
return customValue;
}
public void setCustomValue(BigDecimal customValue) {
this.customValue = customValue;
}
public BigDecimal getCustomDuties() {
return customDuties;
}
public void setCustomDuties(BigDecimal customDuties) {
this.customDuties = customDuties;
}
public BigDecimal getCustomRate() {
return customRate;
}
public void setCustomRate(BigDecimal customRate) {
this.customRate = customRate;
}
public BigDecimal getAnnualCost() {
return annualCost;
}
public void setAnnualCost(BigDecimal annualCost) {
this.annualCost = annualCost;
}
}

View file

@ -0,0 +1,376 @@
package de.avatic.lcc.model.calculations;
import java.math.BigDecimal;
import java.util.List;
public class CalculationJobDestination {
private Integer id;
private Integer calculationJobId;
private Integer premiseDestinationId;
private Integer shippingFrequency;
private BigDecimal totalCost;
private BigDecimal annualAmount;
// Risk
private BigDecimal annualRiskCost;
private BigDecimal annualChanceCost;
// Handling
private Boolean isSmallUnit;
private BigDecimal annualRepackingCost;
private BigDecimal annualHandlingCost;
private BigDecimal annualDisposalCost;
// Inventory
private BigDecimal operationalStock;
private BigDecimal safetyStock;
private BigDecimal stockedInventory;
private BigDecimal inTransportStock;
private BigDecimal stockBeforePayment;
private BigDecimal annualCapitalCost;
private BigDecimal annualStorageCost;
// Custom
private BigDecimal customValue;
private BigDecimal customDuties;
private BigDecimal tariffRate;
private BigDecimal annualCustomCost;
// Air Freight Risk
private BigDecimal airFreightShareMax;
private BigDecimal airFreightShare;
private BigDecimal airFreightVolumetricWeight;
private BigDecimal airFreightWeight;
private BigDecimal annualAirFreightCost;
// Transportation
private String transportationType;
private Integer huPerLayer;
private String layerStructure; // JSON as String
private Integer layerCount;
private Boolean transportWeightExceeded;
private BigDecimal transportsPerYear;
private BigDecimal annualTransportationCost;
private BigDecimal containerUtilization;
private Integer totalTransitTime;
// Material Cost
private BigDecimal materialCost;
private BigDecimal fcaCost;
public BigDecimal getContainerUtilization() {
return containerUtilization;
}
public void setContainerUtilization(BigDecimal containerUtilization) {
this.containerUtilization = containerUtilization;
}
public Integer getTotalTransitTime() {
return totalTransitTime;
}
public void setTotalTransitTime(Integer totalTransitTime) {
this.totalTransitTime = totalTransitTime;
}
public BigDecimal getAnnualAmount() {
return annualAmount;
}
public void setAnnualAmount(BigDecimal annualAmount) {
this.annualAmount = annualAmount;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getCalculationJobId() {
return calculationJobId;
}
public void setCalculationJobId(Integer calculationJobId) {
this.calculationJobId = calculationJobId;
}
public Integer getPremiseDestinationId() {
return premiseDestinationId;
}
public void setPremiseDestinationId(Integer premiseDestinationId) {
this.premiseDestinationId = premiseDestinationId;
}
public Integer getShippingFrequency() {
return shippingFrequency;
}
public void setShippingFrequency(Integer shippingFrequency) {
this.shippingFrequency = shippingFrequency;
}
public BigDecimal getTotalCost() {
return totalCost;
}
public void setTotalCost(BigDecimal totalCost) {
this.totalCost = totalCost;
}
public BigDecimal getAnnualRiskCost() {
return annualRiskCost;
}
public void setAnnualRiskCost(BigDecimal annualRiskCost) {
this.annualRiskCost = annualRiskCost;
}
public BigDecimal getAnnualChanceCost() {
return annualChanceCost;
}
public void setAnnualChanceCost(BigDecimal annualChanceCost) {
this.annualChanceCost = annualChanceCost;
}
public Boolean getSmallUnit() {
return isSmallUnit;
}
public void setSmallUnit(Boolean smallUnit) {
isSmallUnit = smallUnit;
}
public BigDecimal getAnnualRepackingCost() {
return annualRepackingCost;
}
public void setAnnualRepackingCost(BigDecimal annualRepackingCost) {
this.annualRepackingCost = annualRepackingCost;
}
public BigDecimal getAnnualHandlingCost() {
return annualHandlingCost;
}
public void setAnnualHandlingCost(BigDecimal annualHandlingCost) {
this.annualHandlingCost = annualHandlingCost;
}
public BigDecimal getAnnualDisposalCost() {
return annualDisposalCost;
}
public void setAnnualDisposalCost(BigDecimal annualDisposalCost) {
this.annualDisposalCost = annualDisposalCost;
}
public BigDecimal getOperationalStock() {
return operationalStock;
}
public void setOperationalStock(BigDecimal operationalStock) {
this.operationalStock = operationalStock;
}
public BigDecimal getSafetyStock() {
return safetyStock;
}
public void setSafetyStock(BigDecimal safetyStock) {
this.safetyStock = safetyStock;
}
public BigDecimal getStockedInventory() {
return stockedInventory;
}
public void setStockedInventory(BigDecimal stockedInventory) {
this.stockedInventory = stockedInventory;
}
public BigDecimal getInTransportStock() {
return inTransportStock;
}
public void setInTransportStock(BigDecimal inTransportStock) {
this.inTransportStock = inTransportStock;
}
public BigDecimal getStockBeforePayment() {
return stockBeforePayment;
}
public void setStockBeforePayment(BigDecimal stockBeforePayment) {
this.stockBeforePayment = stockBeforePayment;
}
public BigDecimal getAnnualCapitalCost() {
return annualCapitalCost;
}
public void setAnnualCapitalCost(BigDecimal annualCapitalCost) {
this.annualCapitalCost = annualCapitalCost;
}
public BigDecimal getAnnualStorageCost() {
return annualStorageCost;
}
public void setAnnualStorageCost(BigDecimal annualStorageCost) {
this.annualStorageCost = annualStorageCost;
}
public BigDecimal getCustomValue() {
return customValue;
}
public void setCustomValue(BigDecimal customValue) {
this.customValue = customValue;
}
public BigDecimal getCustomDuties() {
return customDuties;
}
public void setCustomDuties(BigDecimal customDuties) {
this.customDuties = customDuties;
}
public BigDecimal getTariffRate() {
return tariffRate;
}
public void setTariffRate(BigDecimal tariffRate) {
this.tariffRate = tariffRate;
}
public BigDecimal getAnnualCustomCost() {
return annualCustomCost;
}
public void setAnnualCustomCost(BigDecimal annualCustomCost) {
this.annualCustomCost = annualCustomCost;
}
public BigDecimal getAirFreightShareMax() {
return airFreightShareMax;
}
public void setAirFreightShareMax(BigDecimal airFreightShareMax) {
this.airFreightShareMax = airFreightShareMax;
}
public BigDecimal getAirFreightShare() {
return airFreightShare;
}
public void setAirFreightShare(BigDecimal airFreightShare) {
this.airFreightShare = airFreightShare;
}
public BigDecimal getAirFreightVolumetricWeight() {
return airFreightVolumetricWeight;
}
public void setAirFreightVolumetricWeight(BigDecimal airFreightVolumetricWeight) {
this.airFreightVolumetricWeight = airFreightVolumetricWeight;
}
public BigDecimal getAirFreightWeight() {
return airFreightWeight;
}
public void setAirFreightWeight(BigDecimal airFreightWeight) {
this.airFreightWeight = airFreightWeight;
}
public BigDecimal getAnnualAirFreightCost() {
return annualAirFreightCost;
}
public void setAnnualAirFreightCost(BigDecimal annualAirFreightCost) {
this.annualAirFreightCost = annualAirFreightCost;
}
public String getTransportationType() {
return transportationType;
}
public void setTransportationType(String transportationType) {
this.transportationType = transportationType;
}
public Integer getHuPerLayer() {
return huPerLayer;
}
public void setHuPerLayer(Integer huPerLayer) {
this.huPerLayer = huPerLayer;
}
public String getLayerStructure() {
return layerStructure;
}
public void setLayerStructure(String layerStructure) {
this.layerStructure = layerStructure;
}
public Integer getLayerCount() {
return layerCount;
}
public void setLayerCount(Integer layerCount) {
this.layerCount = layerCount;
}
public Boolean getTransportWeightExceeded() {
return transportWeightExceeded;
}
public void setTransportWeightExceeded(Boolean transportWeightExceeded) {
this.transportWeightExceeded = transportWeightExceeded;
}
public BigDecimal getTransportsPerYear() {
return transportsPerYear;
}
public void setTransportsPerYear(BigDecimal transportsPerYear) {
this.transportsPerYear = transportsPerYear;
}
public BigDecimal getAnnualTransportationCost() {
return annualTransportationCost;
}
public void setAnnualTransportationCost(BigDecimal annualTransportationCost) {
this.annualTransportationCost = annualTransportationCost;
}
public BigDecimal getMaterialCost() {
return materialCost;
}
public void setMaterialCost(BigDecimal materialCost) {
this.materialCost = materialCost;
}
public BigDecimal getFcaCost() {
return fcaCost;
}
public void setFcaCost(BigDecimal fcaCost) {
this.fcaCost = fcaCost;
}
}

View file

@ -1,70 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_handling")
public class CalculationJobHandling {
@Id
private Integer id;
private Boolean isSmallUnit;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualRepackingCost;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualHandlingCost;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualDisposalCost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Boolean getSmallUnit() {
return isSmallUnit;
}
public void setSmallUnit(Boolean smallUnit) {
isSmallUnit = smallUnit;
}
public BigDecimal getAnnualRepackingCost() {
return annualRepackingCost;
}
public void setAnnualRepackingCost(BigDecimal annualRepackingCost) {
this.annualRepackingCost = annualRepackingCost;
}
public BigDecimal getAnnualHandlingCost() {
return annualHandlingCost;
}
public void setAnnualHandlingCost(BigDecimal annualHandlingCost) {
this.annualHandlingCost = annualHandlingCost;
}
public BigDecimal getAnnualDisposalCost() {
return annualDisposalCost;
}
public void setAnnualDisposalCost(BigDecimal annualDisposalCost) {
this.annualDisposalCost = annualDisposalCost;
}
}

View file

@ -1,108 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_inventory")
public class CalculationJobInventory {
@Id
private Integer id;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal operationalStock;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal safetyStock;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal stockedInventory;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal inTransportStock;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal stockBeforePayment;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualCapitalCost;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualStorageCost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getOperationalStock() {
return operationalStock;
}
public void setOperationalStock(BigDecimal operationalStock) {
this.operationalStock = operationalStock;
}
public BigDecimal getSafetyStock() {
return safetyStock;
}
public void setSafetyStock(BigDecimal safetyStock) {
this.safetyStock = safetyStock;
}
public BigDecimal getStockedInventory() {
return stockedInventory;
}
public void setStockedInventory(BigDecimal stockedInventory) {
this.stockedInventory = stockedInventory;
}
public BigDecimal getInTransportStock() {
return inTransportStock;
}
public void setInTransportStock(BigDecimal inTransportStock) {
this.inTransportStock = inTransportStock;
}
public BigDecimal getStockBeforePayment() {
return stockBeforePayment;
}
public void setStockBeforePayment(BigDecimal stockBeforePayment) {
this.stockBeforePayment = stockBeforePayment;
}
public BigDecimal getAnnualCapitalCost() {
return annualCapitalCost;
}
public void setAnnualCapitalCost(BigDecimal annualCapitalCost) {
this.annualCapitalCost = annualCapitalCost;
}
public BigDecimal getAnnualStorageCost() {
return annualStorageCost;
}
public void setAnnualStorageCost(BigDecimal annualStorageCost) {
this.annualStorageCost = annualStorageCost;
}
}

View file

@ -1,48 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_risk")
public class CalculationJobRisk {
@Id
private Integer id;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualRiskCost;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualChanceCost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getAnnualRiskCost() {
return annualRiskCost;
}
public void setAnnualRiskCost(BigDecimal annualRiskCost) {
this.annualRiskCost = annualRiskCost;
}
public BigDecimal getAnnualChanceCost() {
return annualChanceCost;
}
public void setAnnualChanceCost(BigDecimal annualChanceCost) {
this.annualChanceCost = annualChanceCost;
}
}

View file

@ -1,184 +1,159 @@
package de.avatic.lcc.model.calculations;
import de.avatic.lcc.model.premises.route.RouteSection;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
import java.math.BigDecimal;
public class CalculationJobRouteSection {
private Integer id;
private Integer premiseRouteSectionId;
private Integer calculationJobDestinationId;
private CalculationJobTransportationRuleType usedRule;
private Boolean isUnmixedPrice;
private Boolean isCbmPrice;
private Boolean isWeightPrice;
private Boolean isStacked;
private Boolean isPreRun;
private Boolean isMainRun;
private Boolean isPostRun;
private BigDecimal rate;
private BigDecimal distance;
private BigDecimal cbmPrice;
private BigDecimal weightPrice;
private BigDecimal annualCost;
private Integer transitTime;
public Integer getId() {
return id;
}
@Table(name = "calculation_job_route_section")
public class CalculationJobRouteSection {
public void setId(Integer id) {
this.id = id;
}
@Id
private Integer id;
public Integer getPremiseRouteSectionId() {
return premiseRouteSectionId;
}
private CalculationJobTransportationRuleType usedRule;
public void setPremiseRouteSectionId(Integer premiseRouteSectionId) {
this.premiseRouteSectionId = premiseRouteSectionId;
}
private Boolean isUnmixedPrice;
public Integer getCalculationJobDestinationId() {
return calculationJobDestinationId;
}
private Boolean isCbmPrice;
public void setCalculationJobDestinationId(Integer calculationJobDestinationId) {
this.calculationJobDestinationId = calculationJobDestinationId;
}
private Boolean isWeightPrice;
public CalculationJobTransportationRuleType getUsedRule() {
return usedRule;
}
private Boolean isStacked;
public void setUsedRule(CalculationJobTransportationRuleType usedRule) {
this.usedRule = usedRule;
}
private Boolean isPreRun;
public Boolean getUnmixedPrice() {
return isUnmixedPrice;
}
private Boolean isMainRun;
public void setUnmixedPrice(Boolean unmixedPrice) {
isUnmixedPrice = unmixedPrice;
}
private Boolean isPostRun;
public Boolean isCbmPrice() {
return isCbmPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal rate;
public BigDecimal getCbmPrice() {
return this.cbmPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal distance;
public BigDecimal getWeightPrice() {
return this.weightPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal cbmPrice;
public void setCbmPrice(BigDecimal cbmPrice) {
this.cbmPrice = cbmPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal weightPrice;
public void setCbmPrice(Boolean cbmPrice) {
isCbmPrice = cbmPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualCost;
public Boolean isWeightPrice() {
return isWeightPrice;
}
@NotNull
@Digits(integer = 15, fraction = 2)
private Integer transitTime;
public void setWeightPrice(BigDecimal weightPrice) {
this.weightPrice = weightPrice;
}
@NotNull
private AggregateReference<RouteSection, Integer> premissRouteSection;
public BigDecimal getAnnualCost() {
return annualCost;
}
public Integer getId() {
return id;
public void setAnnualCost(BigDecimal annualCost) {
this.annualCost = annualCost;
}
public Integer getTransitTime() {
return transitTime;
}
public void setTransitTime(Integer transitTime) {
this.transitTime = transitTime;
}
public void setWeightPrice(Boolean weightPrice) {
isWeightPrice = weightPrice;
}
public Boolean getStacked() {
return isStacked;
}
public void setStacked(Boolean stacked) {
isStacked = stacked;
}
public Boolean getPreRun() {
return isPreRun;
}
public void setPreRun(Boolean preRun) {
isPreRun = preRun;
}
public Boolean getMainRun() {
return isMainRun;
}
public void setMainRun(Boolean mainRun) {
isMainRun = mainRun;
}
public Boolean getPostRun() {
return isPostRun;
}
public void setPostRun(Boolean postRun) {
isPostRun = postRun;
}
public BigDecimal getRate() {
return rate;
}
public void setRate(BigDecimal rate) {
this.rate = rate;
}
public BigDecimal getDistance() {
return distance;
}
public void setDistance(BigDecimal distance) {
this.distance = distance;
}
}
public void setId(Integer id) {
this.id = id;
}
public CalculationJobTransportationRuleType getUsedRule() {
return usedRule;
}
public void setUsedRule(CalculationJobTransportationRuleType usedRule) {
this.usedRule = usedRule;
}
public Boolean getUnmixedPrice() {
return isUnmixedPrice;
}
public void setUnmixedPrice(Boolean unmixedPrice) {
isUnmixedPrice = unmixedPrice;
}
public Boolean getCbmPrice() {
return isCbmPrice;
}
public void setCbmPrice(BigDecimal cbmPrice) {
this.cbmPrice = cbmPrice;
}
public void setCbmPrice(Boolean cbmPrice) {
isCbmPrice = cbmPrice;
}
public Boolean getWeightPrice() {
return isWeightPrice;
}
public void setWeightPrice(BigDecimal weightPrice) {
this.weightPrice = weightPrice;
}
public BigDecimal getAnnualCost() {
return annualCost;
}
public void setAnnualCost(BigDecimal annualCost) {
this.annualCost = annualCost;
}
public Integer getTransitTime() {
return transitTime;
}
public void setTransitTime(Integer transitTime) {
this.transitTime = transitTime;
}
public AggregateReference<RouteSection, Integer> getPremissRouteSection() {
return premissRouteSection;
}
public void setPremissRouteSection(AggregateReference<RouteSection, Integer> premissRouteSection) {
this.premissRouteSection = premissRouteSection;
}
public void setWeightPrice(Boolean weightPrice) {
isWeightPrice = weightPrice;
}
public Boolean getStacked() {
return isStacked;
}
public void setStacked(Boolean stacked) {
isStacked = stacked;
}
public Boolean getPreRun() {
return isPreRun;
}
public void setPreRun(Boolean preRun) {
isPreRun = preRun;
}
public Boolean getMainRun() {
return isMainRun;
}
public void setMainRun(Boolean mainRun) {
isMainRun = mainRun;
}
public Boolean getPostRun() {
return isPostRun;
}
public void setPostRun(Boolean postRun) {
isPostRun = postRun;
}
public BigDecimal getRate() {
return rate;
}
public void setRate(BigDecimal rate) {
this.rate = rate;
}
public BigDecimal getDistance() {
return distance;
}
public void setDistance(BigDecimal distance) {
this.distance = distance;
}
}

View file

@ -1,141 +0,0 @@
package de.avatic.lcc.model.calculations;
import de.avatic.lcc.model.premises.route.Destination;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jdk.jfr.Unsigned;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
@Table(name = "calculation_job_sink")
public class CalculationJobSink {
@Id
private Integer id;
@Unsigned
private Integer safetyStock;
@Unsigned
private Integer shippingFrequency;
@Digits(integer = 15, fraction = 2)
private BigDecimal totalCost;
@NotNull
private AggregateReference<Destination, Integer> premissSink;
@MappedCollection
@Column("calculation_job_transportation_id")
private CalculationJobTransportation transportation;
@Column("calculation_job_airfreight_id")
private CalculationJobAirfreight airfreight;
@Column("calculation_job_custom_id")
private CalculationJobCustom custom;
@Column("calculation_job_inventory_id")
private CalculationJobInventory inventory;
@Column("calculation_job_handling_id")
private CalculationJobHandling handling;
@Column("calculation_job_risk_id")
private CalculationJobRisk risk;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSafetyStock() {
return safetyStock;
}
public void setSafetyStock(Integer safetyStock) {
this.safetyStock = safetyStock;
}
public Integer getShippingFrequency() {
return shippingFrequency;
}
public void setShippingFrequency(Integer shippingFrequency) {
this.shippingFrequency = shippingFrequency;
}
public BigDecimal getTotalCost() {
return totalCost;
}
public void setTotalCost(BigDecimal totalCost) {
this.totalCost = totalCost;
}
public AggregateReference<Destination, Integer> getPremissSink() {
return premissSink;
}
public void setPremissSink(AggregateReference<Destination, Integer> premissSink) {
this.premissSink = premissSink;
}
public CalculationJobTransportation getTransportation() {
return transportation;
}
public void setTransportation(CalculationJobTransportation transportation) {
this.transportation = transportation;
}
public CalculationJobAirfreight getAirfreight() {
return airfreight;
}
public void setAirfreight(CalculationJobAirfreight airfreight) {
this.airfreight = airfreight;
}
public CalculationJobCustom getCustom() {
return custom;
}
public void setCustom(CalculationJobCustom custom) {
this.custom = custom;
}
public CalculationJobInventory getInventory() {
return inventory;
}
public void setInventory(CalculationJobInventory inventory) {
this.inventory = inventory;
}
public CalculationJobHandling getHandling() {
return handling;
}
public void setHandling(CalculationJobHandling handling) {
this.handling = handling;
}
public CalculationJobRisk getRisk() {
return risk;
}
public void setRisk(CalculationJobRisk risk) {
this.risk = risk;
}
}

View file

@ -1,117 +0,0 @@
package de.avatic.lcc.model.calculations;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jdk.jfr.Unsigned;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table;
import java.math.BigDecimal;
import java.util.Set;
@Table(name = "calculation_job_transportation")
public class CalculationJobTransportation {
@Id
private Integer id;
private CalculationJobTransportationType transportationType;
@Unsigned
@NotNull
private Integer huPerLayer;
@NotNull
private String layerStructure;
@Unsigned
@NotNull
private Integer layerCount;
private Boolean transportWeightExceeded;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal transportsPerYear;
@NotNull
@Digits(integer = 15, fraction = 2)
private BigDecimal annualCost;
@MappedCollection(idColumn = "calculation_job_transportation_id")
private Set<CalculationJobRouteSection> sections;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public CalculationJobTransportationType getTransportationType() {
return transportationType;
}
public void setTransportationType(CalculationJobTransportationType transportationType) {
this.transportationType = transportationType;
}
public Integer getHuPerLayer() {
return huPerLayer;
}
public void setHuPerLayer(Integer huPerLayer) {
this.huPerLayer = huPerLayer;
}
public String getLayerStructure() {
return layerStructure;
}
public void setLayerStructure(String layerStructure) {
this.layerStructure = layerStructure;
}
public Integer getLayerCount() {
return layerCount;
}
public void setLayerCount(Integer layerCount) {
this.layerCount = layerCount;
}
public Boolean getTransportWeightExceeded() {
return transportWeightExceeded;
}
public void setTransportWeightExceeded(Boolean transportWeightExceeded) {
this.transportWeightExceeded = transportWeightExceeded;
}
public BigDecimal getTransportsPerYear() {
return transportsPerYear;
}
public void setTransportsPerYear(BigDecimal transportsPerYear) {
this.transportsPerYear = transportsPerYear;
}
public BigDecimal getAnnualCost() {
return annualCost;
}
public void setAnnualCost(BigDecimal annualCost) {
this.annualCost = annualCost;
}
public Set<CalculationJobRouteSection> getSections() {
return sections;
}
public void setSections(Set<CalculationJobRouteSection> sections) {
this.sections = sections;
}
}

View file

@ -1,6 +1,5 @@
package de.avatic.lcc.repositories;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.generic.NodeType;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
@ -188,7 +187,7 @@ public class NodeRepository {
public Optional<Node> getByExternalMappingId(String mappingId) {
String query = """
SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source,
v.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required
node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required
FROM node
WHERE node.external_mapping_id = ?""";
@ -197,6 +196,51 @@ public class NodeRepository {
return Optional.ofNullable(chain);
}
@Transactional
public List<List<Node>> findNodeListsForReportingByMaterialId(Integer materialId) {
String validityPeriodSql = """
SELECT DISTINCT cj.validity_period_id
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
WHERE p.material_id = ?
""";
List<Integer> validityPeriodIds = jdbcTemplate.queryForList(validityPeriodSql, Integer.class, materialId);
// For each validity period, get the set of supplier_node_ids
List<List<Node>> nodes = new ArrayList<>();
for (Integer validityPeriodId : validityPeriodIds) {
String suppliersSql = """
SELECT DISTINCT n.*
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
INNER JOIN node n ON p.supplier_node_id = n.id
WHERE p.material_id = ?
AND cj.validity_period_id = ?
AND p.supplier_node_id IS NOT NULL
""";
String userSuppliersSql = """
SELECT DISTINCT un.*
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
INNER JOIN sys_user_node un ON p.user_supplier_node_id = un.id
WHERE p.material_id = ?
AND cj.validity_period_id = ?
AND p.user_supplier_node_id IS NOT NULL
""";
var periodNodes = new ArrayList<>(jdbcTemplate.query(suppliersSql, new NodeMapper(), materialId, validityPeriodId));
periodNodes.addAll(jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, validityPeriodId));
nodes.add(periodNodes);
}
return nodes;
}
/**
* Resolves chains of predecessors for a specified destination chain by its ID.
@ -294,6 +338,37 @@ public class NodeRepository {
return jdbcTemplate.query(query, new NodeMapper(), countryId);
}
public List<Node> findNodeListsForReportingByPeriodId(Integer materialId, Integer periodId) {
String suppliersSql = """
SELECT DISTINCT n.*
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
INNER JOIN node n ON p.supplier_node_id = n.id
WHERE p.material_id = ?
AND cj.validity_period_id = ?
AND p.supplier_node_id IS NOT NULL
""";
return jdbcTemplate.query(suppliersSql, new NodeMapper(), materialId, periodId);
}
public Optional<Node> getByDestinationId(Integer id) {
String query = "SELECT node.* FROM node INNER JOIN premise_destination WHERE node.id = premise_destination.destination_node_id AND premise_destination.id = ?";
var node = jdbcTemplate.query(query, new NodeMapper(), id);
if(node.isEmpty()) {
return Optional.empty();
}
return Optional.ofNullable(node.getFirst());
}
private class NodeMapper implements RowMapper<Node> {

View file

@ -0,0 +1,96 @@
package de.avatic.lcc.repositories.calculation;
import de.avatic.lcc.model.calculations.CalculationJobDestination;
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.List;
@Repository
public class CalculationJobDestinationRepository {
private final JdbcTemplate jdbcTemplate;
public CalculationJobDestinationRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public List<CalculationJobDestination> getDestinationsByJobId(Integer jobId) {
String query = "SELECT * FROM calculation_job_destination WHERE calculation_job_id = ?";
return jdbcTemplate.query(query, new CalculationJobDestinationMapper(), jobId);
}
private static class CalculationJobDestinationMapper implements RowMapper<CalculationJobDestination> {
@Override
public CalculationJobDestination mapRow(ResultSet rs, int rowNum) throws SQLException {
CalculationJobDestination entity = new CalculationJobDestination();
// Basic fields already mapped
entity.setId(rs.getInt("id"));
entity.setPremiseDestinationId(rs.getInt("premise_destination_id"));
entity.setCalculationJobId(rs.getInt("calculation_job_id"));
entity.setSafetyStock(rs.getBigDecimal("safety_stock"));
entity.setShippingFrequency(rs.getInt("shipping_frequency"));
entity.setTotalCost(rs.getBigDecimal("total_cost"));
entity.setTotalCost(rs.getBigDecimal("annual_amount"));
// Risk fields
entity.setAnnualRiskCost(rs.getBigDecimal("annual_risk_cost"));
entity.setAnnualChanceCost(rs.getBigDecimal("annual_chance_cost"));
// Handling fields
entity.setSmallUnit(rs.getBoolean("is_small_unit"));
entity.setAnnualRepackingCost(rs.getBigDecimal("annual_repacking_cost"));
entity.setAnnualHandlingCost(rs.getBigDecimal("annual_handling_cost"));
entity.setAnnualDisposalCost(rs.getBigDecimal("annual_disposal_cost"));
// Inventory fields
entity.setOperationalStock(rs.getBigDecimal("operational_stock"));
// Safety stock already set above
entity.setStockedInventory(rs.getBigDecimal("stocked_inventory"));
entity.setInTransportStock(rs.getBigDecimal("in_transport_stock"));
entity.setStockBeforePayment(rs.getBigDecimal("stock_before_payment"));
entity.setAnnualCapitalCost(rs.getBigDecimal("annual_capital_cost"));
entity.setAnnualStorageCost(rs.getBigDecimal("annual_storage_cost"));
// Custom fields
entity.setCustomValue(rs.getBigDecimal("custom_value"));
entity.setCustomDuties(rs.getBigDecimal("custom_duties"));
entity.setTariffRate(rs.getBigDecimal("tariff_rate"));
entity.setAnnualCustomCost(rs.getBigDecimal("annual_custom_cost"));
// Air Freight Risk fields
entity.setAirFreightShareMax(rs.getBigDecimal("air_freight_share_max"));
entity.setAirFreightShare(rs.getBigDecimal("air_freight_share"));
entity.setAirFreightVolumetricWeight(rs.getBigDecimal("air_freight_volumetric_weight"));
entity.setAirFreightWeight(rs.getBigDecimal("air_freight_weight"));
entity.setAnnualAirFreightCost(rs.getBigDecimal("annual_air_freight_cost"));
// Transportation fields
entity.setTransportationType(rs.getString("transportation_type"));
entity.setHuPerLayer(rs.getInt("hu_per_layer"));
entity.setLayerStructure(rs.getString("layer_structure"));
entity.setLayerCount(rs.getInt("layer_count"));
entity.setTransportWeightExceeded(rs.getBoolean("transport_weight_exceeded"));
entity.setTransportsPerYear(rs.getBigDecimal("transports_per_year"));
entity.setAnnualTransportationCost(rs.getBigDecimal("annual_transportation_cost"));
// Material Cost fields
entity.setMaterialCost(rs.getBigDecimal("material_cost"));
entity.setFcaCost(rs.getBigDecimal("fca_cost"));
return entity;
}
}
}

View file

@ -0,0 +1,58 @@
package de.avatic.lcc.repositories.calculation;
import de.avatic.lcc.model.calculations.CalculationJob;
import de.avatic.lcc.model.calculations.CalculationJobDestination;
import de.avatic.lcc.model.calculations.CalculationJobState;
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.List;
import java.util.Optional;
@Repository
public class CalculationJobRepository {
private final JdbcTemplate jdbcTemplate;
public CalculationJobRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public Optional<CalculationJob> getCalculationJob(Integer periodId, Integer nodeId, Integer materialId) {
/* there should only be one job per period id, node id and material id combination */
String query = "SELECT * FROM calculation_job AS cj INNER JOIN premise AS p ON cj.premise_id = p.id WHERE validity_period_id = ? AND p.supplier_node_id = ? AND material_id = ? LIMIT 1";
var job = jdbcTemplate.query(query, new CalculationJobMapper(), periodId, nodeId, materialId);
if(job.isEmpty())
return Optional.empty();
return Optional.of(job.getFirst());
}
private static class CalculationJobMapper implements RowMapper<CalculationJob> {
@Override
public CalculationJob mapRow(ResultSet rs, int rowNum) throws SQLException {
CalculationJob entity = new CalculationJob();
entity.setId(rs.getInt("id"));
entity.setPremiseId(rs.getInt("premise_id"));
entity.setValidityPeriodId(rs.getInt("validity_period_id"));
entity.setJobState(CalculationJobState.valueOf(rs.getString("job_state")));
entity.setPropertySetId(rs.getInt("property_set_id"));
entity.setUserId(rs.getInt("user_id"));
entity.setCalculationDate(rs.getTimestamp("calculation_date").toLocalDateTime());
entity.setWeightedTotalCosts(rs.getBigDecimal("weighted_total_costs"));
return entity;
}
}
}

View file

@ -0,0 +1,92 @@
package de.avatic.lcc.repositories.calculation;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.model.calculations.CalculationJobTransportationRuleType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
@Repository
public class CalculationJobRouteSectionRepository {
private final JdbcTemplate jdbcTemplate;
public CalculationJobRouteSectionRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public List<CalculationJobRouteSection> getRouteSectionsByDestinationId(Integer destinationId) {
String query = "SELECT * FROM calculation_job_route_section WHERE calculation_job_destination_id = ?";
return jdbcTemplate.query(query, new CalculationJobRouteSectionMapper(), destinationId);
}
@Transactional
public Map<Integer, List<CalculationJobRouteSection>> getRouteSectionsByDestinationIds(List<Integer> destinationIds) {
if (destinationIds == null || destinationIds.isEmpty()) {
return Collections.emptyMap();
}
String placeholders = String.join(",", Collections.nCopies(destinationIds.size(), "?"));
String query = "SELECT * FROM calculation_job_route_section WHERE calculation_job_destination_id IN (" + placeholders + ")";
List<CalculationJobRouteSection> allSections = jdbcTemplate.query(
query,
new CalculationJobRouteSectionMapper(),
destinationIds.toArray()
);
// Group by destination ID
Map<Integer, List<CalculationJobRouteSection>> groupedSections = new HashMap<>();
for (CalculationJobRouteSection section : allSections) {
groupedSections
.computeIfAbsent(section.getCalculationJobDestinationId(), k -> new ArrayList<>())
.add(section);
}
return groupedSections;
}
private static class CalculationJobRouteSectionMapper implements RowMapper<CalculationJobRouteSection> {
@Override
public CalculationJobRouteSection mapRow(ResultSet rs, int rowNum) throws SQLException {
CalculationJobRouteSection entity = new CalculationJobRouteSection();
// Basic fields
entity.setId(rs.getInt("id"));
entity.setPremiseRouteSectionId(rs.getInt("premise_route_section_id"));
entity.setCalculationJobDestinationId(rs.getInt("calculation_job_destination_id"));
// Rule and price type flags
entity.setUsedRule(CalculationJobTransportationRuleType.valueOf(rs.getString("used_rule")));
entity.setUnmixedPrice(rs.getBoolean("is_unmixed_price"));
entity.setCbmPrice(rs.getBoolean("is_cbm_price"));
entity.setWeightPrice(rs.getBoolean("is_weight_price"));
// Route section properties
entity.setStacked(rs.getBoolean("is_stacked"));
entity.setPreRun(rs.getBoolean("is_pre_run"));
entity.setMainRun(rs.getBoolean("is_main_run"));
entity.setPostRun(rs.getBoolean("is_post_run"));
// Pricing information
entity.setRate(rs.getBigDecimal("rate"));
entity.setDistance(rs.getBigDecimal("distance"));
entity.setCbmPrice(rs.getBigDecimal("cbm_price"));
entity.setWeightPrice(rs.getBigDecimal("weight_price"));
entity.setAnnualCost(rs.getBigDecimal("annual_cost"));
// Transit time
entity.setTransitTime(rs.getInt("transit_time"));
return entity;
}
}
}

View file

@ -0,0 +1,21 @@
package de.avatic.lcc.repositories.calculation;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class ReportingRepository {
private final JdbcTemplate jdbcTemplate;
public ReportingRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}

View file

@ -14,6 +14,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Repository
public class RouteSectionRepository {
@ -64,6 +65,12 @@ public class RouteSectionRepository {
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
}
public Optional<RouteSection> getById(Integer id) {
String query = "SELECT * FROM premise_route_section WHERE id = ?";
var section = jdbcTemplate.query(query, new RouteSectionMapper(), id);
return section.isEmpty() ? Optional.empty() : Optional.of(section.getFirst());
}
private static class RouteSectionMapper implements RowMapper<RouteSection> {
@Override
public RouteSection mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -11,6 +11,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -159,6 +160,63 @@ public class ValidityPeriodRepository {
jdbcTemplate.update("UPDATE validity_period SET state = ?, start_date = ? WHERE id = ? AND state = ? ", ValidityPeriodState.VALID.name(), currentTimestamp, ValidityPeriodState.DRAFT.name());
}
@Transactional
public Optional<ValidityPeriod> getValidPeriodForReportingByMaterialId(Integer materialId, List<Integer> nodeIds) {
if (nodeIds == null || nodeIds.isEmpty()) {
return Optional.empty();
}
String placeholders = String.join(",", Collections.nCopies(nodeIds.size(), "?"));
String validityPeriodSql = """
SELECT vp.*
FROM validity_period vp
INNER JOIN (
SELECT
cj.validity_period_id,
COUNT(DISTINCT p.supplier_node_id) as node_count
FROM
premise p
INNER JOIN
calculation_job cj ON p.id = cj.premise_id
WHERE
p.material_id = ?
AND p.supplier_node_id IN ("""
+ placeholders + """
)
GROUP BY
cj.validity_period_id
HAVING
COUNT(DISTINCT p.supplier_node_id) = ?
) matching_periods ON vp.id = matching_periods.validity_period_id
ORDER BY
vp.start_date DESC
LIMIT 1
""";
var periods = jdbcTemplate.query(validityPeriodSql, new ValidityPeriodMapper(), materialId, nodeIds.toArray(), nodeIds.size());
if (periods.isEmpty()) {
return Optional.empty();
}
return Optional.of(periods.getFirst());
}
public List<Integer> findValidityPeriodsWithReportByMaterialId(Integer materialId) {
String validityPeriodSql = """
SELECT DISTINCT cj.validity_period_id
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
WHERE p.material_id = ?
""";
return jdbcTemplate.queryForList(validityPeriodSql, Integer.class, materialId);
}
/**
* Maps rows of a {@link ResultSet} to {@link ValidityPeriod} objects.
*/
@ -170,9 +228,16 @@ public class ValidityPeriodRepository {
var period = new ValidityPeriod();
period.setId(rs.getInt("id"));
period.setStartDate(rs.getTimestamp("start_date").toLocalDateTime());
period.setEndDate(rs.getTimestamp("end_date").toLocalDateTime());
period.setState(ValidityPeriodState.valueOf(rs.getString("state")));
Timestamp startTs = rs.getTimestamp("start_date");
period.setStartDate(startTs != null ? startTs.toLocalDateTime() : null);
Timestamp endTs = rs.getTimestamp("end_date");
period.setEndDate(endTs != null ? endTs.toLocalDateTime() : null);
String stateStr = rs.getString("state");
period.setState(stateStr != null ? ValidityPeriodState.valueOf(stateStr) : null);
return period;

View file

@ -8,6 +8,7 @@ import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@Repository
@ -49,6 +50,21 @@ public class UserNodeRepository {
return Optional.of(nodes.getFirst());
}
public List<Node> findNodeListsForReportingByPeriodId(Integer materialId, Integer periodId) {
String userSuppliersSql = """
SELECT DISTINCT un.*
FROM premise p
INNER JOIN calculation_job cj ON p.id = cj.premise_id
INNER JOIN sys_user_node un ON p.user_supplier_node_id = un.id
WHERE p.material_id = ?
AND cj.validity_period_id = ?
AND p.user_supplier_node_id IS NOT NULL
""";
return jdbcTemplate.query(userSuppliersSql, new NodeMapper(), materialId, periodId));
}
private static class NodeMapper implements RowMapper<Node> {
@Override

View file

@ -92,8 +92,4 @@ public class PremisesService {
destinationService.deleteAllDestinationsByPremiseId(premiseIds, false);
premiseRepository.deletePremisesById(premiseIds);
}
public void fillPackaging(List<Integer> premiseIds) {
}
}

View file

@ -1,16 +0,0 @@
package de.avatic.lcc.service.report;
import de.avatic.lcc.dto.generic.NodeDTO;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ReportFinderService {
public List<List<NodeDTO>> findSupplierForReporting(Integer materialId) {
return null;
}
}

View file

@ -1,13 +1,70 @@
package de.avatic.lcc.service.report;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.report.ReportDTO;
import de.avatic.lcc.model.calculations.CalculationJobDestination;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.service.transformer.report.ReportTransformer;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class ReportingService {
public ReportDTO getReport(Integer materialId, List<Integer> nodeIds) {
return null;
private final NodeRepository nodeRepository;
private final NodeTransformer nodeTransformer;
private final ValidityPeriodRepository validityPeriodRepository;
private final CalculationJobRepository calculationJobRepository;
private final UserNodeRepository userNodeRepository;
private final ReportTransformer reportTransformer;
public ReportingService(NodeRepository nodeRepository, NodeTransformer nodeTransformer, ValidityPeriodRepository validityPeriodRepository, CalculationJobRepository calculationJobRepository, UserNodeRepository userNodeRepository, ReportTransformer reportTransformer) {
this.nodeRepository = nodeRepository;
this.nodeTransformer = nodeTransformer;
this.validityPeriodRepository = validityPeriodRepository;
this.calculationJobRepository = calculationJobRepository;
this.userNodeRepository = userNodeRepository;
this.reportTransformer = reportTransformer;
}
public List<List<NodeDTO>> findSupplierForReporting(Integer materialId) {
var periodIds = validityPeriodRepository.findValidityPeriodsWithReportByMaterialId(materialId);
var nodes = new ArrayList<List<NodeDTO>>();
for (var periodId : periodIds) {
var periodNodes = new ArrayList<>(nodeRepository.findNodeListsForReportingByPeriodId(materialId, periodId).stream().map(nodeTransformer::toNodeDTO).toList());
periodNodes.addAll(userNodeRepository.findNodeListsForReportingByPeriodId(materialId, periodId).stream().map(nodeTransformer::toNodeDTO).peek(n -> n.setUserNode(true)).toList());
//TODO destinations have to match.
nodes.add(periodNodes);
}
return nodes;
}
public List<ReportDTO> getReport(Integer materialId, List<Integer> nodeIds) {
var period = validityPeriodRepository.getValidPeriodForReportingByMaterialId(materialId, nodeIds);
if (period.isEmpty())
throw new IllegalArgumentException("No valid period found");
var periodId = period.get().getId();
var jobs = nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJob(periodId, nodeId,materialId)).filter(Optional::isPresent).map(Optional::get).toList();
return jobs.stream().map(reportTransformer::toReportDTO).toList();
}
}

View file

@ -0,0 +1,230 @@
package de.avatic.lcc.service.transformer.report;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.dto.report.*;
import de.avatic.lcc.model.calculations.CalculationJob;
import de.avatic.lcc.model.calculations.CalculationJobDestination;
import de.avatic.lcc.model.calculations.CalculationJobRouteSection;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.premise.RouteSectionRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ReportTransformer {
private final CalculationJobDestinationRepository calculationJobDestinationRepository;
private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository;
private final PremiseRepository premiseRepository;
private final DestinationRepository destinationRepository;
private final NodeRepository nodeRepository;
private final RouteSectionRepository routeSectionRepository;
private final NodeTransformer nodeTransformer;
public ReportTransformer(CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository, PremiseRepository premiseRepository, DestinationRepository destinationRepository, NodeRepository nodeRepository, RouteSectionRepository routeSectionRepository, NodeTransformer nodeTransformer) {
this.calculationJobDestinationRepository = calculationJobDestinationRepository;
this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository;
this.premiseRepository = premiseRepository;
this.destinationRepository = destinationRepository;
this.nodeRepository = nodeRepository;
this.routeSectionRepository = routeSectionRepository;
this.nodeTransformer = nodeTransformer;
}
public ReportDTO toReportDTO(CalculationJob job) {
ReportDTO reportDTO = new ReportDTO();
List<CalculationJobDestination> destinations = calculationJobDestinationRepository.getDestinationsByJobId(job.getId());
Map<Integer, List<CalculationJobRouteSection>> sections = calculationJobRouteSectionRepository.getRouteSectionsByDestinationIds(destinations.stream().map(CalculationJobDestination::getId).toList());
Premise premise = premiseRepository.getPremiseById(job.getPremiseId()).orElseThrow();
reportDTO.setCost(getCostMap(job, destinations));
reportDTO.setRisk(getRisk(job, destinations));
ReportPremisesDTO premisesDTO = new ReportPremisesDTO();
premisesDTO.setQuantities(destinations.stream().map(q -> getQuantitiesDTO(q, sections.get(q.getId()))).toList());
premisesDTO.setContainer(getContainerDTO(destinations, sections, premise));
premisesDTO.setPackaging(getPackagingDTO(destinations, premise));
premisesDTO.setQuotaShare(getQuotaShare(destinations, sections, premise));
premisesDTO.setHsCode(premise.getHsCode());
reportDTO.setPremises(premisesDTO);
return reportDTO;
}
private ReportQuotaShareDTO getQuotaShare(List<CalculationJobDestination> destinations, Map<Integer, List<CalculationJobRouteSection>> sections, Premise premise) {
ReportQuotaShareDTO quotaShare = new ReportQuotaShareDTO();
var safetyStock = destinations.stream().map(CalculationJobDestination::getSafetyStock).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destinations.size()), 2, RoundingMode.HALF_UP);
double transportTime = (double) (destinations.stream().map(CalculationJobDestination::getId).map(sections::get).mapToInt(l -> l.stream().mapToInt(CalculationJobRouteSection::getTransitTime).sum()).sum()) / sections.size();
var airfreight = destinations.stream().map(CalculationJobDestination::getAirFreightShare).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(destinations.size()), 2, RoundingMode.HALF_UP);
quotaShare.setOverseaShare(premise.getOverseaShare().doubleValue());
quotaShare.setSafetyStock(safetyStock.doubleValue());
quotaShare.setTransportTime(transportTime);
quotaShare.setAirFreightShare(airfreight.doubleValue());
return quotaShare;
}
private ReportPackagingDTO getPackagingDTO(List<CalculationJobDestination> destination, Premise premise) {
ReportPackagingDTO packaging = new ReportPackagingDTO();
var dimensionUnit = premise.getDimensionUnit();
var weightUnit = premise.getWeightUnit();
packaging.setDimensionUnit(dimensionUnit);
packaging.setWeightUnit(weightUnit);
packaging.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()).doubleValue());
packaging.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()).doubleValue());
packaging.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()).doubleValue());
packaging.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()).doubleValue());
packaging.setLayer(destination.stream().mapToInt(CalculationJobDestination::getLayerCount).sum()/destination.size());
packaging.setUnitCount(premise.getHuUnitCount());
return packaging;
}
private ReportContainerDTO getContainerDTO(List<CalculationJobDestination> destination, List<CalculationJobRouteSection> sections, Premise premise) {
ReportContainerDTO container = new ReportContainerDTO();
CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null);
container.setMixed(premise.getHuMixable());
container.setRate(mainRun == null ? 0 : mainRun.getRate());
container.setType(ContainerType.valueOf(destination.getFirst().getTransportationType()));
container.setUtilization(destination.getFirst().getContainerUtilization());
container.setUnitCount(premise.getHuUnitCount());
container.setWeightExceeded(destination.getFirst().getTransportWeightExceeded());
return container;
}
private ReportQuantityDTO getQuantitiesDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections) {
ReportQuantityDTO quantity = new ReportQuantityDTO();
var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow();
quantity.setQuantity(destination.getAnnualAmount());
quantity.setDestination(nodeTransformer.toNodeDTO(destinationNode));
quantity.setRoute(sections.stream().map(this::getRouteEntryDTO).toList());
var total = quantity.getRoute().stream().map(ReportRouteEntryDTO::getCost).map(ReportEntryDTO::getTotal).mapToDouble(Number::doubleValue).sum();
quantity.getRoute().forEach(r -> r.getCost().setPercentage(BigDecimal.valueOf(r.getCost().getTotal().doubleValue() / total).setScale(2, RoundingMode.HALF_UP)));
return quantity;
}
private ReportRouteEntryDTO getRouteEntryDTO(CalculationJobRouteSection routeSection) {
ReportRouteEntryDTO routeEntry = new ReportRouteEntryDTO();
var premiseSection = routeSectionRepository.getById(routeSection.getPremiseRouteSectionId()).orElseThrow();
var sectionNode = nodeRepository.getById(premiseSection.getFromRouteNodeId()).orElseThrow();
routeEntry.setName(sectionNode.getExternalMappingId());
routeEntry.setType(premiseSection.getTransportType());
routeEntry.setCost(new ReportEntryDTO());
routeEntry.getCost().setTotal(routeSection.getAnnualCost());
return routeEntry;
}
private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination) {
Map<String, ReportEntryDTO> risk = new HashMap<>();
var totalValue = job.getWeightedTotalCosts();
ReportEntryDTO airfreight = new ReportEntryDTO();
var airfreightValue = destination.stream().map(CalculationJobDestination::getAnnualAirFreightCost).reduce(BigDecimal.ZERO, BigDecimal::add);
airfreight.setTotal(airfreightValue);
airfreight.setPercentage(airfreightValue.divide(totalValue, 2, RoundingMode.HALF_UP));
risk.put("air_freight_cost", airfreight);
ReportEntryDTO worst = new ReportEntryDTO();
var worstValue = destination.stream().map(CalculationJobDestination::getAnnualRiskCost).reduce(BigDecimal.ZERO, BigDecimal::add);
worst.setTotal(worstValue);
worst.setPercentage(worstValue.divide(totalValue, 2, RoundingMode.HALF_UP));
risk.put("worst_case_cost", worst);
ReportEntryDTO best = new ReportEntryDTO();
var bestValue = destination.stream().map(CalculationJobDestination::getAnnualChanceCost).reduce(BigDecimal.ZERO, BigDecimal::add);
best.setTotal(bestValue);
best.setPercentage(bestValue.divide(totalValue, 2, RoundingMode.HALF_UP));
risk.put("best_case_cost", best);
return risk;
}
private Map<String, ReportEntryDTO> getCostMap(CalculationJob job, List<CalculationJobDestination> destination) {
Map<String, ReportEntryDTO> cost = new HashMap<>();
ReportEntryDTO total = new ReportEntryDTO();
var totalValue = job.getWeightedTotalCosts();
total.setTotal(totalValue);
total.setPercentage(BigDecimal.valueOf(100));
cost.put("total", total);
ReportEntryDTO material = new ReportEntryDTO();
var materialValue = destination.stream().map(CalculationJobDestination::getMaterialCost).reduce(BigDecimal.ZERO, BigDecimal::add);
material.setTotal(materialValue);
material.setPercentage(materialValue.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("material", material);
ReportEntryDTO fcaFees = new ReportEntryDTO();
var fcaFeesValues = destination.stream().map(CalculationJobDestination::getFcaCost).reduce(BigDecimal.ZERO, BigDecimal::add);
fcaFees.setTotal(fcaFeesValues);
fcaFees.setPercentage(fcaFeesValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("fcaFees", fcaFees);
ReportEntryDTO repacking = new ReportEntryDTO();
var repackingValues = destination.stream().map(CalculationJobDestination::getAnnualRepackingCost).reduce(BigDecimal.ZERO, BigDecimal::add);
repacking.setTotal(repackingValues);
repacking.setPercentage(repackingValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("repacking", repacking);
ReportEntryDTO handling = new ReportEntryDTO();
var handlingValues = destination.stream().map(CalculationJobDestination::getAnnualHandlingCost).reduce(BigDecimal.ZERO, BigDecimal::add);
handling.setTotal(handlingValues);
handling.setPercentage(handlingValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("handling", handling);
ReportEntryDTO storage = new ReportEntryDTO();
var storageValues = destination.stream().map(CalculationJobDestination::getAnnualStorageCost).reduce(BigDecimal.ZERO, BigDecimal::add);
storage.setTotal(storageValues);
storage.setPercentage(storageValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("storage", storage);
ReportEntryDTO capital = new ReportEntryDTO();
var capitalValues = destination.stream().map(CalculationJobDestination::getAnnualCapitalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
capital.setTotal(capitalValues);
capital.setPercentage(capitalValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("capital", capital);
ReportEntryDTO disposal = new ReportEntryDTO();
var disposalValues = destination.stream().map(CalculationJobDestination::getAnnualDisposalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
disposal.setTotal(disposalValues);
disposal.setPercentage(disposalValues.divide(totalValue, 2, RoundingMode.HALF_UP));
cost.put("disposal", disposal);
return cost;
}
}

View file

@ -130,7 +130,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_node`
-- logistic nodes
CREATE TABLE IF NOT EXISTS chain
CREATE TABLE IF NOT EXISTS node
(
id INT PRIMARY KEY,
country_id INT NOT NULL,
@ -153,7 +153,7 @@ CREATE TABLE IF NOT EXISTS node_predecessor_chain
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
node_id INT NOT NULL,
FOREIGN KEY (node_id) REFERENCES chain (id)
FOREIGN KEY (node_id) REFERENCES node (id)
);
@ -163,7 +163,7 @@ CREATE TABLE IF NOT EXISTS node_predecessor_entry
node_id INT NOT NULL,
node_predecessor_chain_id INT NOT NULL,
sequence_number INT NOT NULL CHECK (sequence_number > 0),
FOREIGN KEY (node_id) REFERENCES chain (id),
FOREIGN KEY (node_id) REFERENCES node (id),
FOREIGN KEY (node_predecessor_chain_id) REFERENCES node_predecessor_chain (id),
UNIQUE KEY uk_node_predecessor (node_predecessor_chain_id, sequence_number),
INDEX idx_node_predecessor (node_predecessor_chain_id),
@ -175,7 +175,7 @@ CREATE TABLE IF NOT EXISTS outbound_country_mapping
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
node_id INT NOT NULL,
country_id INT NOT NULL,
FOREIGN KEY (node_id) REFERENCES chain (id),
FOREIGN KEY (node_id) REFERENCES node (id),
FOREIGN KEY (country_id) REFERENCES country (id),
UNIQUE KEY uk_node_id_country_id (node_id, country_id),
INDEX idx_node_id (node_id),
@ -194,8 +194,8 @@ CREATE TABLE IF NOT EXISTS distance_matrix
distance DECIMAL(15, 2) NOT NULL COMMENT 'travel distance between the two nodes in meters',
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
state CHAR(10),
FOREIGN KEY (from_node_id) REFERENCES chain (id),
FOREIGN KEY (to_node_id) REFERENCES chain (id),
FOREIGN KEY (from_node_id) REFERENCES node (id),
FOREIGN KEY (to_node_id) REFERENCES node (id),
CONSTRAINT `chk_state` CHECK (`state` IN
('VALID', 'STALE')),
INDEX idx_from_to_nodes (from_node_id, to_node_id)
@ -223,8 +223,8 @@ CREATE TABLE IF NOT EXISTS container_rate
rate_hc DECIMAL(15, 2) NOT NULL COMMENT 'rate for 40ft HQ container in EUR',
lead_time INT UNSIGNED NOT NULL COMMENT 'lead time in days',
validity_period_id INT NOT NULL,
FOREIGN KEY (from_node_id) REFERENCES chain (id),
FOREIGN KEY (to_node_id) REFERENCES chain (id),
FOREIGN KEY (from_node_id) REFERENCES node (id),
FOREIGN KEY (to_node_id) REFERENCES node (id),
FOREIGN KEY (validity_period_id) REFERENCES validity_period (id),
INDEX idx_from_to_nodes (from_node_id, to_node_id),
INDEX idx_validity_period_id (validity_period_id)
@ -285,7 +285,7 @@ CREATE TABLE IF NOT EXISTS packaging
`hu_dimension_id` INT NOT NULL,
`shu_dimension_id` INT NOT NULL,
`is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (supplier_node_id) REFERENCES chain (id),
FOREIGN KEY (supplier_node_id) REFERENCES node (id),
FOREIGN KEY (material_id) REFERENCES material (id),
FOREIGN KEY (hu_dimension_id) REFERENCES packaging_dimension (id),
FOREIGN KEY (shu_dimension_id) REFERENCES packaging_dimension (id),
@ -331,8 +331,8 @@ CREATE TABLE IF NOT EXISTS premise
user_supplier_node_id INT,
packaging_id INT DEFAULT NULL,
user_id INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
material_cost DECIMAL(15, 2) COMMENT 'aka MEK_A in EUR',
is_fca_enabled BOOLEAN DEFAULT FALSE,
oversea_share DECIMAL(7, 4),
@ -349,7 +349,7 @@ CREATE TABLE IF NOT EXISTS premise
hu_stackable BOOLEAN DEFAULT TRUE,
hu_mixable BOOLEAN DEFAULT TRUE,
FOREIGN KEY (material_id) REFERENCES material (id),
FOREIGN KEY (supplier_node_id) REFERENCES chain (id),
FOREIGN KEY (supplier_node_id) REFERENCES node (id),
FOREIGN KEY (user_supplier_node_id) REFERENCES sys_user_node (id),
FOREIGN KEY (packaging_id) REFERENCES packaging (id),
FOREIGN KEY (user_id) REFERENCES sys_user (id),
@ -373,13 +373,13 @@ CREATE TABLE IF NOT EXISTS premise_destination
premise_id INT NOT NULL,
annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces',
destination_node_id INT NOT NULL,
is_d2d BOOLEAN DEFAULT FALSE,
is_d2d BOOLEAN DEFAULT FALSE,
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
repacking_cost DECIMAL(15, 2) DEFAULT NULL,
handling_cost DECIMAL(15, 2) DEFAULT NULL,
disposal_cost DECIMAL(15, 2) DEFAULT NULL,
FOREIGN KEY (premise_id) REFERENCES premise (id),
FOREIGN KEY (destination_node_id) REFERENCES chain (id),
FOREIGN KEY (destination_node_id) REFERENCES node (id),
INDEX idx_destination_node_id (destination_node_id),
INDEX idx_premise_id (premise_id)
);
@ -401,14 +401,14 @@ CREATE TABLE IF NOT EXISTS premise_route_node
user_node_id INT DEFAULT NULL,
name VARCHAR(255) NOT NULL,
address VARCHAR(500),
country_id INT NOT NULL,
country_id INT NOT NULL,
is_destination BOOLEAN DEFAULT FALSE,
is_intermediate BOOLEAN DEFAULT FALSE,
is_source BOOLEAN DEFAULT FALSE,
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
is_outdated BOOLEAN DEFAULT FALSE,
FOREIGN KEY (node_id) REFERENCES chain (id),
FOREIGN KEY (node_id) REFERENCES node (id),
FOREIGN KEY (country_id) REFERENCES country (id),
FOREIGN KEY (user_node_id) REFERENCES sys_user_node (id),
INDEX idx_node_id (node_id),
@ -463,129 +463,88 @@ CREATE TABLE IF NOT EXISTS calculation_job
);
CREATE TABLE IF NOT EXISTS calculation_job_material
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
material_cost DECIMAL(15, 2) NOT NULL,
fca_cost DECIMAL(15, 2) NOT NULL
);
CREATE TABLE IF NOT EXISTS calculation_job_transportation
CREATE TABLE IF NOT EXISTS calculation_job_destination
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
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_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?!',
annual_cost DECIMAL(15, 2) NOT NULL COMMENT 'total annual transportation costs in EUR'
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
calculation_job_id INT NOT NULL,
premise_destination_id INT NOT NULL,
shipping_frequency INT UNSIGNED COMMENT 'annual shipping frequency',
total_cost DECIMAL(15, 2) COMMENT 'aka MEK_B in EUR',
annual_amount DECIMAL(15, 2) COMMENT 'annual quantity for this destinations',
-- risk
annual_risk_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored worst case container rates',
annual_chance_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored best case container rates',
-- handling
is_small_unit BOOLEAN DEFAULT FALSE COMMENT 'small unit equals KLT, volume of a handling unit is smaller than 0.08 cbm ',
annual_repacking_cost DECIMAL(15, 2) NOT NULL,
annual_handling_cost DECIMAL(15, 2) NOT NULL,
annual_disposal_cost DECIMAL(15, 2) NOT NULL,
-- inventory
operational_stock DECIMAL(15, 2) NOT NULL COMMENT 'operational stock in single pieces',
safety_stock DECIMAL(15, 2) NOT NULL COMMENT 'safety stock in single pieces',
stocked_inventory DECIMAL(15, 2) NOT NULL COMMENT 'sum of operational and safety stock ?!',
in_transport_stock DECIMAL(15, 2) NOT NULL,
stock_before_payment DECIMAL(15, 2) NOT NULL,
annual_capital_cost DECIMAL(15, 2) NOT NULL,
annual_storage_cost DECIMAL(15, 2) NOT NULL, -- Flächenkosten
-- custom
custom_value DECIMAL(15, 2) NOT NULL,-- Zollwert,
custom_duties DECIMAL(15, 2) NOT NULL,-- Zollabgaben,
tariff_rate DECIMAL(7, 4) NOT NULL,-- Zollsatz,
annual_custom_cost DECIMAL(15, 2) NOT NULL,-- Zollabgaben inkl. Einmalkosten,
-- air freight risk
air_freight_share_max DECIMAL(7, 4) NOT NULL,
air_freight_share DECIMAL(7, 4) NOT NULL,
air_freight_volumetric_weight DECIMAL(15, 2) NOT NULL,
air_freight_weight DECIMAL(15, 2) NOT NULL,
annual_air_freight_cost DECIMAL(15, 2) NOT NULL,
-- transportation
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_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?!',
annual_transportation_cost DECIMAL(15, 2) NOT NULL COMMENT 'total annual transportation costs in EUR',
container_utilization DECIMAL(7, 4) NOT NULL,
-- material cost
material_cost DECIMAL(15, 2) NOT NULL,
fca_cost DECIMAL(15, 2) NOT NULL,
FOREIGN KEY (calculation_job_id) REFERENCES calculation_job (id),
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id),
INDEX idx_calculation_job_id (calculation_job_id),
INDEX idx_premise_destination_id (premise_destination_id)
);
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_transportation_id INT NOT NULL,
used_rule CHAR(8) CHECK (used_rule IN
('CONTAINER', 'MATRIX')),
is_unmixed_price BOOLEAN DEFAULT FALSE,
is_cbm_price BOOLEAN DEFAULT FALSE,
is_weight_price BOOLEAN DEFAULT FALSE,
is_stacked BOOLEAN DEFAULT FALSE,
is_pre_run BOOLEAN DEFAULT FALSE,
is_main_run BOOLEAN DEFAULT FALSE,
is_post_run BOOLEAN DEFAULT FALSE,
rate DECIMAL(15, 2) NOT NULL COMMENT 'copy of the container rate resp. price matrix in EUR (depends on used_rule)',
distance DECIMAL(15, 2) DEFAULT NULL COMMENT 'distance of this routeInformationObject section im meters',
cbm_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per cubic meter',
weight_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per kilogram',
annual_cost DECIMAL(15, 2) NOT NULL COMMENT 'annual costs for this routeInformationObject section, result depends on calculation method (mixed or unmixed, stacked or unstacked, per volume/per weight resp. container rate/price matrix)',
transit_time INT UNSIGNED NOT NULL,
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
premise_route_section_id INT NOT NULL,
calculation_job_destination_id INT NOT NULL,
used_rule CHAR(8) CHECK (used_rule IN
('CONTAINER', 'MATRIX')),
is_unmixed_price BOOLEAN DEFAULT FALSE,
is_cbm_price BOOLEAN DEFAULT FALSE,
is_weight_price BOOLEAN DEFAULT FALSE,
is_stacked BOOLEAN DEFAULT FALSE,
is_pre_run BOOLEAN DEFAULT FALSE,
is_main_run BOOLEAN DEFAULT FALSE,
is_post_run BOOLEAN DEFAULT FALSE,
rate DECIMAL(15, 2) NOT NULL COMMENT 'copy of the container rate resp. price matrix in EUR (depends on used_rule)',
distance DECIMAL(15, 2) DEFAULT NULL COMMENT 'distance of this routeInformationObject section im meters',
cbm_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per cubic meter',
weight_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per kilogram',
annual_cost DECIMAL(15, 2) NOT NULL COMMENT 'annual costs for this routeInformationObject section, result depends on calculation method (mixed or unmixed, stacked or unstacked, per volume/per weight resp. container rate/price matrix)',
transit_time INT UNSIGNED NOT NULL,
FOREIGN KEY (premise_route_section_id) REFERENCES premise_route_section (id),
FOREIGN KEY (calculation_job_transportation_id) REFERENCES calculation_job_transportation (id),
FOREIGN KEY (calculation_job_destination_id) REFERENCES calculation_job_destination (id),
INDEX idx_premise_route_section_id (premise_route_section_id),
INDEX idx_calculation_job_transportation_id (calculation_job_transportation_id),
INDEX idx_calculation_job_destination_id (calculation_job_destination_id),
CONSTRAINT chk_stacked CHECK (is_unmixed_price IS TRUE OR is_stacked IS TRUE), -- only unmixed transports can be unstacked
CONSTRAINT chk_cbm_weight_price CHECK (is_unmixed_price IS FALSE OR
(is_cbm_price IS FALSE AND is_weight_price IS FALSE))
);
CREATE TABLE IF NOT EXISTS calculation_job_airfreight
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
air_freight_share_max DECIMAL(7, 4) NOT NULL,
air_freight_share DECIMAL(7, 4) NOT NULL,
air_freight_volumetric_weight DECIMAL(15, 2) NOT NULL,
air_freight_weight DECIMAL(15, 2) NOT NULL,
annual_cost DECIMAL(15, 2) NOT NULL
);
CREATE TABLE IF NOT EXISTS calculation_job_custom
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
custom_value DECIMAL(15, 2) NOT NULL,-- Zollwert,
custom_duties DECIMAL(15, 2) NOT NULL,-- Zollabgaben,
tariff_rate DECIMAL(7, 4) NOT NULL,-- Zollsatz,
annual_cost DECIMAL(15, 2) NOT NULL-- Zollabgaben inkl. Einmalkosten,
);
CREATE TABLE IF NOT EXISTS calculation_job_inventory
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
operational_stock DECIMAL(15, 2) NOT NULL COMMENT 'operational stock in single pieces',
safety_stock DECIMAL(15, 2) NOT NULL COMMENT 'safety stock in single pieces',
stocked_inventory DECIMAL(15, 2) NOT NULL COMMENT 'sum of operational and safety stock ?!',
in_transport_stock DECIMAL(15, 2) NOT NULL,
stock_before_payment DECIMAL(15, 2) NOT NULL,
annual_capital_cost DECIMAL(15, 2) NOT NULL,
annual_storage_cost DECIMAL(15, 2) NOT NULL -- Flächenkosten,
);
CREATE TABLE IF NOT EXISTS calculation_job_handling
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
is_small_unit BOOLEAN DEFAULT FALSE COMMENT 'small unit equals KLT, volume of a handling unit is smaller than 0.08 cbm ',
annual_repacking_cost DECIMAL(15, 2) NOT NULL,
annual_handling_cost DECIMAL(15, 2) NOT NULL,
annual_disposal_cost DECIMAL(15, 2) NOT NULL
);
CREATE TABLE IF NOT EXISTS calculation_job_risk
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
annual_risk_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored worst case container rates',
annual_chance_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored best case container rates'
);
CREATE TABLE IF NOT EXISTS calculation_job_destination
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
calculation_job_id INT NOT NULL,
premise_destination_id INT NOT NULL,
safety_stock INT UNSIGNED COMMENT 'safety stock in single pieces ?!?!',
shipping_frequency INT UNSIGNED COMMENT 'annual shipping frequency',
total_cost DECIMAL(15, 2) COMMENT 'aka MEK_B in EUR',
calculation_job_custom_id INT NOT NULL,
calculation_job_inventory_id INT NOT NULL,
calculation_job_handling_id INT NOT NULL,
calculation_job_risk_id INT NOT NULL,
calculation_job_material_id INT NOT NULL,
calculation_job_transportation_id INT NOT NULL,
calculation_job_airfreight_id INT NOT NULL,
FOREIGN KEY (calculation_job_id) REFERENCES calculation_job (id),
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id),
FOREIGN KEY (calculation_job_custom_id) REFERENCES calculation_job_custom (id),
FOREIGN KEY (calculation_job_inventory_id) REFERENCES calculation_job_inventory (id),
FOREIGN KEY (calculation_job_handling_id) REFERENCES calculation_job_handling (id),
FOREIGN KEY (calculation_job_risk_id) REFERENCES calculation_job_risk (id),
FOREIGN KEY (calculation_job_material_id) REFERENCES calculation_job_material (id),
FOREIGN KEY (calculation_job_transportation_id) REFERENCES calculation_job_transportation (id),
FOREIGN KEY (calculation_job_airfreight_id) REFERENCES calculation_job_airfreight (id),
INDEX idx_calculation_job_id (calculation_job_id),
INDEX idx_premise_destination_id (premise_destination_id)
);