- size and weight check before calculation
- part numbers search in assistant
This commit is contained in:
Jan 2025-09-22 18:56:43 +02:00
parent 9fe7da4a33
commit 64ec4f2d11
20 changed files with 524 additions and 73 deletions

View file

@ -186,7 +186,7 @@ export default {
created() {
this.bulkQuery = this.$route.params.ids;
this.ids = new UrlSafeBase64().decodeIds(this.$route.params.ids);
this.premiseEditStore.loadPremissesIfNeeded(this.ids, true);
this.premiseEditStore.loadPremissesForced(this.ids);
},
data() {
return {

View file

@ -225,6 +225,9 @@ public class ContainerCalculationResult {
* @return The volume utilization ratio of one HU to container volume.
*/
public double getHuUtilizationByVolume() {
if (containerType.getVolume() <= 0.0) {
return 0.0;
}
return hu.getVolume(DimensionUnit.M) / containerType.getVolume();
}
@ -245,7 +248,10 @@ public class ContainerCalculationResult {
* @return Weight utilization ratio of one HU to the container's maximum weight.
*/
public double getHuUtilizationByWeight() {
return WeightUnit.KG.convertFromG(hu.getWeight()).doubleValue() / maxContainerWeight;
if (maxContainerWeight == null || maxContainerWeight <= 0) {
return 0;
}
return WeightUnit.KG.convertFromG(hu.getWeight()) / maxContainerWeight;
}
}

View file

@ -61,11 +61,11 @@ public class NodeController {
return ResponseEntity.ok(nodeService.deleteNode(id));
}
// @PutMapping("/{id}")
// public ResponseEntity<Integer> updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) {
// Check.equals(id, node.getId());
// return ResponseEntity.ok(nodeService.updateNode(node));
// }
@PutMapping("/{id}")
public ResponseEntity<Integer> updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) {
Check.equals(id, node.getId());
return ResponseEntity.ok(nodeService.updateNode(node));
}
@GetMapping("/locate")
public ResponseEntity<LocateNodeDTO> locateNode(@RequestParam String address) {

View file

@ -3,6 +3,8 @@ package de.avatic.lcc.excelmodel;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.utils.WeightUnit;
import java.util.Map;
public class ExcelPackaging {
private Integer id;
@ -19,6 +21,8 @@ public class ExcelPackaging {
private DimensionUnit shuDimensionUnit;
private WeightUnit shuWeightUnit;
private Integer shuUnitCount;
private Double huLength;
private Double huWidth;
private Double huHeight;
@ -27,6 +31,9 @@ public class ExcelPackaging {
private DimensionUnit huDimensionUnit;
private WeightUnit huWeightUnit;
private Integer huUnitCount;
private Map<String, String> properties;
public Integer getId() {
return id;
}
@ -146,4 +153,29 @@ public class ExcelPackaging {
public void setHuWeightUnit(WeightUnit huWeightUnit) {
this.huWeightUnit = huWeightUnit;
}
public Integer getShuUnitCount() {
return shuUnitCount;
}
public void setShuUnitCount(Integer shuUnitCount) {
this.shuUnitCount = shuUnitCount;
}
public Integer getHuUnitCount() {
return huUnitCount;
}
public void setHuUnitCount(Integer huUnitCount) {
this.huUnitCount = huUnitCount;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Map<String, String> getProperties() {
return properties;
}
}

View file

@ -79,6 +79,23 @@ public class MaterialRepository {
return jdbcTemplate.query(query, new MaterialMapper(), params.toArray());
}
public Optional<Material> getByPartNumber(String partNumber) {
if (partNumber == null) {
return Optional.empty();
}
String query = "SELECT * FROM material WHERE part_number = ? OR normalized_part_number = ?";
var material = jdbcTemplate.query(query, new MaterialMapper(), partNumber, partNumber);
if(material.isEmpty())
return Optional.empty();
return Optional.ofNullable(material.getFirst());
}
@Transactional
public Optional<Integer> setDeprecatedById(Integer id) {
String query = "UPDATE material SET is_deprecated = TRUE WHERE id = ?";
@ -190,6 +207,8 @@ public class MaterialRepository {
.collect(Collectors.toList());
}
private static class MaterialMapper implements RowMapper<Material> {
@Override

View file

@ -101,4 +101,19 @@ public class PackagingPropertiesRepository {
jdbcTemplate.update(query, value, packagingId, typeId, value);
}
public Integer getTypeIdByMappingId(String mappingId) {
String query = "SELECT id FROM packaging_property_type WHERE external_mapping_id = ?";
return jdbcTemplate.queryForObject(query, Integer.class, mappingId);
}
public void update(Integer packagingId, String typeId, String value) {
String query = """
INSERT INTO packaging_property (property_value, packaging_id, packaging_property_type_id) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE property_value = ?""";
jdbcTemplate.update(query, value, packagingId, typeId, value);
}
}

View file

@ -107,8 +107,7 @@ public class NodeService {
* @param includeUserNode if true, includes user-specific nodes in the search results.
* @return a list of {@link NodeDTO} objects representing the search results.
*/
public List<NodeDTO> searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) {
List<NodeDTO> nodes = new ArrayList<>();
public List<NodeDTO> searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) { List<NodeDTO> nodes = new ArrayList<>();
int userId = 1; //TODO get current user's id
if( includeUserNode && NodeType.SOURCE.equals(nodeType)) {

View file

@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
* Service for managing properties and property sets within the application.
@ -117,4 +118,40 @@ public class PropertyService {
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getProperty(SystemPropertyMappingId mappingId) {
var prop = propertyRepository.getPropertyByMappingId(mappingId);
if (prop.isEmpty())
return Optional.empty();
var property = prop.get();
var propValue = property.getCurrentValue();
var dataType = property.getDataType();
if (propValue == null)
return Optional.empty();
try {
Object convertedValue = convertStringByDataType(propValue, dataType);
return Optional.of((T) convertedValue);
} catch (ClassCastException e) {
// The caller expected a different type than what the dataType indicates
return Optional.empty();
} catch (Exception e) {
// Parsing error
return Optional.empty();
}
}
private Object convertStringByDataType(String value, String dataType) {
return switch (dataType.toUpperCase()) {
case "TEXT" -> value;
case "CURRENCY", "PERCENTAGE" -> Double.valueOf(value);
case "BOOLEAN" -> Boolean.valueOf(value);
case "INT" -> Integer.valueOf(value);
default -> throw new IllegalArgumentException("Unsupported data type: " + dataType);
};
}
}

View file

@ -4,9 +4,11 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
import de.avatic.lcc.model.properties.PackagingPropertyMappingId;
import de.avatic.lcc.model.properties.PropertyDataType;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.util.exception.badrequest.PropertyValidationException;
import de.avatic.lcc.util.exception.base.InternalErrorException;
@ -23,11 +25,13 @@ public class PropertyValidationService {
private final CountryPropertyRepository countryPropertyRepository;
private final PropertyRepository propertyRepository;
private final PackagingPropertiesRepository packagingPropertiesRepository;
public PropertyValidationService(ObjectMapper objectMapper, CountryPropertyRepository countryPropertyRepository, PropertyRepository propertyRepository) {
public PropertyValidationService(ObjectMapper objectMapper, CountryPropertyRepository countryPropertyRepository, PropertyRepository propertyRepository, PackagingPropertiesRepository packagingPropertiesRepository) {
this.objectMapper = objectMapper;
this.countryPropertyRepository = countryPropertyRepository;
this.propertyRepository = propertyRepository;
this.packagingPropertiesRepository = packagingPropertiesRepository;
}
public void validateProperty(SystemPropertyMappingId mappingId, String value) {
@ -36,6 +40,13 @@ public class PropertyValidationService {
validate(mappingId.name(), PropertyDataType.valueOf(property.orElseThrow().getDataType()), property.orElseThrow().getValidationRule(), value);
}
public void validatePackagingProperty(Integer packagingId, PackagingPropertyMappingId mappingId, String value) {
var property = packagingPropertiesRepository.getByPackagingIdAndType(packagingId, mappingId.name());
validate(mappingId.name(), property.orElseThrow().getType().getDataType(), property.orElseThrow().getType().getValidationRule(), value);
}
public void validateCountryProperty(Integer countryId, CountryPropertyMappingId mappingId, String value) {
var property = countryPropertyRepository.getByMappingIdAndCountryId(mappingId, countryId);

View file

@ -5,7 +5,10 @@ import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.bulk.BulkOperation;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.bulk.bulkImport.NodeBulkImportService;
import de.avatic.lcc.service.bulk.bulkImport.PackagingBulkImportService;
import de.avatic.lcc.service.excelMapper.*;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
@ -17,6 +20,7 @@ import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@Service
public class BulkImportService {
@ -28,8 +32,10 @@ public class BulkImportService {
private final NodeExcelMapper nodeExcelMapper;
private final NodeRepository nodeRepository;
private final NodeTransformer nodeTransformer;
private final NodeBulkImportService nodeBulkImportService;
private final PackagingBulkImportService packagingBulkImportService;
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeRepository nodeRepository, NodeTransformer nodeTransformer) {
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService) {
this.matrixRateExcelMapper = matrixRateExcelMapper;
this.containerRateExcelMapper = containerRateExcelMapper;
this.materialExcelMapper = materialExcelMapper;
@ -37,6 +43,8 @@ public class BulkImportService {
this.nodeExcelMapper = nodeExcelMapper;
this.nodeRepository = nodeRepository;
this.nodeTransformer = nodeTransformer;
this.nodeBulkImportService = nodeBulkImportService;
this.packagingBulkImportService = packagingBulkImportService;
}
public void processOperation(BulkOperation op) throws IOException {
@ -63,10 +71,11 @@ public class BulkImportService {
break;
case PACKAGING:
var packaging = packagingExcelMapper.extractSheet(sheet);
packaging.forEach(packagingBulkImportService::processPackagingInstructions);
break;
case NODE:
var nodeInstructions = nodeExcelMapper.extractSheet(sheet);
nodeInstructions.forEach(this::processNodeInstructions);
nodeInstructions.forEach(nodeBulkImportService::processNodeInstructions);
break;
default:
@ -75,31 +84,5 @@ public class BulkImportService {
}
private void processNodeInstructions(BulkInstruction<ExcelNode> instr) {
BulkInstructionType instrType = instr.getType();
ExcelNode excelNode = instr.getEntity();
if (instrType == BulkInstructionType.UPDATE) {
updateNode(excelNode);
} else if (instrType == BulkInstructionType.DELETE) {
deleteNode(excelNode);
}
}
private void deleteNode(ExcelNode excelNode) {
nodeRepository.setDeprecatedById(excelNode.getId());
}
private void updateNode(ExcelNode excelNode) {
var node = nodeRepository.getByExternalMappingId(excelNode.getExternalMappingId());
if(node.isEmpty()) {
nodeRepository.insert(nodeTransformer.toNodeEntity(excelNode));
} else {
nodeRepository.update(nodeTransformer.toNodeEntity(excelNode));
}
}
}

View file

@ -0,0 +1,120 @@
package de.avatic.lcc.service.bulk.bulkImport;
import de.avatic.lcc.excelmodel.ExcelNode;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class NodeBulkImportService {
private final NodeRepository nodeRepository;
private final NodeTransformer nodeTransformer;
public NodeBulkImportService(NodeRepository nodeRepository, NodeTransformer nodeTransformer) {
this.nodeRepository = nodeRepository;
this.nodeTransformer = nodeTransformer;
}
public void processNodeInstructions(BulkInstruction<ExcelNode> instr) {
BulkInstructionType instrType = instr.getType();
ExcelNode excelNode = instr.getEntity();
if (instrType == BulkInstructionType.UPDATE) {
updateNode(excelNode);
} else if (instrType == BulkInstructionType.DELETE) {
deleteNode(excelNode);
}
}
private void deleteNode(ExcelNode excelNode) {
var node = nodeRepository.getByExternalMappingId(excelNode.getExternalMappingId());
if (node.isEmpty()) {
throw new ExcelValidationError("Node with external mapping id " + excelNode.getExternalMappingId() + " not found");
} else {
nodeRepository.setDeprecatedById(node.get().getId());
}
}
private void updateNode(ExcelNode excelNode) {
var node = nodeRepository.getByExternalMappingId(excelNode.getExternalMappingId());
if (node.isEmpty()) {
nodeRepository.insert(nodeTransformer.toNodeEntity(excelNode));
} else {
var convertedNode = nodeTransformer.toNodeEntity(excelNode);
convertedNode.setId(node.get().getId());
if (!compare(convertedNode, node.get())) {
nodeRepository.update(convertedNode);
}
}
}
private boolean compare(Node updateNode, Node currentNode) {
return updateNode.getName().equals(currentNode.getName()) &&
updateNode.getGeoLat().compareTo(currentNode.getGeoLat()) == 0 &&
updateNode.getGeoLng().compareTo(currentNode.getGeoLng()) == 0 &&
updateNode.getExternalMappingId().equals(currentNode.getExternalMappingId()) &&
updateNode.getCountryId().equals(currentNode.getCountryId()) &&
updateNode.getIntermediate().equals(currentNode.getIntermediate()) &&
updateNode.getDestination().equals(currentNode.getDestination()) &&
updateNode.getSource().equals(currentNode.getSource()) &&
updateNode.getAddress().equals(currentNode.getAddress()) &&
updateNode.getDeprecated().equals(currentNode.getDeprecated()) &&
updateNode.getId().equals(currentNode.getId()) &&
updateNode.getPredecessorRequired().equals(currentNode.getPredecessorRequired()) &&
compare(updateNode.getNodePredecessors(), currentNode.getNodePredecessors()) &&
compare(updateNode.getOutboundCountries(), currentNode.getOutboundCountries());
}
private boolean compare(Collection<Integer> outbound1, Collection<Integer> outbound2) {
if (outbound1 == null && outbound2 == null) {
return true;
}
if (outbound1 == null || outbound2 == null) {
return false;
}
if (outbound1.size() != outbound2.size()) {
return false;
}
var set1 = new HashSet<>(outbound1);
var set2 = new HashSet<>(outbound2);
return set1.equals(set2);
}
private boolean compare(List<Map<Integer, Integer>> chain1, List<Map<Integer, Integer>> chain2) {
if (chain1 == null && chain2 == null) {
return true;
}
if (chain1 == null || chain2 == null) {
return false;
}
if (chain1.size() != chain2.size()) {
return false;
}
Set<Map<Integer, Integer>> set1 = new HashSet<>(chain1);
Set<Map<Integer, Integer>> set2 = new HashSet<>(chain2);
return set1.equals(set2);
}
}

View file

@ -0,0 +1,124 @@
package de.avatic.lcc.service.bulk.bulkImport;
import de.avatic.lcc.excelmodel.ExcelPackaging;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.packaging.Packaging;
import de.avatic.lcc.model.packaging.PackagingType;
import de.avatic.lcc.model.properties.PackagingPropertyMappingId;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
import de.avatic.lcc.repositories.packaging.PackagingRepository;
import de.avatic.lcc.service.access.PropertyValidationService;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.springframework.stereotype.Service;
@Service
public class PackagingBulkImportService {
private final PackagingRepository packagingRepository;
private final MaterialRepository materialRepository;
private final NodeRepository nodeRepository;
private final PackagingDimensionRepository packagingDimensionRepository;
private final DimensionTransformer dimensionTransformer;
private final PackagingPropertiesRepository packagingPropertiesRepository;
private final PropertyValidationService propertyValidationService;
public PackagingBulkImportService(PackagingRepository packagingRepository, MaterialRepository materialRepository, NodeRepository nodeRepository, PackagingDimensionRepository packagingDimensionRepository, DimensionTransformer dimensionTransformer, PackagingPropertiesRepository packagingPropertiesRepository, PropertyValidationService propertyValidationService) {
this.packagingRepository = packagingRepository;
this.materialRepository = materialRepository;
this.nodeRepository = nodeRepository;
this.packagingDimensionRepository = packagingDimensionRepository;
this.dimensionTransformer = dimensionTransformer;
this.packagingPropertiesRepository = packagingPropertiesRepository;
this.propertyValidationService = propertyValidationService;
}
public void processPackagingInstructions(BulkInstruction<ExcelPackaging> instr) {
BulkInstructionType instrType = instr.getType();
ExcelPackaging excelPackaging = instr.getEntity();
if (instrType == BulkInstructionType.UPDATE) {
updatePackaging(excelPackaging);
} else if (instrType == BulkInstructionType.DELETE) {
deletePackaging(excelPackaging);
}
}
private void deletePackaging(ExcelPackaging excelPackaging) {
var material = materialRepository.getByPartNumber(excelPackaging.getPartNumber());
var supplier = nodeRepository.getByExternalMappingId(excelPackaging.getSupplierMappingId());
if (material.isEmpty() || supplier.isEmpty()) {
throw new ExcelValidationError("Packaging references non existing supplier \"" + excelPackaging.getSupplierMappingId() + "\" and/or material \"" + excelPackaging.getPartNumber() + "\"");
} else {
packagingRepository.getByMaterialIdAndSupplierId(material.get().getId(), supplier.get().getId()).ifPresent(packaging -> packagingRepository.setDeprecatedById(packaging.getId()));
}
}
private void updatePackaging(ExcelPackaging excelPackaging) {
var material = materialRepository.getByPartNumber(excelPackaging.getPartNumber());
var supplier = nodeRepository.getByExternalMappingId(excelPackaging.getSupplierMappingId());
if (material.isEmpty() || supplier.isEmpty()) {
throw new ExcelValidationError("Packaging references non existing supplier \"" + excelPackaging.getSupplierMappingId() + "\" and/or material \"" + excelPackaging.getPartNumber() + "\"");
} else {
var excelHu = dimensionTransformer.toDimensionEntity(excelPackaging, PackagingType.HU);
var excelShu = dimensionTransformer.toDimensionEntity(excelPackaging, PackagingType.SHU);
var packaging = packagingRepository.getByMaterialIdAndSupplierId(material.get().getId(), supplier.get().getId());
if (packaging.isEmpty()) {
var huId = packagingDimensionRepository.insert(excelHu).orElseThrow();
var shuId = packagingDimensionRepository.insert(excelShu).orElseThrow();
var convertedPackaging = new Packaging();
convertedPackaging.setMaterialId(material.get().getId());
convertedPackaging.setSupplierId(supplier.get().getId());
convertedPackaging.setDeprecated(false);
convertedPackaging.setHuId(huId);
convertedPackaging.setShuId(shuId);
packagingRepository.insert(convertedPackaging);
} else {
var hu = packaging.flatMap(o -> packagingDimensionRepository.getById(o.getHuId()));
var shu = packaging.flatMap(o -> packagingDimensionRepository.getById(o.getShuId()));
excelHu.setId(hu.isEmpty() ? packagingDimensionRepository.insert(excelHu).orElseThrow() : hu.get().getId());
excelShu.setId(shu.isEmpty() ? packagingDimensionRepository.insert(excelShu).orElseThrow() : shu.get().getId());
var convertedPackaging = new Packaging();
convertedPackaging.setMaterialId(material.get().getId());
convertedPackaging.setSupplierId(supplier.get().getId());
convertedPackaging.setDeprecated(false);
convertedPackaging.setHuId(excelHu.getId());
convertedPackaging.setShuId(excelShu.getId());
convertedPackaging.setId(packaging.get().getId());
if (hu.isPresent())
packagingDimensionRepository.update(excelHu);
if (shu.isPresent())
packagingDimensionRepository.update(excelShu);
for (var mappingId : excelPackaging.getProperties().keySet()) {
var id = packagingPropertiesRepository.getTypeIdByMappingId(mappingId);
propertyValidationService.validatePackagingProperty(id, PackagingPropertyMappingId.valueOf(mappingId), excelPackaging.getProperties().get(mappingId));
packagingPropertiesRepository.update(convertedPackaging.getId(), id, excelPackaging.getProperties().get(mappingId));
}
packagingRepository.update(convertedPackaging);
}
}
}
}

View file

@ -193,6 +193,22 @@ public class ConstraintGenerator {
}
public void validateIntegerCell(Row row, int columnIdx) {
CellType cellType = row.getCell(columnIdx).getCellType();
if (cellType == CellType.NUMERIC) {
double value = row.getCell(columnIdx).getNumericCellValue();
if(value % 1 == 0) {
return;
}
}
throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected integer value");
}
public void validateStringCell(Row row, int columnIdx) {
CellType cellType = row.getCell(columnIdx).getCellType();
@ -202,7 +218,7 @@ public class ConstraintGenerator {
return;
}
throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value");
throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value");
}

View file

@ -99,9 +99,14 @@ public class PremiseSearchStringAnalyzerService {
while (matcher.find()) {
// Get the match from group 1 (inside the lookahead)
String partNumber = matcher.group(1);
partNumbers.add(partNumber);
partNumbers.add(normalizePartNumber(partNumber));
}
return partNumbers;
}
private static String normalizePartNumber(String partNumber) {
if (partNumber.length() > 12) throw new IllegalArgumentException("Part number must be less than 12 characters");
return "000000000000".concat(partNumber).substring(partNumber.length());
}
}

View file

@ -48,19 +48,6 @@ public class CustomCostCalculationService {
}
}
public CustomResult doD2dCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {
var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow();
var sourceUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, premise.getCountryId()).orElseThrow();
if (!CustomUnionType.EU.name().equals(destUnion.getCurrentValue()) || !CustomUnionType.NONE.name().equals(sourceUnion.getCurrentValue()))
return CustomResult.EMPTY;
double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(sections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue();
return getCustomCalculationResult(premise, destination, getContainerShare(premise, sections.getFirst().containerResult()), huAnnualAmount, sections.getFirst().result().getAnnualCost(), sections.getFirst().result().getAnnualChanceCost(), sections.getFirst().result().getAnnualRiskCost());
}
public CustomResult doCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {
var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow();

View file

@ -20,11 +20,9 @@ import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class PackagingExcelMapper {
@ -132,19 +130,32 @@ public class PackagingExcelMapper {
}
public List<ExcelPackaging> extractSheet(Sheet sheet) {
public List<BulkInstruction<ExcelPackaging>> extractSheet(Sheet sheet) {
headerGenerator.validateHeader(sheet, PackagingHeader.class);
Map<Integer, String> propertyMappingIds = getPropertyHeaders(sheet.getRow(0));
var packaging = new ArrayList<ExcelPackaging>();
var packaging = new ArrayList<BulkInstruction<ExcelPackaging>>();
sheet.forEach(row -> {
if(row.getRowNum() == 0) return;
packaging.add(mapToEntity(row));
packaging.add(mapToEntity(row, propertyMappingIds));
});
return packaging;
}
private Map<Integer, String> getPropertyHeaders(Row row) {
var propertyMappingIds = packagingPropertiesRepository.listTypes().stream().map(PropertyType::getExternalMappingId).toList();
private ExcelPackaging mapToEntity(Row row) {
var headers = new HashMap<Integer, String>();
for (int i = 0; i < row.getLastCellNum(); i++) {
if (propertyMappingIds.contains(row.getCell(i).getStringCellValue())) {
headers.put(i, row.getCell(i).getStringCellValue());
}
}
return headers;
}
private BulkInstruction<ExcelPackaging> mapToEntity(Row row, Map<Integer, String> propertyMappingIds) {
ExcelPackaging entity = new ExcelPackaging();
validateConstraints(row);
@ -168,7 +179,12 @@ public class PackagingExcelMapper {
entity.setShuDimensionUnit(DimensionUnit.valueOf(row.getCell(PackagingHeader.SHU_DIMENSION_UNIT.ordinal()).getStringCellValue()));
entity.setShuWeightUnit(WeightUnit.valueOf(row.getCell(PackagingHeader.SHU_WEIGHT_UNIT.ordinal()).getStringCellValue()));
return entity;
entity.setHuUnitCount(Double.valueOf(row.getCell(PackagingHeader.HU_UNIT_COUNT.ordinal()).getNumericCellValue()).intValue());
entity.setShuUnitCount(Double.valueOf(row.getCell(PackagingHeader.SHU_UNIT_COUNT.ordinal()).getNumericCellValue()).intValue());
entity.setProperties(propertyMappingIds.keySet().stream().collect(Collectors.toMap(propertyMappingIds::get, idx -> row.getCell(idx).getStringCellValue())));
return new BulkInstruction<>(entity, BulkInstructionType.valueOf(row.getCell(PackagingHeader.OPERATION.ordinal()).getStringCellValue()));
}
@ -186,6 +202,9 @@ public class PackagingExcelMapper {
constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_LENGTH.ordinal());
constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_WEIGHT.ordinal());
constraintGenerator.validateIntegerCell(row, PackagingHeader.HU_UNIT_COUNT.ordinal());
constraintGenerator.validateIntegerCell(row, PackagingHeader.SHU_UNIT_COUNT.ordinal());
constraintGenerator.validateLengthConstraint(row, PackagingHeader.PART_NUMBER.ordinal(), 0, 12);
constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_DIMENSION_UNIT.ordinal(), DimensionUnit.class);
constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_WEIGHT_UNIT.ordinal(), WeightUnit.class);

View file

@ -1,34 +1,49 @@
package de.avatic.lcc.service.precalculation;
import de.avatic.lcc.dto.generic.ContainerType;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.model.premises.route.Route;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.model.utils.WeightUnit;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.premise.RouteRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.service.CustomApiService;
import de.avatic.lcc.service.access.PropertyService;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Optional;
@Service
public class PreCalculationCheckService {
private static final double DIMENSION_TOLERANCE = 0.02;
private final PremiseRepository premiseRepository;
private final CustomApiService customApiService;
private final DestinationRepository destinationRepository;
private final RouteRepository routeRepository;
private final NodeRepository nodeRepository;
private final DimensionTransformer dimensionTransformer;
private final PropertyRepository propertyRepository;
private final PropertyService propertyService;
public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository) {
public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyRepository propertyRepository, PropertyService propertyService) {
this.premiseRepository = premiseRepository;
this.customApiService = customApiService;
this.destinationRepository = destinationRepository;
this.routeRepository = routeRepository;
this.nodeRepository = nodeRepository;
this.dimensionTransformer = dimensionTransformer;
this.propertyRepository = propertyRepository;
this.propertyService = propertyService;
}
public void doPrecheck(Integer premiseId) {
@ -45,12 +60,12 @@ public class PreCalculationCheckService {
var destinations = destinationRepository.getByPremiseId(premiseId);
if(destinations == null || destinations.isEmpty()) {
if (destinations == null || destinations.isEmpty()) {
throw new PremiseValidationError("No destination for calculation. At least one destination is required.");
}
for (Destination destination : destinations ) {
for (Destination destination : destinations) {
var node = nodeRepository.getByDestinationId(destination.getId()).orElseThrow();
@ -59,18 +74,17 @@ public class PreCalculationCheckService {
var routes = routeRepository.getByDestinationId(destination.getId());
if(routes.isEmpty() && destination.getD2d() == false)
if (routes.isEmpty() && destination.getD2d() == false)
throw new PremiseValidationError("No route found for destination " + node.getName() + ". Cannot use standard routing.");
if(routes.stream().noneMatch(Route::getSelected) && destination.getD2d() == false)
if (routes.stream().noneMatch(Route::getSelected) && destination.getD2d() == false)
throw new PremiseValidationError("No route selected for destination " + node.getName());
if(destination.getD2d() && (destination.getRateD2d() == null || destination.getRateD2d().compareTo(BigDecimal.ZERO) == 0)) {
if (destination.getD2d() && (destination.getRateD2d() == null || destination.getRateD2d().compareTo(BigDecimal.ZERO) == 0)) {
throw new PremiseValidationError("Door-2-door rate not set or set to zero.");
}
if(destination.getD2d() && (destination.getLeadTimeD2d() == null || destination.getLeadTimeD2d() == 0)) {
if (destination.getD2d() && (destination.getLeadTimeD2d() == null || destination.getLeadTimeD2d() == 0)) {
throw new PremiseValidationError("Door-2-door rate not set or set to zero.");
}
@ -84,7 +98,7 @@ public class PreCalculationCheckService {
throw new PremiseValidationError("In destination " + node.getName() + ": annual quantity must be greater than zero.");
if (destination.getD2d() == null)
throw new PremiseValidationError("In destination " + node.getName() +": D2D not set.");
throw new PremiseValidationError("In destination " + node.getName() + ": D2D not set.");
if (destination.getD2d() == true) {
if (destination.getRateD2d() == null || destination.getRateD2d().compareTo(BigDecimal.ZERO) == 0) {
@ -97,7 +111,7 @@ public class PreCalculationCheckService {
}
if (destination.getCountryId() == null || destination.getCountryId() == 0) {
throw new PremiseValidationError("In destination " + node.getName() +": destination country ID not set. Please contact administrator");
throw new PremiseValidationError("In destination " + node.getName() + ": destination country ID not set. Please contact administrator");
}
if (destination.getGeoLat() == null || destination.getGeoLng() == null) {
@ -183,6 +197,24 @@ public class PreCalculationCheckService {
throw new PremiseValidationError("Packaging dimension unit is not set");
}
var hu = dimensionTransformer.toDimensionEntity(premise).withTolerance(DIMENSION_TOLERANCE);
Optional<Integer> teuLoad = propertyService.getProperty(SystemPropertyMappingId.TEU_LOAD);
Optional<Integer> feuLoad = propertyService.getProperty(SystemPropertyMappingId.TEU_LOAD);
if (teuLoad.isEmpty() || feuLoad.isEmpty())
throw new PremiseValidationError("There is a system property misconfiguration. Please contact administrator");
if (WeightUnit.KG.convertFromG(hu.getWeight()) > teuLoad.get() && hu.getWeight() > feuLoad.get())
throw new PremiseValidationError("The handling unit is too heavy. Configured maximum is " + Math.max(teuLoad.get(), feuLoad.get()) + " kg. ");
var teuFitsXY = (hu.getLength() < ContainerType.TEU.getLength() && hu.getWidth() < ContainerType.TEU.getWidth());
var teuFitsYX = (hu.getWidth() < ContainerType.TEU.getLength() && hu.getLength() < ContainerType.TEU.getWidth());
if (!teuFitsYX && !teuFitsXY) {
throw new PremiseValidationError("The handling unit is too big. Has to fit into twenty food equivalent (minus " + DIMENSION_TOLERANCE + " tolerance) container.");
}
}
private void materialCheck(Premise premise) {

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.service.transformer.generic;
import de.avatic.lcc.dto.generic.DimensionDTO;
import de.avatic.lcc.excelmodel.ExcelPackaging;
import de.avatic.lcc.model.packaging.PackagingDimension;
import de.avatic.lcc.model.packaging.PackagingType;
import de.avatic.lcc.model.premises.Premise;
@ -108,4 +109,46 @@ public class DimensionTransformer {
return dto;
}
public PackagingDimension toDimensionEntity(ExcelPackaging packaging, PackagingType type) {
PackagingDimension dimension = new PackagingDimension();
if (type == PackagingType.HU) {
WeightUnit weightUnit = packaging.getHuWeightUnit();
DimensionUnit unit = packaging.getHuDimensionUnit();
dimension.setLength(unit.convertToMM(packaging.getHuLength()));
dimension.setHeight(unit.convertToMM(packaging.getHuHeight()));
dimension.setWidth(unit.convertToMM(packaging.getHuWidth()));
dimension.setDimensionUnit(unit);
dimension.setWeight(weightUnit.convertToG(packaging.getHuWeight()));
dimension.setWeightUnit(weightUnit);
dimension.setContentUnitCount(packaging.getHuUnitCount());
dimension.setType(PackagingType.HU);
dimension.setDeprecated(false);
} else if (type == PackagingType.SHU) {
WeightUnit weightUnit = packaging.getShuWeightUnit();
DimensionUnit unit = packaging.getShuDimensionUnit();
dimension.setLength(unit.convertToMM(packaging.getShuLength()));
dimension.setHeight(unit.convertToMM(packaging.getShuHeight()));
dimension.setWidth(unit.convertToMM(packaging.getShuWidth()));
dimension.setDimensionUnit(unit);
dimension.setWeight(weightUnit.convertToG(packaging.getShuWeight()));
dimension.setWeightUnit(weightUnit);
dimension.setContentUnitCount(packaging.getShuUnitCount());
dimension.setType(PackagingType.SHU);
dimension.setDeprecated(false);
}
return dimension;
}
}

View file

@ -90,6 +90,8 @@ public class NodeTransformer {
}
entity.setId(excelNode.getId());
entity.setExternalMappingId(excelNode.getExternalMappingId());
entity.setName(excelNode.getName());
entity.setAddress(excelNode.getAddress());
entity.setCountryId(countryId.get().getId());

View file

@ -7,7 +7,8 @@
INSERT INTO packaging_property_type (name, external_mapping_id, data_type, validation_rule, is_required)
VALUES
('Stackable', 'STACKABLE', 'BOOLEAN', NULL, FALSE),
('Rust Prevention', 'RUST_PREVENTION', 'BOOLEAN', NULL, FALSE)
('Rust Prevention', 'RUST_PREVENTION', 'BOOLEAN', NULL, FALSE),
('Mixable', 'MIXABLE', 'BOOLEAN', NULL, FALSE)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
data_type = VALUES(data_type);