diff --git a/src/frontend/src/components/layout/config/Property.vue b/src/frontend/src/components/layout/config/Property.vue index 3852069..537fa36 100644 --- a/src/frontend/src/components/layout/config/Property.vue +++ b/src/frontend/src/components/layout/config/Property.vue @@ -2,8 +2,9 @@
-
{{ property.name }}:
-
Ext. Mapping: {{ property.external_mapping_id }}
+
{{ property.name }}:
+
{{ property.description }}
+
{{ property.external_mapping_id}}
@@ -43,10 +44,11 @@ import Dropdown from "@/components/UI/Dropdown.vue"; import IconButton from "@/components/UI/IconButton.vue"; import Tooltip from "@/components/UI/Tooltip.vue"; import {parseNumberFromString} from "@/common.js"; +import BasicBadge from "@/components/UI/BasicBadge.vue"; export default { name: "Property", - components: {Tooltip, IconButton, Dropdown, ToggleSwitch, PhGear, InputField}, + components: {BasicBadge, Tooltip, IconButton, Dropdown, ToggleSwitch, PhGear, InputField}, props: { property: { type: Object, @@ -199,6 +201,7 @@ export default { align-items: center; gap: 2.4rem; height: 8rem; + margin: 3.6rem 0 ; } .caption-column { @@ -206,6 +209,7 @@ export default { flex-direction: column; font-weight: 300; font-size: 1.4rem; + max-width: 60rem; } .caption-column-id { diff --git a/src/frontend/src/pages/CalculationMassEdit.vue b/src/frontend/src/pages/CalculationMassEdit.vue index 3150225..b4ec499 100644 --- a/src/frontend/src/pages/CalculationMassEdit.vue +++ b/src/frontend/src/pages/CalculationMassEdit.vue @@ -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 { diff --git a/src/main/java/de/avatic/lcc/calculationmodel/ContainerCalculationResult.java b/src/main/java/de/avatic/lcc/calculationmodel/ContainerCalculationResult.java index 2deaa38..49c08d3 100644 --- a/src/main/java/de/avatic/lcc/calculationmodel/ContainerCalculationResult.java +++ b/src/main/java/de/avatic/lcc/calculationmodel/ContainerCalculationResult.java @@ -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; } } diff --git a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java index df227b8..d455dea 100644 --- a/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java +++ b/src/main/java/de/avatic/lcc/controller/configuration/NodeController.java @@ -61,11 +61,11 @@ public class NodeController { return ResponseEntity.ok(nodeService.deleteNode(id)); } -// @PutMapping("/{id}") -// public ResponseEntity updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) { -// Check.equals(id, node.getId()); -// return ResponseEntity.ok(nodeService.updateNode(node)); -// } + @PutMapping("/{id}") + public ResponseEntity updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) { + Check.equals(id, node.getId()); + return ResponseEntity.ok(nodeService.updateNode(node)); + } @GetMapping("/locate") public ResponseEntity locateNode(@RequestParam String address) { diff --git a/src/main/java/de/avatic/lcc/dto/generic/PropertyDTO.java b/src/main/java/de/avatic/lcc/dto/generic/PropertyDTO.java index ab7b633..bd01ed6 100644 --- a/src/main/java/de/avatic/lcc/dto/generic/PropertyDTO.java +++ b/src/main/java/de/avatic/lcc/dto/generic/PropertyDTO.java @@ -24,6 +24,15 @@ public class PropertyDTO { @JsonProperty("validation_rule") private String validationRule; + @JsonProperty("description") + private String description; + + @JsonProperty("property_group") + private String propertyGroup; + + @JsonProperty("sequence_number") + private Integer sequenceNumber; + public String getName() { return name; } @@ -79,4 +88,28 @@ public class PropertyDTO { public String getValidationRule() { return validationRule; } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getPropertyGroup() { + return propertyGroup; + } + + public void setPropertyGroup(String propertyGroup) { + this.propertyGroup = propertyGroup; + } + + public Integer getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(Integer sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } } diff --git a/src/main/java/de/avatic/lcc/excelmodel/ExcelPackaging.java b/src/main/java/de/avatic/lcc/excelmodel/ExcelPackaging.java index 231348b..ae6ac0f 100644 --- a/src/main/java/de/avatic/lcc/excelmodel/ExcelPackaging.java +++ b/src/main/java/de/avatic/lcc/excelmodel/ExcelPackaging.java @@ -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 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 properties) { + this.properties = properties; + + } + + public Map getProperties() { + return properties; + } } diff --git a/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java b/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java index f3f19dd..7041272 100644 --- a/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java +++ b/src/main/java/de/avatic/lcc/model/properties/SystemPropertyMappingId.java @@ -42,7 +42,8 @@ public enum SystemPropertyMappingId { KLT_HANDLING("GR handling KLT [EUR/HU]", "0,71 €"), GLT_HANDLING("GR handling GLT [EUR/HU]", "3,50 €"), - BOOKING("GLT/KLT booking & document handling [EUR/GR]", "3,50 €"), + BOOKING("GLT booking & document handling [EUR/GR]", "3,50 €"), + BOOKING_KLT("KLT booking & document handling [EUR/GR]", "0,35 €"), GLT_RELEASE("GLT release from storage [EUR/GLT release]", "2,23 €"), KLT_RELEASE("KLT release from storage [EUR/KLT release]", "1,12 €"), GLT_DISPATCH("GLT dispatch [EUR/GLT dispatch]", "1,61 €"), diff --git a/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java index eef8059..1555176 100644 --- a/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java @@ -79,6 +79,23 @@ public class MaterialRepository { return jdbcTemplate.query(query, new MaterialMapper(), params.toArray()); } + public Optional 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 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 { @Override diff --git a/src/main/java/de/avatic/lcc/repositories/country/CountryPropertyRepository.java b/src/main/java/de/avatic/lcc/repositories/country/CountryPropertyRepository.java index ebe5f29..4e9fe97 100644 --- a/src/main/java/de/avatic/lcc/repositories/country/CountryPropertyRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/country/CountryPropertyRepository.java @@ -65,8 +65,12 @@ public class CountryPropertyRepository { type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, type.is_required as is_required, + type.description as description, + type.property_group as propertyGroup, + type.sequence_number as sequenceNumber, draft.property_value as draftValue, valid.property_value as validValue + FROM country_property_type AS type LEFT JOIN ( SELECT cp.property_value, cp.country_property_type_id @@ -104,6 +108,10 @@ public class CountryPropertyRepository { type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, type.is_required as is_required, + type.is_required as is_required, + type.description as description, + type.property_group as propertyGroup, + type.sequence_number as sequenceNumber, MAX(CASE WHEN ps.state = 'DRAFT' THEN cp.property_value END) as draftValue, MAX(CASE WHEN ps.state = 'VALID' THEN cp.property_value END) as validValue FROM country_property_type AS type @@ -121,6 +129,9 @@ public class CountryPropertyRepository { String query = """ SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, type.is_required as is_required, + type.description as description, + type.property_group as propertyGroup, + type.sequence_number as sequenceNumber, property.property_value as draftValue, property.property_value as validValue FROM country_property_type AS type LEFT JOIN country_property AS property ON property.country_property_type_id = type.id @@ -161,6 +172,10 @@ public class CountryPropertyRepository { dto.setRequired(rs.getBoolean("is_required")); dto.setDataType(rs.getString("dataType")); + dto.setDescription(rs.getString("description")); + dto.setPropertyGroup(rs.getString("propertyGroup")); + dto.setSequenceNumber(rs.getInt("sequenceNumber")); + return dto; } } diff --git a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java index bd4fde6..d4424f9 100644 --- a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingPropertiesRepository.java @@ -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); + } + } diff --git a/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java b/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java index 0b4917f..91d4717 100644 --- a/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/properties/PropertyRepository.java @@ -92,13 +92,14 @@ public class PropertyRepository { String query = """ SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, + type.description as description, type.property_group as propertyGroup, type.sequence_number as sequenceNumber, MAX(CASE WHEN ps.state = ? THEN sp.property_value END) as draftValue, MAX(CASE WHEN ps.state = ? THEN sp.property_value END) as validValue FROM system_property_type AS type LEFT JOIN system_property AS sp ON sp.system_property_type_id = type.id LEFT JOIN property_set AS ps ON ps.id = sp.property_set_id AND ps.state IN (?, ?) - GROUP BY type.id, type.name, type.data_type, type.external_mapping_id, type.validation_rule - HAVING draftValue IS NOT NULL OR validValue IS NOT NULL ORDER BY type.name; + GROUP BY type.id, type.name, type.data_type, type.external_mapping_id, type.validation_rule, type.description, type.property_group, type.sequence_number + HAVING draftValue IS NOT NULL OR validValue IS NOT NULL ORDER BY type.property_group , type.sequence_number; """; return jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.DRAFT.name(), ValidityPeriodState.VALID.name(), ValidityPeriodState.DRAFT.name(), ValidityPeriodState.VALID.name()); @@ -115,11 +116,12 @@ public class PropertyRepository { String query = """ SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, + type.description as description, type.property_group as propertyGroup, type.sequence_number as sequenceNumber, NULL as draftValue, property.property_value as validValue FROM system_property_type AS type LEFT JOIN system_property AS property ON property.system_property_type_id = type.id LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id - WHERE propertySet.id = ? AND (propertySet.state = ? OR propertySet.state = ?) ORDER BY type.name; + WHERE propertySet.id = ? AND (propertySet.state = ? OR propertySet.state = ?) ORDER BY type.property_group , type.sequence_number; """; return jdbcTemplate.query(query, new PropertyMapper(), propertySetId, ValidityPeriodState.EXPIRED.name(), ValidityPeriodState.INVALID.name()); @@ -129,6 +131,7 @@ public class PropertyRepository { public Optional getPropertyByMappingId(SystemPropertyMappingId mappingId) { String query = """ SELECT type.name as name, type.data_type as dataType, type.external_mapping_id as externalMappingId, type.validation_rule as validationRule, + type.description as description, type.property_group as propertyGroup, type.sequence_number as sequenceNumber, property.property_value as draftValue, property.property_value as validValue FROM system_property_type AS type LEFT JOIN system_property AS property ON property.system_property_type_id = type.id @@ -190,6 +193,10 @@ public class PropertyRepository { dto.setRequired(true); dto.setDataType(rs.getString("dataType")); + dto.setDescription(rs.getString("description")); + dto.setPropertyGroup(rs.getString("propertyGroup")); + dto.setSequenceNumber(rs.getInt("sequenceNumber")); + return dto; } } diff --git a/src/main/java/de/avatic/lcc/service/access/NodeService.java b/src/main/java/de/avatic/lcc/service/access/NodeService.java index 438db3e..f0cc764 100644 --- a/src/main/java/de/avatic/lcc/service/access/NodeService.java +++ b/src/main/java/de/avatic/lcc/service/access/NodeService.java @@ -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 searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) { - List nodes = new ArrayList<>(); + public List searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) { List nodes = new ArrayList<>(); int userId = 1; //TODO get current user's id if( includeUserNode && NodeType.SOURCE.equals(nodeType)) { diff --git a/src/main/java/de/avatic/lcc/service/access/PropertyService.java b/src/main/java/de/avatic/lcc/service/access/PropertyService.java index e26146e..b415328 100644 --- a/src/main/java/de/avatic/lcc/service/access/PropertyService.java +++ b/src/main/java/de/avatic/lcc/service/access/PropertyService.java @@ -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 Optional 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", "ENUMERATION" -> 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); + }; + } } diff --git a/src/main/java/de/avatic/lcc/service/access/PropertyValidationService.java b/src/main/java/de/avatic/lcc/service/access/PropertyValidationService.java index 4dd9a4d..50c4341 100644 --- a/src/main/java/de/avatic/lcc/service/access/PropertyValidationService.java +++ b/src/main/java/de/avatic/lcc/service/access/PropertyValidationService.java @@ -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); diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java index 8cfd96e..8948f1d 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java @@ -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 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)); - } - } } diff --git a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java new file mode 100644 index 0000000..646aabc --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/NodeBulkImportService.java @@ -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 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 outbound1, Collection 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> chain1, List> chain2) { + + if (chain1 == null && chain2 == null) { + return true; + } + + if (chain1 == null || chain2 == null) { + return false; + } + + if (chain1.size() != chain2.size()) { + return false; + } + + Set> set1 = new HashSet<>(chain1); + Set> set2 = new HashSet<>(chain2); + + return set1.equals(set2); + } + +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/PackagingBulkImportService.java b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/PackagingBulkImportService.java new file mode 100644 index 0000000..4f01777 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/PackagingBulkImportService.java @@ -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 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); + } + } + + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java b/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java index e854948..6280eac 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java @@ -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"); } diff --git a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java index 8ece6d1..67bdb8e 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/PremiseSearchStringAnalyzerService.java @@ -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()); + } } diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java index 2247be7..4b71ac7 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/CustomCostCalculationService.java @@ -48,19 +48,6 @@ public class CustomCostCalculationService { } } - public CustomResult doD2dCalculation(Premise premise, Destination destination, List 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 sections) { var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow(); diff --git a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java index bef9df3..b4bcd82 100644 --- a/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java +++ b/src/main/java/de/avatic/lcc/service/calculation/execution/steps/HandlingCostCalculationService.java @@ -57,7 +57,7 @@ public class HandlingCostCalculationService { BigDecimal disposal = destinationDisposal != null ? destinationDisposal : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL).orElseThrow().getCurrentValue())); BigDecimal wageFactor = BigDecimal.valueOf(Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, destination.getCountryId()).orElseThrow().getCurrentValue())); - BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING).orElseThrow().getCurrentValue())); + BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING_KLT).orElseThrow().getCurrentValue())); return new HandlingResult(LoadCarrierType.SLC, getRepackingCost(hu, loadCarrierType, addRepackingCosts, destinationRepacking).multiply(huAnnualAmount), diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java index bc8beac..3294c99 100644 --- a/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java @@ -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 extractSheet(Sheet sheet) { + public List> extractSheet(Sheet sheet) { headerGenerator.validateHeader(sheet, PackagingHeader.class); + Map propertyMappingIds = getPropertyHeaders(sheet.getRow(0)); - var packaging = new ArrayList(); + var packaging = new ArrayList>(); sheet.forEach(row -> { if(row.getRowNum() == 0) return; - packaging.add(mapToEntity(row)); + packaging.add(mapToEntity(row, propertyMappingIds)); }); return packaging; } + private Map getPropertyHeaders(Row row) { + var propertyMappingIds = packagingPropertiesRepository.listTypes().stream().map(PropertyType::getExternalMappingId).toList(); - private ExcelPackaging mapToEntity(Row row) { + var headers = new HashMap(); + 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 mapToEntity(Row row, Map 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); diff --git a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java index fa7fc83..b852a74 100644 --- a/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java +++ b/src/main/java/de/avatic/lcc/service/precalculation/PreCalculationCheckService.java @@ -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 teuLoad = propertyService.getProperty(SystemPropertyMappingId.TEU_LOAD); + Optional 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) { diff --git a/src/main/java/de/avatic/lcc/service/transformer/generic/DimensionTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/generic/DimensionTransformer.java index 58757ac..6857395 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/generic/DimensionTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/generic/DimensionTransformer.java @@ -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; + } } diff --git a/src/main/java/de/avatic/lcc/service/transformer/generic/NodeTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/generic/NodeTransformer.java index c075381..f01db86 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/generic/NodeTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/generic/NodeTransformer.java @@ -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()); diff --git a/src/main/java/de/avatic/lcc/service/transformer/material/MaterialDetailPackagingPropertyTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/material/MaterialDetailPackagingPropertyTransformer.java index 3cd00df..f9c2d94 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/material/MaterialDetailPackagingPropertyTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/material/MaterialDetailPackagingPropertyTransformer.java @@ -30,6 +30,9 @@ public class MaterialDetailPackagingPropertyTransformer { dto.setExternalMappingId(property.getType().getExternalMappingId()); dto.setValidationRule(property.getType().getValidationRule()); + + + return dto; } diff --git a/src/main/resources/master_data/01-properties.sql b/src/main/resources/master_data/01-properties.sql index 126e635..7b37234 100644 --- a/src/main/resources/master_data/01-properties.sql +++ b/src/main/resources/master_data/01-properties.sql @@ -5,51 +5,51 @@ -- Description -> name -- =================================================== -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Reference route: Port of origin', 'START_REF', 'TEXT', '{}','Reference route used to demonstrate sea freight fluctuation, port in country of origin','Reference route','1'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Reference route: Port of destination', 'END_REF', 'TEXT', '{}','Reference route used to demonstrate sea freight fluctuation, port in country of destination','Reference route','2'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'All-time-high container rate (40 ft.) [EUR]', 'RISK_REF', 'CURRENCY', '{"GT":0}','Highest container rate to be used to demnastrate sea freight fluctuation, cost for 40 Ft. (FEU) general purpose container','Reference route','3'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'All-time-low container rate (40 ft.) [EUR]', 'CHANCE_REF', 'CURRENCY', '{"GT":0}','Lowest container rate to be used to demnastrate sea freight fluctuation, cost for 40 Ft. (FEU) general purpose container','Reference route','4'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Payment terms [days]', 'PAYMENT_TERMS', 'INT', '{}','Payment target [days] agreed with suppliers','General','3'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Annual working days', 'WORKDAYS', 'INT', '{"GT": 0, "LT": 366}','Annual working days (production)','General','2'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Interest rate inventory [%]', 'INTEREST_RATE', 'PERCENTAGE', '{"GTE": 0}','Calculatory interest rate for inventories and payment targets','General','4'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'FCA fee [%]', 'FCA_FEE', 'PERCENTAGE', '{"GTE": 0}','FCA fee to be added on EXW prices (MEK_A)','General','5'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Default customs rate [%]', 'TARIFF_RATE', 'PERCENTAGE', '{"GTE":0}','Customs rate to be applied, if no exact value is available','General','6'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Customs clearance fee per import & HS code [EUR]', 'CUSTOM_FEE', 'CURRENCY', '{"GTE":0}','Average customs clearance fee charged for the import of one HS code per import (mixed container)','General','7'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Standard reporting format', 'REPORTING', 'ENUMERATION', '{"ENUM":["MEK_B","MEK_C"]}','Defines standard format for reports (MEK_B = excl. Risks, MEK_C = incl. risks)','General','1'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( '40 ft.', 'FEU', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','1'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( '20 ft.', 'TEU', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','2'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( '40 ft. HC', 'FEU_HQ', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','3'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Container utilization in mixed containers [%]', 'CONTAINER_UTIL', 'PERCENTAGE', '{"GTE":0,"LTE":1}','Defines, to which degree the volume of a mixed container shall be utilized (loss due to inefficient stacking and different packaging sizes).','Sea and road transport','6'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Truck utilization road transport EMEA [%]', 'TRUCK_UTIL', 'PERCENTAGE', '{"GTE":0,"LTE":1}','Defines, to which degree the volume of a truck shall be utilized (loss due to inefficient stacking and different packaging sizes).','Sea and road transport','8'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Max validity period of container freight rates [days]', 'VALID_DAYS', 'INT', '{"GT": 0}','Defines the maximum period of time, after which container freight rates cannot be used for calculations anymore.','General','8'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Metropolition region size (diameter) [km]', 'RADIUS_REGION', 'INT', '{"GT": 0}','Defines the average distance a supplier may have from a defined source (e.g. center of metropolitan area), for which a container rate is maintained.','General','9'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Min delivery frequency / year for containter transports', 'FREQ_MIN', 'INT', '{"GT": 0, "LT": 366}','Relevant for low runners; defines into how many deliveries a HU shall be split, if HU content >= yearly demand (e.g. 4 = 1/4 of yearly demand is allocated to a shipment)','General','10'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Max delivery frequency / year for containter transport', 'FREQ_MAX', 'INT', '{"GT": 0, "LT": 366}','Relevant for high runners; defines the maximum shipping frequency for sea shipments (e.g. 52 = weekly, 104 = 2x/week, 12 = monthly etc.)','General','11'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Max load 20 ft. container [kg]', 'TEU_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a TEU container.','Sea and road transport','4'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Max load 40 ft. container [kg]', 'FEU_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a FEU container. Can be also limited by local law (e.g. road transport weight in CN limited to 21 tons).','Sea and road transport','5'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Max load truck [kg]', 'TRUCK_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a truck (use standard type, usually 40-ton truck)','Sea and road transport','7'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Pre-carriage [EUR/kg]', 'AIR_PRECARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for pre-carriage to airport in country of origin','Air transport','1'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Pre-carriage handling [EUR]', 'AIR_HANDLING', 'CURRENCY', '{"GTE": 0}','For air transports only: one time costs for handling of air shipments (documents)','Air transport','2'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Main carriage [EUR/kg]', 'AIR_MAINCARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for flight. (use as reference route the flight route with the highest traffic, e.g. CN-DE)','Air transport','3'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Hand over fee [EUR]', 'AIR_HANDOVER_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: one time cost for handover of air shipment ','Air transport','4'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Customs clearance fee [EUR]', 'AIR_CUSTOM_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: one time cost for customs clearance of air shipment ','Air transport','5'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'On-carriage [EUR/kg]', 'AIR_ONCARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for on-carriage from airport to destination','Air transport','6'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Terminal handling fee [EUR/kg]', 'AIR_TERMINAL_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for handling of shipment in airport terminal','Air transport','7'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GR handling KLT [EUR/HU] (DE)', 'KLT_HANDLING', 'CURRENCY', '{"GTE": 0}','Cost per KLT for the handling in the Goods Receipt area of a warehouse or plant; reference country: Germany','Warehouse','4'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GR handling GLT [EUR/HU] (DE)', 'GLT_HANDLING', 'CURRENCY', '{"GTE": 0}','Cost per GLT for the handling in the Goods Receipt area of a warehouse or plant; reference country: Germany','Warehouse','5'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GLT booking & document handling [EUR/GR] (DE)', 'BOOKING', 'CURRENCY', '{"GTE": 0}','One time document handling fee for 1 GLT; reference country: Germany','Warehouse','2'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GLT release from storage [EUR/GLT release] (DE)', 'GLT_RELEASE', 'CURRENCY', '{"GTE": 0}','Cost to release one GLT from the storage; reference country: Germany','Warehouse','12'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'KLT release from storage [EUR/KLT release] (DE)', 'KLT_RELEASE', 'CURRENCY', '{"GTE": 0}','Cost to release one KLT from the storage; reference country: Germany','Warehouse','11'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GLT dispatch [EUR/GLT dispatch] (DE)', 'GLT_DISPATCH', 'CURRENCY', '{"GTE": 0}','Cost to disptach one GLT; reference country: Germany','Warehouse','14'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'KLT dispacth [EUR/KLT dispatch] (DE)', 'KLT_DISPATCH', 'CURRENCY', '{"GTE": 0}','Cost to disptach one KLT; reference country: Germany','Warehouse','13'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Repacking KLT, HU <15kg [EUR/HU] (DE)', 'KLT_REPACK_S', 'CURRENCY', '{"GTE": 0}','Cost to repack one KLT (<15 kg) from one-way to returnable empty; reference country: Germany','Warehouse','6'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Repacking KLT, HU >=15kg [EUR/HU] (DE)', 'KLT_REPACK_M', 'CURRENCY', '{"GTE": 0}','Cost to repack one KLT (>=15 kg) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','7'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Repacking GLT, HU <15kg [EUR/HU] (DE)', 'GLT_REPACK_S', 'CURRENCY', '{"GTE": 0}','Cost to repack one GLT (<15 kg) from oneway to returnable empty; reference country: Germany','Warehouse','8'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Repacking GLT, HU 15 - 2000kg [EUR/HU] (DE)', 'GLT_REPACK_M', 'CURRENCY', '{"GTE": 0}','Cost to repack one GLT (>=15 - 2000 kg) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','9'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Repacking GLT, HU >2000kg [EUR/HU] (DE)', 'GLT_REPACK_L', 'INT', '{"GTE": 0}','Cost to repack one GLT (> 2000 kg, e.g. counterweight) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','10'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'GLT disposal [EUR/GLT] (DE)', 'DISPOSAL', 'INT', '{"GTE": 0}','Cost to dispose one wooden pallet (value valid for all countries)','Warehouse','15'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'Space costs per cbm per night [EUR/cbm] (DE)', 'SPACE_COST', 'CURRENCY', '{"GTE": 0}','Cost per month for a standard storage bin (1,20 x 0,80 x 1,00 m)','Warehouse','1'); -INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sort_id) VALUES ( 'KLT booking & document handling [EUR/GR] (DE)', 'BOOKING_KLT', 'CURRENCY', '{"GTE": 0}','One time document handling fee for 1 KLT; reference country: Germany','Warehouse','3'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Reference route: Port of origin', 'START_REF', 'TEXT', '{}','Reference route used to demonstrate sea freight fluctuation, port in country of origin','Reference route','1'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Reference route: Port of destination', 'END_REF', 'TEXT', '{}','Reference route used to demonstrate sea freight fluctuation, port in country of destination','Reference route','2'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'All-time-high container rate (40 ft.) [EUR]', 'RISK_REF', 'CURRENCY', '{"GT":0}','Highest container rate to be used to demnastrate sea freight fluctuation, cost for 40 Ft. (FEU) general purpose container','Reference route','3'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'All-time-low container rate (40 ft.) [EUR]', 'CHANCE_REF', 'CURRENCY', '{"GT":0}','Lowest container rate to be used to demnastrate sea freight fluctuation, cost for 40 Ft. (FEU) general purpose container','Reference route','4'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Payment terms [days]', 'PAYMENT_TERMS', 'INT', '{}','Payment target [days] agreed with suppliers','General','3'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Annual working days', 'WORKDAYS', 'INT', '{"GT": 0, "LT": 366}','Annual working days (production)','General','2'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Interest rate inventory [%]', 'INTEREST_RATE', 'PERCENTAGE', '{"GTE": 0}','Calculatory interest rate for inventories and payment targets','General','4'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'FCA fee [%]', 'FCA_FEE', 'PERCENTAGE', '{"GTE": 0}','FCA fee to be added on EXW prices (MEK_A)','General','5'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Default customs rate [%]', 'TARIFF_RATE', 'PERCENTAGE', '{"GTE":0}','Customs rate to be applied, if no exact value is available','General','6'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Customs clearance fee per import & HS code [EUR]', 'CUSTOM_FEE', 'CURRENCY', '{"GTE":0}','Average customs clearance fee charged for the import of one HS code per import (mixed container)','General','7'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Standard reporting format', 'REPORTING', 'ENUMERATION', '{"ENUM":["MEK_B","MEK_C"]}','Defines standard format for reports (MEK_B = excl. Risks, MEK_C = incl. risks)','General','1'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( '40 ft.', 'FEU', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','1'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( '20 ft.', 'TEU', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','2'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( '40 ft. HC', 'FEU_HQ', 'BOOLEAN', '{}','To be activated, if calculation shall be possible with this container size. Container rates to be maintained.','Sea and road transport','3'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Container utilization in mixed containers [%]', 'CONTAINER_UTIL', 'PERCENTAGE', '{"GTE":0,"LTE":1}','Defines, to which degree the volume of a mixed container shall be utilized (loss due to inefficient stacking and different packaging sizes).','Sea and road transport','6'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Truck utilization road transport EMEA [%]', 'TRUCK_UTIL', 'PERCENTAGE', '{"GTE":0,"LTE":1}','Defines, to which degree the volume of a truck shall be utilized (loss due to inefficient stacking and different packaging sizes).','Sea and road transport','8'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Max validity period of container freight rates [days]', 'VALID_DAYS', 'INT', '{"GT": 0}','Defines the maximum period of time, after which container freight rates cannot be used for calculations anymore.','General','8'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Metropolition region size (diameter) [km]', 'RADIUS_REGION', 'INT', '{"GT": 0}','Defines the average distance a supplier may have from a defined source (e.g. center of metropolitan area), for which a container rate is maintained.','General','9'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Min delivery frequency / year for containter transports', 'FREQ_MIN', 'INT', '{"GT": 0, "LT": 366}','Relevant for low runners; defines into how many deliveries a HU shall be split, if HU content >= yearly demand (e.g. 4 = 1/4 of yearly demand is allocated to a shipment)','General','10'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Max delivery frequency / year for containter transport', 'FREQ_MAX', 'INT', '{"GT": 0, "LT": 366}','Relevant for high runners; defines the maximum shipping frequency for sea shipments (e.g. 52 = weekly, 104 = 2x/week, 12 = monthly etc.)','General','11'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Max load 20 ft. container [kg]', 'TEU_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a TEU container.','Sea and road transport','4'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Max load 40 ft. container [kg]', 'FEU_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a FEU container. Can be also limited by local law (e.g. road transport weight in CN limited to 21 tons).','Sea and road transport','5'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Max load truck [kg]', 'TRUCK_LOAD', 'INT', '{"GT": 0}','Defines the weight limit of a truck (use standard type, usually 40-ton truck)','Sea and road transport','7'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Pre-carriage [EUR/kg]', 'AIR_PRECARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for pre-carriage to airport in country of origin','Air transport','1'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Pre-carriage handling [EUR]', 'AIR_HANDLING', 'CURRENCY', '{"GTE": 0}','For air transports only: one time costs for handling of air shipments (documents)','Air transport','2'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Main carriage [EUR/kg]', 'AIR_MAINCARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for flight. (use as reference route the flight route with the highest traffic, e.g. CN-DE)','Air transport','3'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Hand over fee [EUR]', 'AIR_HANDOVER_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: one time cost for handover of air shipment ','Air transport','4'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Customs clearance fee [EUR]', 'AIR_CUSTOM_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: one time cost for customs clearance of air shipment ','Air transport','5'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'On-carriage [EUR/kg]', 'AIR_ONCARRIAGE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for on-carriage from airport to destination','Air transport','6'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Terminal handling fee [EUR/kg]', 'AIR_TERMINAL_FEE', 'CURRENCY', '{"GTE": 0}','For air transports only: cost per kg for handling of shipment in airport terminal','Air transport','7'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GR handling KLT [EUR/HU] (DE)', 'KLT_HANDLING', 'CURRENCY', '{"GTE": 0}','Cost per KLT for the handling in the Goods Receipt area of a warehouse or plant; reference country: Germany','Warehouse','4'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GR handling GLT [EUR/HU] (DE)', 'GLT_HANDLING', 'CURRENCY', '{"GTE": 0}','Cost per GLT for the handling in the Goods Receipt area of a warehouse or plant; reference country: Germany','Warehouse','5'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GLT booking & document handling [EUR/GR] (DE)', 'BOOKING', 'CURRENCY', '{"GTE": 0}','One time document handling fee for 1 GLT; reference country: Germany','Warehouse','2'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GLT release from storage [EUR/GLT release] (DE)', 'GLT_RELEASE', 'CURRENCY', '{"GTE": 0}','Cost to release one GLT from the storage; reference country: Germany','Warehouse','12'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'KLT release from storage [EUR/KLT release] (DE)', 'KLT_RELEASE', 'CURRENCY', '{"GTE": 0}','Cost to release one KLT from the storage; reference country: Germany','Warehouse','11'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GLT dispatch [EUR/GLT dispatch] (DE)', 'GLT_DISPATCH', 'CURRENCY', '{"GTE": 0}','Cost to disptach one GLT; reference country: Germany','Warehouse','14'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'KLT dispacth [EUR/KLT dispatch] (DE)', 'KLT_DISPATCH', 'CURRENCY', '{"GTE": 0}','Cost to disptach one KLT; reference country: Germany','Warehouse','13'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Repacking KLT, HU <15kg [EUR/HU] (DE)', 'KLT_REPACK_S', 'CURRENCY', '{"GTE": 0}','Cost to repack one KLT (<15 kg) from one-way to returnable empty; reference country: Germany','Warehouse','6'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Repacking KLT, HU >=15kg [EUR/HU] (DE)', 'KLT_REPACK_M', 'CURRENCY', '{"GTE": 0}','Cost to repack one KLT (>=15 kg) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','7'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Repacking GLT, HU <15kg [EUR/HU] (DE)', 'GLT_REPACK_S', 'CURRENCY', '{"GTE": 0}','Cost to repack one GLT (<15 kg) from oneway to returnable empty; reference country: Germany','Warehouse','8'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Repacking GLT, HU 15 - 2000kg [EUR/HU] (DE)', 'GLT_REPACK_M', 'CURRENCY', '{"GTE": 0}','Cost to repack one GLT (>=15 - 2000 kg) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','9'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Repacking GLT, HU >2000kg [EUR/HU] (DE)', 'GLT_REPACK_L', 'INT', '{"GTE": 0}','Cost to repack one GLT (> 2000 kg, e.g. counterweight) from one-way to returnable empty with crane handling; reference country: Germany','Warehouse','10'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'GLT disposal [EUR/GLT] (DE)', 'DISPOSAL', 'INT', '{"GTE": 0}','Cost to dispose one wooden pallet (value valid for all countries)','Warehouse','15'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'Space costs per cbm per night [EUR/cbm] (DE)', 'SPACE_COST', 'CURRENCY', '{"GTE": 0}','Cost per month for a standard storage bin (1,20 x 0,80 x 1,00 m)','Warehouse','1'); +INSERT INTO system_property_type ( name, external_mapping_id, data_type, validation_rule, description, property_group, sequence_number) VALUES ( 'KLT booking & document handling [EUR/GR] (DE)', 'BOOKING_KLT', 'CURRENCY', '{"GTE": 0}','One time document handling fee for 1 KLT; reference country: Germany','Warehouse','3'); diff --git a/src/main/resources/master_data/02-country.sql b/src/main/resources/master_data/02-country.sql index 7460ab9..31b1d91 100644 --- a/src/main/resources/master_data/02-country.sql +++ b/src/main/resources/master_data/02-country.sql @@ -22,12 +22,12 @@ WHERE NOT EXISTS ( -- ============================================================================= INSERT INTO `country_property_type` -(`name`, `external_mapping_id`, `data_type`, `validation_rule`, `is_required`) +(`name`, `external_mapping_id`, `data_type`, `validation_rule`, `is_required`, `description`, `property_group`, `sequence_number`) VALUES - ('Customs Union', 'UNION', 'ENUMERATION', '{ "ENUM" : ["EU", "NONE"]}', FALSE), - ('Safety Stock [working days]', 'SAFETY_STOCK', 'INT', '{"GTE": 0}', FALSE), - ('Air Freight Share [%]', 'AIR_SHARE', 'PERCENTAGE', '{"GTE": 0}', FALSE), - ('Wage Factor [%]', 'WAGE', 'PERCENTAGE', '{"GT": 0}', FALSE); + ('Customs Union', 'UNION', 'ENUMERATION', '{ "ENUM" : ["EU", "NONE"]}', FALSE, 'Custom union description', 'General', 1), + ('Safety Stock [working days]', 'SAFETY_STOCK', 'INT', '{"GTE": 0}', FALSE, 'Safety stock description', 'General', 2), + ('Air Freight Share [%]', 'AIR_SHARE', 'PERCENTAGE', '{"GTE": 0}', FALSE, 'Air freight description', 'General', 3), + ('Wage Factor [%]', 'WAGE', 'PERCENTAGE', '{"GT": 0}', FALSE, 'Wage factor description', 'General', 4); -- ============================================================================= -- 2. INSERT COUNTRIES diff --git a/src/main/resources/master_data/07-packaging.sql b/src/main/resources/master_data/07-packaging.sql index 52e32f3..6bc45f0 100644 --- a/src/main/resources/master_data/07-packaging.sql +++ b/src/main/resources/master_data/07-packaging.sql @@ -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); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 91a4d20..242ef87 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -21,8 +21,8 @@ CREATE TABLE IF NOT EXISTS `system_property_type` `name` VARCHAR(255) NOT NULL, `external_mapping_id` VARCHAR(16), `description` VARCHAR(255) NOT NULL, - `property_group` VARCHAR(32) NOT NULL, - `sort_id` INT, + `property_group` VARCHAR(32) NOT NULL, + `sequence_number` INT NOT NULL, `data_type` VARCHAR(16) NOT NULL, `validation_rule` VARCHAR(64), UNIQUE KEY `idx_external_mapping` (`external_mapping_id`), @@ -52,6 +52,9 @@ CREATE TABLE IF NOT EXISTS `country` `iso_code` CHAR(2) NOT NULL COMMENT 'ISO 3166-1 alpha-2 country code', `region_code` CHAR(5) NOT NULL COMMENT 'Geographic region code (EMEA/LATAM/APAC/NAM)', `name` VARCHAR(255) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `property_group` VARCHAR(32) NOT NULL, + `sequence_number` INT NOT NULL, `is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`id`), UNIQUE KEY `uk_country_iso_code` (`iso_code`), @@ -308,6 +311,9 @@ CREATE TABLE IF NOT EXISTS packaging_property_type `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255) NOT NULL, external_mapping_id VARCHAR(16) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `property_group` VARCHAR(32) NOT NULL, + `sequence_number` INT NOT NULL, `data_type` VARCHAR(16), `validation_rule` VARCHAR(64), `is_required` BOOLEAN NOT NULL DEFAULT FALSE,