diff --git a/LocationDTO.java b/LocationDTO.java deleted file mode 100644 index e13247b..0000000 --- a/LocationDTO.java +++ /dev/null @@ -1,81 +0,0 @@ -package de.avatic.lcc.dto.generic; - -import java.util.Objects; - -/** - * Represents a geographical location with latitude and longitude. - * This immutable DTO (Data Transfer Object) is used to transfer location data across system layers. - */ -public class LocationDTO { - - /** - * The latitude of the location. - * Positive values indicate north and negative values indicate south. - */ - private final double latitude; - - /** - * The longitude of the location. - * Positive values indicate east and negative values indicate west. - */ - private final double longitude; - - /** - * Constructs a new {@code LocationDTO} with the specified latitude and longitude. - * - * @param latitude the latitude of the location, where north is positive and south is negative - * @param longitude the longitude of the location, where east is positive and west is negative - */ - public LocationDTO(double latitude, double longitude) { - this.latitude = latitude; - this.longitude = longitude; - } - - /** - * Default constructor for creating a {@code LocationDTO} at origin (0,0). - * Required for frameworks that use default constructors. - */ - public LocationDTO() { - this(0.0, 0.0); - } - - /** - * Gets the latitude of the location. - * - * @return the latitude, where positive values indicate north and negative values indicate south - */ - public double getLatitude() { - return latitude; - } - - /** - * Gets the longitude of the location. - * - * @return the longitude, where positive values indicate east and negative values indicate west - */ - public double getLongitude() { - return longitude; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LocationDTO that = (LocationDTO) o; - return Double.compare(that.latitude, latitude) == 0 && - Double.compare(that.longitude, longitude) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(latitude, longitude); - } - - @Override - public String toString() { - return "LocationDTO{" + - "latitude=" + latitude + - ", longitude=" + longitude + - '}'; - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8113385..13da216 100644 --- a/pom.xml +++ b/pom.xml @@ -83,10 +83,17 @@ org.apache.poi poi + 5.4.1 org.apache.poi poi-ooxml + 5.4.1 + + + org.dhatim + fastexcel + 0.18.4 diff --git a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java index 9503761..5eca2be 100644 --- a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java +++ b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java @@ -6,7 +6,7 @@ import de.avatic.lcc.dto.bulk.BulkProcessingType; import de.avatic.lcc.dto.bulk.BulkStatus; import de.avatic.lcc.service.bulk.BulkExportService; import de.avatic.lcc.service.bulk.BulkFileProcessingService; -import de.avatic.lcc.service.bulk.TemplateGenerationService; +import de.avatic.lcc.service.bulk.TemplateExportService; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -24,12 +24,12 @@ import org.springframework.web.multipart.MultipartFile; public class BulkOperationController { private final BulkFileProcessingService bulkProcessingService; - private final TemplateGenerationService templateGenerationService; + private final TemplateExportService templateExportService; private final BulkExportService bulkExportService; - public BulkOperationController(BulkFileProcessingService bulkProcessingService, TemplateGenerationService templateGenerationService, BulkExportService bulkExportService) { + public BulkOperationController(BulkFileProcessingService bulkProcessingService, TemplateExportService templateExportService, BulkExportService bulkExportService) { this.bulkProcessingService = bulkProcessingService; - this.templateGenerationService = templateGenerationService; + this.templateExportService = templateExportService; this.bulkExportService = bulkExportService; } @@ -75,7 +75,7 @@ public class BulkOperationController { .ok() .headers(headers) .contentType(MediaType.parseMediaType("application/vnd.ms-excel")) - .body(new InputStreamResource(templateGenerationService.generateTemplate(BulkFileType.valueOf(type.name().toUpperCase())))); + .body(new InputStreamResource(templateExportService.generateTemplate(BulkFileType.valueOf(type.name().toUpperCase())))); } /** diff --git a/src/main/java/de/avatic/lcc/dto/configuration/nodes/view/NodeDetailDTO.java b/src/main/java/de/avatic/lcc/dto/configuration/nodes/view/NodeDetailDTO.java index bb178d7..3e80159 100644 --- a/src/main/java/de/avatic/lcc/dto/configuration/nodes/view/NodeDetailDTO.java +++ b/src/main/java/de/avatic/lcc/dto/configuration/nodes/view/NodeDetailDTO.java @@ -19,7 +19,7 @@ public class NodeDetailDTO { private LocationDTO location; @JsonProperty("is_deprecated") private Boolean isDeprecated; - private Map predecessors; + private List> predecessors; @JsonProperty("outbound_countries") private List outboundCountries; @@ -80,11 +80,11 @@ public class NodeDetailDTO { isDeprecated = deprecated; } - public Map getPredecessors() { + public List> getPredecessors() { return predecessors; } - public void setPredecessors(Map predecessors) { + public void setPredecessors(List> predecessors) { this.predecessors = predecessors; } diff --git a/src/main/java/de/avatic/lcc/model/bulk/BulkFileTypes.java b/src/main/java/de/avatic/lcc/model/bulk/BulkFileTypes.java new file mode 100644 index 0000000..9d00719 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/BulkFileTypes.java @@ -0,0 +1,20 @@ +package de.avatic.lcc.model.bulk; + + +public enum BulkFileTypes { + + CONTAINER_RATE("Container Rate"), COUNTRY_MATRIX("Country Matrix Rate"), MATERIAL("Material"), PACKAGING("Packaging"), NODE("Nodes"); + + + private final String sheetName; + + BulkFileTypes(String sheetName) { + this.sheetName = sheetName; + + } + + public String getSheetName() { + return sheetName; + } + +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java b/src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java new file mode 100644 index 0000000..83dc789 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java @@ -0,0 +1,16 @@ +package de.avatic.lcc.model.bulk; + +public enum ContainerRateHeader implements HeaderProvider { + FROM_NODE("Origin"), TO_NODE("Destination"), CONTAINER_RATE_TYPE("Transport mode"), RATE_FEU("Rate 40 ft GP [EUR]"), + RATE_TEU("Rate 20 ft GP [EUR]"), RATE_HC("Rate 40 ft HC [EUR]"), LEAD_TIME("Lead time [d]"); + + private final String header; + + ContainerRateHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java b/src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java new file mode 100644 index 0000000..83355c0 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java @@ -0,0 +1,5 @@ +package de.avatic.lcc.model.bulk; + +public interface HeaderProvider { + String getHeader(); +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java b/src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java new file mode 100644 index 0000000..e6cc168 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java @@ -0,0 +1,16 @@ +package de.avatic.lcc.model.bulk; + +public enum HiddenCountryHeader implements HeaderProvider{ + ISO_CODE("Iso Code"), NAME("Name"); + + private final String header; + + HiddenCountryHeader(String header) { + this.header = header; + } + + @Override + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java b/src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java new file mode 100644 index 0000000..bfddd0d --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java @@ -0,0 +1,15 @@ +package de.avatic.lcc.model.bulk; + +public enum HiddenNodeHeader implements HeaderProvider { + MAPPING_ID("Mapping Id"), NAME("Name"); + + private final String header; + + HiddenNodeHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/HiddenTableType.java b/src/main/java/de/avatic/lcc/model/bulk/HiddenTableType.java new file mode 100644 index 0000000..22fcc21 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/HiddenTableType.java @@ -0,0 +1,19 @@ +package de.avatic.lcc.model.bulk; + +public enum HiddenTableType { + COUNTRY_HIDDEN_TABLE("ISOCodes"), NODE_HIDDEN_TABLE("Nodes"), ORIGIN_HIDDEN_TABLE("Origins"); + + private final String sheetName; + + HiddenTableType(String sheetName) { + this.sheetName = sheetName; + } + + public String getSheetName() { + return sheetName; + } + + public String getReferenceName() { + return "ref_"+sheetName.toLowerCase(); + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java b/src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java new file mode 100644 index 0000000..ac42b80 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java @@ -0,0 +1,18 @@ +package de.avatic.lcc.model.bulk; + +public enum MaterialHeader implements HeaderProvider { + PART_NUMBER("Part number"), + DESCRIPTION("Material description"), + HS_CODE("HS code"); + + + private final String header; + + MaterialHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java b/src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java new file mode 100644 index 0000000..41e054e --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java @@ -0,0 +1,17 @@ +package de.avatic.lcc.model.bulk; + +public enum MatrixRateHeader implements HeaderProvider { + FROM_COUNTRY("Country of origin (ISO 3166-1)"), + TO_COUNTRY("Country of destination (ISO 3166-1)"), + RATE("Cost per km (incl. floater) [EUR]"); + + private final String header; + + MatrixRateHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java b/src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java new file mode 100644 index 0000000..0f0fc9c --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java @@ -0,0 +1,19 @@ +package de.avatic.lcc.model.bulk; + +public enum NodeHeader implements HeaderProvider { + MAPPING_ID("Mapping ID"), NAME("Name"), ADDRESS("Address"), + COUNTRY("Country (ISO 3166-1)"), GEO_LATITUDE("Latitude"), GEO_LONGITUDE("Longitude"), + IS_ORIGIN("Origin"), IS_INTERMEDIATE("Intermediate"), IS_DESTINATION("Destination"), + OUTBOUND_COUNTRIES("Outbound countries (ISO 3166-1)"), PREDECESSOR_NODES("Predecessor Nodes (Mapping ID)"), + IS_PREDECESSOR_MANDATORY("Predecessors mandatory"); + + private final String header; + + NodeHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java b/src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java new file mode 100644 index 0000000..c9ca86c --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java @@ -0,0 +1,32 @@ +package de.avatic.lcc.model.bulk; + +public enum PackagingHeader implements HeaderProvider { + PART_NUMBER("Part number"), + SUPPLIER("Supplier (Mapping ID)"), + SHU_LENGTH("SHU length"), + SHU_WIDTH("SHU width"), + SHU_HEIGHT("SHU height"), + SHU_DIMENSION_UNIT("SHU Dimension unit"), + SHU_WEIGHT("SHU gross weight"), + SHU_WEIGHT_UNIT("SHU gross weight unit"), + SHU_UNIT_COUNT("Units/SHU [pieces]"), + HU_LENGTH("HU length"), + HU_WIDTH("HU width"), + HU_HEIGHT("HU height"), + HU_DIMENSION_UNIT("HU Dimension unit"), + HU_WEIGHT("HU gross weight"), + HU_WEIGHT_UNIT("HU gross weight unit"), + HU_UNIT_COUNT("Units/HU [pieces]"), + STACKABLE("Stackable"); + + + private final String header; + + PackagingHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/src/main/java/de/avatic/lcc/model/nodes/Node.java b/src/main/java/de/avatic/lcc/model/nodes/Node.java index 8a9d534..122103b 100644 --- a/src/main/java/de/avatic/lcc/model/nodes/Node.java +++ b/src/main/java/de/avatic/lcc/model/nodes/Node.java @@ -27,7 +27,7 @@ public class Node { @NotNull private Boolean predecessorRequired; - private Boolean isSink; + private Boolean isDestination; private Boolean isSource; @@ -49,7 +49,7 @@ public class Node { private Integer countryId; - private Map nodePredecessors; + private List> nodePredecessors; private Collection outboundCountries; @@ -93,12 +93,12 @@ public class Node { this.predecessorRequired = predecessorRequired; } - public Boolean getSink() { - return isSink; + public Boolean getDestination() { + return isDestination; } - public void setSink(Boolean sink) { - isSink = sink; + public void setDestination(Boolean destination) { + isDestination = destination; } public Boolean getSource() { @@ -157,11 +157,11 @@ public class Node { this.countryId = countryId; } - public Map getNodePredecessors() { + public List> getNodePredecessors() { return nodePredecessors; } - public void setNodePredecessors(Map nodePredecessors) { + public void setNodePredecessors(List> nodePredecessors) { this.nodePredecessors = nodePredecessors; } diff --git a/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java index 7800c9e..7d9939b 100644 --- a/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java @@ -15,6 +15,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.List; import java.util.Objects; import java.util.Optional; @@ -28,6 +29,11 @@ public class MaterialRepository { this.jdbcTemplate = jdbcTemplate; } + public List listAllMaterials() { + String query = "SELECT * FROM material ORDER BY normalized_part_number"; + return jdbcTemplate.query(query, new MaterialMapper()); + } + private static class MaterialMapper implements RowMapper { diff --git a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java index 6b92c50..1f2a46f 100644 --- a/src/main/java/de/avatic/lcc/repositories/NodeRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/NodeRepository.java @@ -28,7 +28,7 @@ public class NodeRepository { String query = """ SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source, - node.is_sink as is_sink, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required + node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required FROM node WHERE node.id = ?"""; @@ -37,22 +37,29 @@ public class NodeRepository { return Optional.ofNullable(node); } - private Map getPredecessorsOf(Integer id) { - String query = """ - SELECT node_predecessor.predecessor_node_id, node_predecessor.sequence_number - FROM node_predecessor - WHERE node_predecessor.node_id = ? ORDER BY node_predecessor.sequence_number"""; + private List> getPredecessorsOf(Integer id) { - return jdbcTemplate.query(query, rs -> { - Map predecessors = new HashMap<>(); + String queryChains = """ + SELECT chain.id AS id FROM node_predecessor_chain AS chain WHERE chain.node_id = ? ORDER BY chain.id + """; - while(rs.next()) { - predecessors.put(rs.getInt("sequence_number"), rs.getInt("predecessor_node_id")); - } + return jdbcTemplate.query(queryChains, (chainRs, rowNum) -> { + String query = """ + SELECT entry.node_id AS predecessor , entry.sequence_number as sequence_number + FROM node_predecessor_entry AS entry + WHERE entry.node_predecessor_chain_id = ? ORDER BY entry.sequence_number"""; - return predecessors; + return jdbcTemplate.query(query, rs -> { + Map predecessors = new HashMap<>(); + + while (rs.next()) { + predecessors.put(rs.getInt("sequence_number"), rs.getInt("predecessor_node_id")); + } + + return predecessors; + }, id); }, id); - } + }; private Collection getOutboundCountriesOf(Integer id) { String query = """ @@ -70,12 +77,11 @@ public class NodeRepository { List entities = null; Integer totalCount = 0; - if(filter == null) { + if (filter == null) { entities = jdbcTemplate.query(query, new NodeMapper(), pagination.getLimit(), pagination.getOffset()); totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class); - } - else { - entities= jdbcTemplate.query(query, new NodeMapper(), "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", pagination.getLimit(), pagination.getOffset()); + } else { + entities = jdbcTemplate.query(query, new NodeMapper(), "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", pagination.getLimit(), pagination.getOffset()); totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class, "%" + filter + "%", "%" + filter + "%", "%" + filter + "%"); } @@ -131,24 +137,33 @@ public class NodeRepository { } public List searchNode(String filter, int limit, NodeType nodeType, boolean excludeDeprecated) { - StringBuilder queryBuilder = new StringBuilder().append("SELECT * FROM node WHERE (name LIKE ? OR address LIKE ?)"); + StringBuilder queryBuilder = new StringBuilder().append("SELECT * FROM node WHERE (name LIKE ? OR address LIKE ?)"); - if(nodeType != null) { + if (nodeType != null) { queryBuilder.append(" AND node_type = ?"); } - if(excludeDeprecated) { + if (excludeDeprecated) { queryBuilder.append(" AND is_deprecated = false"); } queryBuilder.append(" LIMIT ?"); - if(nodeType != null) - { - return jdbcTemplate.query(queryBuilder.toString(),new NodeMapper(), "%" + filter + "%", "%" + filter + "%", nodeType.name(), limit); + if (nodeType != null) { + return jdbcTemplate.query(queryBuilder.toString(), new NodeMapper(), "%" + filter + "%", "%" + filter + "%", nodeType.name(), limit); } - return jdbcTemplate.query(queryBuilder.toString(),new NodeMapper(), "%" + filter + "%", "%" + filter + "%", limit); + return jdbcTemplate.query(queryBuilder.toString(), new NodeMapper(), "%" + filter + "%", "%" + filter + "%", limit); + } + + public List listAllNodes(boolean onlySources) { + StringBuilder queryBuilder = new StringBuilder("SELECT * FROM node"); + if (onlySources) { + queryBuilder.append(" WHERE is_source = true"); + } + queryBuilder.append(" ORDER BY id"); + + return jdbcTemplate.query(queryBuilder.toString(), new NodeMapper()); } private class NodeMapper implements RowMapper { @@ -161,7 +176,7 @@ public class NodeRepository { data.setName(rs.getString("name")); data.setAddress(rs.getString("address")); data.setCountryId(rs.getInt("country_id")); - data.setSink(rs.getBoolean("is_sink")); + data.setDestination(rs.getBoolean("is_destination")); data.setSource(rs.getBoolean("is_source")); data.setIntermediate(rs.getBoolean("is_intermediate")); data.setPredecessorRequired(rs.getBoolean("predecessor_required")); diff --git a/src/main/java/de/avatic/lcc/repositories/country/CountryRepository.java b/src/main/java/de/avatic/lcc/repositories/country/CountryRepository.java index 1f460d4..3f9f942 100644 --- a/src/main/java/de/avatic/lcc/repositories/country/CountryRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/country/CountryRepository.java @@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Optional; @Repository @@ -114,6 +115,10 @@ public class CountryRepository { return queryBuilder.toString(); } + public List listAllCountries() { + String query = "SELECT * FROM country ORDER BY iso_code"; + return jdbcTemplate.query(query, new CountryMapper()); + } private static class CountryMapper implements RowMapper { diff --git a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java index 31db385..842be84 100644 --- a/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/packaging/PackagingRepository.java @@ -23,6 +23,11 @@ import java.util.Optional; public class PackagingRepository { + public List listAllPackaging() { + String query = "SELECT * FROM packaging ORDER BY id"; + return jdbcTemplate.query(query, new PackagingMapper()); + } + private static class PackagingMapper implements RowMapper { @Override diff --git a/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java b/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java index ca0bdac..9a569ec 100644 --- a/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; @Repository public class ContainerRateRepository { @@ -33,6 +34,12 @@ public class ContainerRateRepository { return jdbcTemplate.queryForObject(query, new ContainerRateMapper(), id); } + public List listAllRatesByPeriodId(Integer periodId) { + String query = "SELECT * FROM container_rate WHERE validity_period_id = ?"; + + return jdbcTemplate.query(query, new ContainerRateMapper()); + } + private static class ContainerRateMapper implements RowMapper { @Override public ContainerRate mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java b/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java index 3a63aff..b454ca2 100644 --- a/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/rates/MatrixRateRepository.java @@ -1,6 +1,5 @@ package de.avatic.lcc.repositories.rates; -import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO; import de.avatic.lcc.model.rates.MatrixRate; import de.avatic.lcc.repositories.pagination.SearchQueryPagination; import de.avatic.lcc.repositories.pagination.SearchQueryResult; @@ -11,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; /** * Repository for managing {@link MatrixRate} entities, including retrieval and mapping of MatrixRate rows @@ -71,6 +71,11 @@ public class MatrixRateRepository { return jdbcTemplate.queryForObject(query, new MatrixRateMapper(), id); } + public List listAllRatesByPeriodId(Integer periodId) { + String query = "SELECT * FROM country_matrix_rate WHERE validity_period_id = ?"; + return jdbcTemplate.query(query, new MatrixRateMapper()); + } + /** * Maps rows of a {@link ResultSet} to {@link MatrixRate} objects as required by * the {@link JdbcTemplate}. diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java index f3fd3ab..f18733b 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkExportService.java @@ -1,12 +1,48 @@ package de.avatic.lcc.service.bulk; import de.avatic.lcc.dto.bulk.BulkFileType; +import de.avatic.lcc.model.bulk.BulkFileTypes; +import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; +import de.avatic.lcc.service.bulk.mapper.ContainerRateExcelMapper; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.core.io.InputStreamSource; import org.springframework.stereotype.Service; @Service public class BulkExportService { + + private final HeaderCellStyleProvider headerCellStyleProvider; + private final ContainerRateExcelMapper containerRateExcelMapper; + + public BulkExportService(HeaderCellStyleProvider headerCellStyleProvider, ContainerRateExcelMapper containerRateExcelMapper) { + this.headerCellStyleProvider = headerCellStyleProvider; + this.containerRateExcelMapper = containerRateExcelMapper; + } + public InputStreamSource generateExport(BulkFileType bulkFileType) { + Workbook workbook = new XSSFWorkbook(); + Sheet worksheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); + + CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook); + + // Create headers based on the bulk file type + switch (bulkFileType) { + case CONTAINER_RATE: + containerRateExcelMapper.fillSheet(); + break; + case COUNTRY_MATRIX: + break; + case MATERIAL: + break; + case PACKAGING: + break; + case NODE: + break; + } + return null; } diff --git a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java new file mode 100644 index 0000000..d987bba --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java @@ -0,0 +1,110 @@ +package de.avatic.lcc.service.bulk; + +import de.avatic.lcc.dto.bulk.BulkFileType; +import de.avatic.lcc.model.bulk.*; +import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import de.avatic.lcc.service.bulk.mapper.*; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamSource; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +@Service +public class TemplateExportService { + + + private final HeaderGenerator headerGenerator; + private final HeaderCellStyleProvider headerCellStyleProvider; + private final HiddenNodeExcelMapper hiddenNodeExcelMapper; + private final HiddenCountryExcelMapper hiddenCountryExcelMapper; + private final String sheetPassword; + private final ContainerRateExcelMapper containerRateExcelMapper; + private final MatrixRateExcelMapper matrixRateExcelMapper; + private final MaterialExcelMapper materialExcelMapper; + private final PackagingExcelMapper packagingExcelMapper; + private final NodeExcelMapper nodeExcelMapper; + + public TemplateExportService(@Value("${lcc.bulk.sheet_password}") String sheetPassword, HeaderGenerator headerGenerator, HeaderCellStyleProvider headerCellStyleProvider, HiddenNodeExcelMapper hiddenNodeExcelMapper, HiddenCountryExcelMapper hiddenCountryExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MatrixRateExcelMapper matrixRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper) { + this.headerGenerator = headerGenerator; + this.headerCellStyleProvider = headerCellStyleProvider; + this.hiddenNodeExcelMapper = hiddenNodeExcelMapper; + this.hiddenCountryExcelMapper = hiddenCountryExcelMapper; + this.sheetPassword = sheetPassword; + this.containerRateExcelMapper = containerRateExcelMapper; + this.matrixRateExcelMapper = matrixRateExcelMapper; + this.materialExcelMapper = materialExcelMapper; + this.packagingExcelMapper = packagingExcelMapper; + this.nodeExcelMapper = nodeExcelMapper; + } + + public InputStreamSource generateTemplate(BulkFileType bulkFileType) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet(BulkFileTypes.valueOf(bulkFileType.name()).getSheetName()); + + CellStyle style = headerCellStyleProvider.createHeaderCellStyle(workbook); + Sheet hiddenSheet = null; + + // Create headers and constraints based on the bulk file type + switch (bulkFileType) { + case CONTAINER_RATE: + headerGenerator.generateHeader(sheet, ContainerRateHeader.class, style); + containerRateExcelMapper.createConstraints(workbook, sheet); + break; + case COUNTRY_MATRIX: + headerGenerator.generateHeader(sheet, MatrixRateHeader.class, style); + matrixRateExcelMapper.createConstraints(workbook, sheet); + break; + case MATERIAL: + headerGenerator.generateHeader(sheet, MaterialHeader.class, style); + materialExcelMapper.createConstraints(sheet); + break; + case PACKAGING: + headerGenerator.generateHeader(sheet, PackagingHeader.class, style); + packagingExcelMapper.createConstraints(workbook, sheet); + break; + case NODE: + headerGenerator.generateHeader(sheet, NodeHeader.class, style); + nodeExcelMapper.createConstraints(workbook, sheet); + break; + default: + throw new IllegalArgumentException("Unsupported bulk file type: " + bulkFileType); + } + + switch (bulkFileType) { + case CONTAINER_RATE: + case PACKAGING: + var hiddenNodeSheet = workbook.createSheet(HiddenTableType.NODE_HIDDEN_TABLE.getSheetName()); + hiddenNodeExcelMapper.fillSheet(hiddenNodeSheet, style, BulkFileType.PACKAGING.equals(bulkFileType)); + hiddenNodeSheet.protectSheet(sheetPassword); + workbook.createName().setNameName(HiddenTableType.NODE_HIDDEN_TABLE.getSheetName()+"_ref"); + break; + case COUNTRY_MATRIX: + case NODE: + var hiddenCountrySheet = workbook.createSheet(HiddenTableType.COUNTRY_HIDDEN_TABLE.getSheetName()); + hiddenCountryExcelMapper.fillSheet(hiddenCountrySheet, style); + hiddenCountrySheet.protectSheet(sheetPassword); + workbook.setSheetVisibility(workbook.getSheetIndex(hiddenCountrySheet), SheetVisibility.VERY_HIDDEN); + break; + default: + break; + } + + // Return the Excel file as an InputStreamSource + return new ByteArrayResource(outputStream.toByteArray()); + } catch (IOException e) { + throw new RuntimeException("Failed to generate template", e); + } + + } + + +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/service/bulk/TemplateGenerationService.java b/src/main/java/de/avatic/lcc/service/bulk/TemplateGenerationService.java deleted file mode 100644 index 8bd616b..0000000 --- a/src/main/java/de/avatic/lcc/service/bulk/TemplateGenerationService.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.avatic.lcc.service.bulk; - -import de.avatic.lcc.dto.bulk.BulkFileType; -import org.springframework.core.io.InputStreamSource; -import org.springframework.stereotype.Service; - -@Service -public class TemplateGenerationService { - - public InputStreamSource generateTemplate(BulkFileType bulkFileType) { - } -} 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 new file mode 100644 index 0000000..b6d153c --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java @@ -0,0 +1,89 @@ +package de.avatic.lcc.service.bulk.helper; + +import de.avatic.lcc.model.bulk.HiddenTableType; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.springframework.stereotype.Service; + +import java.util.EnumSet; + +@Service +public class ConstraintGenerator { + + + public void createBooleanConstraint(Sheet sheet, Integer columnIdx) { + createConstraint(sheet, columnIdx, new String[]{"true", "false"}); + } + + public > void createEnumConstraint(Sheet sheet, Integer columnIdx, Class values) { + createConstraint(sheet, columnIdx, EnumSet.allOf(values).stream().map(Enum::name).toArray(String[]::new)); + } + + private void createConstraint(Sheet sheet, Integer columnIdx, String[] values) { + var helper = sheet.getDataValidationHelper(); + var constraint = helper.createExplicitListConstraint(values); + + var validation = helper.createValidation(constraint, new CellRangeAddressList(1, SpreadsheetVersion.EXCEL2007.getMaxRows(), columnIdx, columnIdx)); + validation.createErrorBox("Invalid Value", values.length > 8 ? "Please check dropdown for allowed values." : String.format("Allowed values are: %s.", String.join(", ", values))); + validation.setShowErrorBox(true); + sheet.addValidationData(validation); + } + + public void createFormulaListConstraint(Sheet sheet, Integer columnIdx, String namedReference) { + var helper = sheet.getDataValidationHelper(); + var constraint = helper.createFormulaListConstraint(namedReference); + + var validation = helper.createValidation(constraint, new CellRangeAddressList(1, SpreadsheetVersion.EXCEL2007.getMaxRows(), columnIdx, columnIdx)); + validation.createErrorBox("Invalid Value", "Please check dropdown for allowed values."); + validation.setShowErrorBox(true); + sheet.addValidationData(validation); + } + + public void createLengthConstraint(Sheet sheet, Integer columnIdx, Integer min, Integer max) { + var helper = sheet.getDataValidationHelper(); + var constraint = helper.createTextLengthConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max)); + + var validation = helper.createValidation(constraint, new CellRangeAddressList(1, SpreadsheetVersion.EXCEL2007.getMaxRows(), columnIdx, columnIdx)); + validation.createErrorBox("Invalid Value", String.format("Allowed length is between %d and %d characters.", min, max)); + validation.setShowErrorBox(true); + sheet.addValidationData(validation); + } + + public void createDecimalConstraint(Sheet sheet, Integer columnIdx, Number min, Number max) { + var helper = sheet.getDataValidationHelper(); + var constraint = helper.createDecimalConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max)); + + var validation = helper.createValidation(constraint, new CellRangeAddressList(1, SpreadsheetVersion.EXCEL2007.getMaxRows(), columnIdx, columnIdx)); + validation.createErrorBox("Invalid Value", String.format("Allowed value is between %f and %f.", min.floatValue(), max.floatValue())); + validation.setShowErrorBox(true); + sheet.addValidationData(validation); + } + + public void createIntegerConstraint(Sheet sheet, Integer columnIdx, Number min, Number max) { + var helper = sheet.getDataValidationHelper(); + var constraint = helper.createIntegerConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max)); + + var validation = helper.createValidation(constraint, new CellRangeAddressList(1, SpreadsheetVersion.EXCEL2007.getMaxRows(), columnIdx, columnIdx)); + validation.createErrorBox("Invalid Value", String.format("Allowed value is between %f and %f.", min.floatValue(), max.floatValue())); + validation.setShowErrorBox(true); + sheet.addValidationData(validation); + } + + public Name createReference(Workbook workbook, Integer columnIdx, HiddenTableType hiddenTableType ) { + Name namedRange = workbook.createName(); + namedRange.setNameName(hiddenTableType.getReferenceName()); + String reference = hiddenTableType.getSheetName()+ "!$"+toExcelLetter(columnIdx)+"$1:$"+toExcelLetter(columnIdx)+"$" + SpreadsheetVersion.EXCEL2007.getMaxRows(); + namedRange.setRefersToFormula(reference); + return namedRange; + } + + private String toExcelLetter(int columnIdx) { + int digit = columnIdx % 26; + int quotient = columnIdx / 26; + return (quotient==0 ? "" : toExcelLetter(quotient)) + (char) ('A' + digit); + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java new file mode 100644 index 0000000..712e362 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderCellStyleProvider.java @@ -0,0 +1,24 @@ +package de.avatic.lcc.service.bulk.helper; + +import org.apache.poi.ss.usermodel.*; +import org.springframework.stereotype.Service; + +@Service +public class HeaderCellStyleProvider { + + public CellStyle createHeaderCellStyle(Workbook workbook) { + CellStyle headerStyle = workbook.createCellStyle(); + Font headerFont = workbook.createFont(); + headerFont.setBold(true); + headerStyle.setFont(headerFont); + headerStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex()); + headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + headerStyle.setBorderBottom(BorderStyle.THIN); + headerStyle.setBorderTop(BorderStyle.THIN); + headerStyle.setBorderLeft(BorderStyle.THIN); + headerStyle.setBorderRight(BorderStyle.THIN); + headerStyle.setAlignment(HorizontalAlignment.CENTER); + return headerStyle; + } + +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java new file mode 100644 index 0000000..cd1a19a --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java @@ -0,0 +1,40 @@ +package de.avatic.lcc.service.bulk.helper; + +import de.avatic.lcc.model.bulk.HeaderProvider; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.springframework.stereotype.Service; + +import java.util.EnumSet; + +@Service +public class HeaderGenerator { + + + + + public & HeaderProvider> void generateHeader(Sheet worksheet, Class headers, CellStyle style) { + Row row = worksheet.createRow(0); + for (T header : EnumSet.allOf(headers)) { + Cell cell = row.createCell(header.ordinal()); + cell.setCellValue(header.getHeader()); + cell.setCellStyle(style); + worksheet.autoSizeColumn(header.ordinal()); + } + } + + public void generateHeader(Sheet worksheet, String[] headers, CellStyle style) { + Row row = worksheet.createRow(0); + int idx = 0; + for (String header : headers) { + Cell cell = row.createCell(idx); + cell.setCellValue(header); + cell.setCellStyle(style); + worksheet.autoSizeColumn(idx++); + } + } + + +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java new file mode 100644 index 0000000..d922da6 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/ContainerRateExcelMapper.java @@ -0,0 +1,63 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.ContainerRateHeader; +import de.avatic.lcc.model.bulk.HiddenNodeHeader; +import de.avatic.lcc.model.bulk.HiddenTableType; +import de.avatic.lcc.model.rates.ContainerRate; +import de.avatic.lcc.model.rates.ContainerRateType; +import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.repositories.rates.ContainerRateRepository; +import de.avatic.lcc.service.bulk.helper.ConstraintGenerator; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; + +@Service +public class ContainerRateExcelMapper { + + private final HeaderGenerator headerGenerator; + private final ContainerRateRepository containerRateRepository; + private final NodeRepository nodeRepository; + private final ConstraintGenerator constraintGenerator; + + public ContainerRateExcelMapper(HeaderGenerator headerGenerator, ContainerRateRepository containerRateRepository, NodeRepository nodeRepository, ConstraintGenerator constraintGenerator) { + this.headerGenerator = headerGenerator; + this.containerRateRepository = containerRateRepository; + this.nodeRepository = nodeRepository; + this.constraintGenerator = constraintGenerator; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle, Integer periodId) { + headerGenerator.generateHeader(sheet, ContainerRateHeader.class, headerStyle); + containerRateRepository.listAllRatesByPeriodId(periodId).forEach(rate -> mapToRow(rate, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(ContainerRate rate, Row row) { + row.createCell(ContainerRateHeader.FROM_NODE.ordinal()).setCellValue(nodeRepository.getById(rate.getFromNodeId()).orElseThrow().getExternalMappingId()); + row.createCell(ContainerRateHeader.TO_NODE.ordinal()).setCellValue(nodeRepository.getById(rate.getToNodeId()).orElseThrow().getExternalMappingId()); + row.createCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).setCellValue(rate.getType().name()); + row.createCell(ContainerRateHeader.RATE_FEU.ordinal()).setCellValue(rate.getRateFeu().doubleValue()); + row.createCell(ContainerRateHeader.RATE_TEU.ordinal()).setCellValue(rate.getRateTeu().doubleValue()); + row.createCell(ContainerRateHeader.RATE_HC.ordinal()).setCellValue(rate.getRateHc().doubleValue()); + row.createCell(ContainerRateHeader.LEAD_TIME.ordinal()).setCellValue(rate.getLeadTime()); + } + + public void createConstraints(Workbook workbook, Sheet sheet) { + + var namedRange = constraintGenerator.createReference(workbook, HiddenNodeHeader.MAPPING_ID.ordinal(),HiddenTableType.NODE_HIDDEN_TABLE); + + constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.FROM_NODE.ordinal(), namedRange.getNameName()); + constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.TO_NODE.ordinal(), namedRange.getNameName()); + + constraintGenerator.createEnumConstraint(sheet, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), ContainerRateType.class); + constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_FEU.ordinal(), 0.0, 1000000.0); + constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_TEU.ordinal(),0.0, 1000000.0); + constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_HC.ordinal(),0.0, 1000000.0); + + constraintGenerator.createIntegerConstraint(sheet, ContainerRateHeader.LEAD_TIME.ordinal(), 0, 365); + + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java new file mode 100644 index 0000000..280a3a3 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenCountryExcelMapper.java @@ -0,0 +1,34 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.HiddenCountryHeader; +import de.avatic.lcc.model.country.Country; +import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.springframework.stereotype.Service; + +@Service +public class HiddenCountryExcelMapper { + + + private final HeaderGenerator headerGenerator; + private final CountryRepository countryRepository; + + public HiddenCountryExcelMapper(HeaderGenerator headerGenerator, CountryRepository countryRepository) { + this.headerGenerator = headerGenerator; + this.countryRepository = countryRepository; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle) { + headerGenerator.generateHeader(sheet, HiddenCountryHeader.class, headerStyle); + countryRepository.listAllCountries().forEach(c -> mapToRow(c, sheet.createRow(sheet.getLastRowNum() + 1))); + } + + + private void mapToRow(Country entity, Row row) { + row.createCell(HiddenCountryHeader.ISO_CODE.ordinal()).setCellValue(entity.getIsoCode().getCode()); + row.createCell(HiddenCountryHeader.NAME.ordinal()).setCellValue(entity.getIsoCode().getFullName()); + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java new file mode 100644 index 0000000..e2fb238 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/HiddenNodeExcelMapper.java @@ -0,0 +1,33 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.HiddenNodeHeader; +import de.avatic.lcc.model.nodes.Node; +import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.springframework.stereotype.Service; + +@Service +public class HiddenNodeExcelMapper { + + + private final HeaderGenerator headerGenerator; + private final NodeRepository nodeRepository; + + public HiddenNodeExcelMapper(HeaderGenerator headerGenerator, NodeRepository nodeRepository) { + this.headerGenerator = headerGenerator; + this.nodeRepository = nodeRepository; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle, boolean originOnly){ + headerGenerator.generateHeader(sheet, HiddenNodeHeader.class, headerStyle); + nodeRepository.listAllNodes(originOnly).forEach(node -> mapToRow(node, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(Node node, Row row) { + row.createCell(HiddenNodeHeader.MAPPING_ID.ordinal()).setCellValue(node.getExternalMappingId()); + row.createCell(HiddenNodeHeader.NAME.ordinal()).setCellValue(node.getName()); + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java new file mode 100644 index 0000000..baeb1b0 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/MaterialExcelMapper.java @@ -0,0 +1,46 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.HiddenCountryHeader; +import de.avatic.lcc.model.bulk.HiddenTableType; +import de.avatic.lcc.model.bulk.MaterialHeader; +import de.avatic.lcc.model.bulk.MatrixRateHeader; +import de.avatic.lcc.model.materials.Material; +import de.avatic.lcc.repositories.MaterialRepository; +import de.avatic.lcc.service.bulk.helper.ConstraintGenerator; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; + +@Service +public class MaterialExcelMapper { + + private final HeaderGenerator headerGenerator; + private final MaterialRepository materialRepository; + private final ConstraintGenerator constraintGenerator; + + public MaterialExcelMapper(HeaderGenerator headerGenerator, MaterialRepository materialRepository, ConstraintGenerator constraintGenerator) { + this.headerGenerator = headerGenerator; + this.materialRepository = materialRepository; + this.constraintGenerator = constraintGenerator; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle) { + headerGenerator.generateHeader(sheet, MaterialHeader.class, headerStyle); + materialRepository.listAllMaterials().forEach(material -> mapToRow(material, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(Material material, Row row) { + row.createCell(MaterialHeader.PART_NUMBER.ordinal()).setCellValue(material.getPartNumber()); + row.createCell(MaterialHeader.DESCRIPTION.ordinal()).setCellValue(material.getName()); + row.createCell(MaterialHeader.HS_CODE.ordinal()).setCellValue(material.getHsCode()); + } + + public void createConstraints(Sheet sheet) { + constraintGenerator.createLengthConstraint(sheet, MaterialHeader.PART_NUMBER.ordinal(), 0, 12); + constraintGenerator.createLengthConstraint(sheet, MaterialHeader.HS_CODE.ordinal(), 0, 8); + constraintGenerator.createLengthConstraint(sheet, MaterialHeader.DESCRIPTION.ordinal(), 1, 500); + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java new file mode 100644 index 0000000..13ccb55 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/MatrixRateExcelMapper.java @@ -0,0 +1,50 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.*; +import de.avatic.lcc.model.rates.ContainerRateType; +import de.avatic.lcc.model.rates.MatrixRate; +import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.repositories.rates.MatrixRateRepository; +import de.avatic.lcc.service.bulk.helper.ConstraintGenerator; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; + +@Service +public class MatrixRateExcelMapper { + + private final HeaderGenerator headerGenerator; + private final MatrixRateRepository matrixRateRepository; + private final CountryRepository countryRepository; + private final ConstraintGenerator constraintGenerator; + + public MatrixRateExcelMapper(HeaderGenerator headerGenerator, MatrixRateRepository matrixRateRepository, CountryRepository countryRepository, ConstraintGenerator constraintGenerator) { + this.headerGenerator = headerGenerator; + this.matrixRateRepository = matrixRateRepository; + this.countryRepository = countryRepository; + this.constraintGenerator = constraintGenerator; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle, Integer periodId) { + headerGenerator.generateHeader(sheet, MatrixRateHeader.class, headerStyle); + matrixRateRepository.listAllRatesByPeriodId(periodId).forEach(rate -> mapToRow(rate, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(MatrixRate rate, Row row) { + row.createCell(MatrixRateHeader.FROM_COUNTRY.ordinal()).setCellValue(countryRepository.getById(rate.getFromCountry()).orElseThrow().getIsoCode().getCode()); + row.createCell(MatrixRateHeader.TO_COUNTRY.ordinal()).setCellValue(countryRepository.getById(rate.getToCountry()).orElseThrow().getIsoCode().getCode()); + row.createCell(MatrixRateHeader.RATE.ordinal()).setCellValue(rate.getRate().doubleValue()); + } + + public void createConstraints(Workbook workbook, Sheet sheet) { + var namedRange = constraintGenerator.createReference(workbook, HiddenCountryHeader.ISO_CODE.ordinal(), HiddenTableType.COUNTRY_HIDDEN_TABLE); + + constraintGenerator.createFormulaListConstraint(sheet, MatrixRateHeader.FROM_COUNTRY.ordinal(), namedRange.getNameName()); + constraintGenerator.createFormulaListConstraint(sheet, MatrixRateHeader.TO_COUNTRY.ordinal(), namedRange.getNameName()); + constraintGenerator.createDecimalConstraint(sheet, MatrixRateHeader.RATE.ordinal(), 0.0, 1000000.0); + + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java new file mode 100644 index 0000000..87b31d4 --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/NodeExcelMapper.java @@ -0,0 +1,88 @@ +package de.avatic.lcc.service.bulk.mapper; + +import de.avatic.lcc.model.bulk.HiddenCountryHeader; +import de.avatic.lcc.model.bulk.HiddenTableType; +import de.avatic.lcc.model.bulk.MatrixRateHeader; +import de.avatic.lcc.model.bulk.NodeHeader; +import de.avatic.lcc.model.country.Country; +import de.avatic.lcc.model.country.IsoCode; +import de.avatic.lcc.model.nodes.Node; +import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.repositories.country.CountryRepository; +import de.avatic.lcc.service.bulk.helper.ConstraintGenerator; +import de.avatic.lcc.service.bulk.helper.HeaderGenerator; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class NodeExcelMapper { + + + private final HeaderGenerator headerGenerator; + private final NodeRepository nodeRepository; + private final CountryRepository countryRepository; + private final ConstraintGenerator constraintGenerator; + + public NodeExcelMapper(HeaderGenerator headerGenerator, NodeRepository nodeRepository, CountryRepository countryRepository, ConstraintGenerator constraintGenerator) { + this.headerGenerator = headerGenerator; + this.nodeRepository = nodeRepository; + this.countryRepository = countryRepository; + this.constraintGenerator = constraintGenerator; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle) + { + headerGenerator.generateHeader(sheet, NodeHeader.class, headerStyle); + nodeRepository.listAllNodes(false).forEach(node -> mapToRow(node, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(Node node, Row row) { + row.createCell(NodeHeader.MAPPING_ID.ordinal()).setCellValue(node.getExternalMappingId()); + row.createCell(NodeHeader.NAME.ordinal()).setCellValue(node.getName()); + row.createCell(NodeHeader.ADDRESS.ordinal()).setCellValue(node.getAddress()); + row.createCell(NodeHeader.COUNTRY.ordinal()).setCellValue(countryRepository.getById(node.getCountryId()).orElseThrow().getIsoCode().getCode()); + row.createCell(NodeHeader.GEO_LATITUDE.ordinal()).setCellValue(node.getGeoLat().doubleValue()); + row.createCell(NodeHeader.GEO_LONGITUDE.ordinal()).setCellValue(node.getGeoLng().doubleValue()); + row.createCell(NodeHeader.IS_ORIGIN.ordinal()).setCellValue(node.getSource()); + row.createCell(NodeHeader.IS_INTERMEDIATE.ordinal()).setCellValue(node.getIntermediate()); + row.createCell(NodeHeader.IS_DESTINATION.ordinal()).setCellValue(node.getDestination()); + row.createCell(NodeHeader.OUTBOUND_COUNTRIES.ordinal()).setCellValue(mapOutboundCountries(node.getOutboundCountries())); + row.createCell(NodeHeader.PREDECESSOR_NODES.ordinal()).setCellValue(mapChains(node.getNodePredecessors())); + row.createCell(NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()).setCellValue(node.getPredecessorRequired()); + } + + private String mapChains(List> chains) { + return chains.stream().map(this::mapChain).collect(Collectors.joining(", ")); + } + + private String mapChain(Map chain) { + var seq = new ArrayList<>(chain.keySet()); + Collections.sort(seq); + return seq.stream().map(s -> nodeRepository.getById(chain.get(s)).orElseThrow().getExternalMappingId()).collect(Collectors.joining(", ")); + } + + private String mapOutboundCountries(Collection outboundCountryIds) { + return outboundCountryIds.stream().map(countryRepository::getById).filter(Optional::isPresent).map(Optional::get).map(Country::getIsoCode).map(IsoCode::getCode).collect(Collectors.joining(", ")); + } + + public void createConstraints(Workbook workbook, Sheet sheet) { + var namedRange = constraintGenerator.createReference(workbook, HiddenCountryHeader.ISO_CODE.ordinal(), HiddenTableType.COUNTRY_HIDDEN_TABLE); + + constraintGenerator.createFormulaListConstraint(sheet, NodeHeader.COUNTRY.ordinal(), namedRange.getNameName()); + constraintGenerator.createDecimalConstraint(sheet, NodeHeader.GEO_LATITUDE.ordinal(), -90.0, 90.0); + constraintGenerator.createDecimalConstraint(sheet, NodeHeader.GEO_LONGITUDE.ordinal(), -180.0, 180.0); + constraintGenerator.createBooleanConstraint(sheet, NodeHeader.IS_ORIGIN.ordinal()); + constraintGenerator.createBooleanConstraint(sheet, NodeHeader.IS_INTERMEDIATE.ordinal()); + constraintGenerator.createBooleanConstraint(sheet, NodeHeader.IS_DESTINATION.ordinal()); + constraintGenerator.createBooleanConstraint(sheet, NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()); + constraintGenerator.createLengthConstraint(sheet, NodeHeader.ADDRESS.ordinal(), 1, 500); + constraintGenerator.createLengthConstraint(sheet, NodeHeader.NAME.ordinal(), 1, 255); + + } +} diff --git a/src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java b/src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java new file mode 100644 index 0000000..179f9cc --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/bulk/mapper/PackagingExcelMapper.java @@ -0,0 +1,106 @@ +package de.avatic.lcc.service.bulk.mapper; + + +import de.avatic.lcc.model.bulk.*; +import de.avatic.lcc.model.packaging.Packaging; +import de.avatic.lcc.model.packaging.PackagingDimension; +import de.avatic.lcc.model.properties.PropertyType; +import de.avatic.lcc.model.utils.DimensionUnit; +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.bulk.helper.ConstraintGenerator; +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.Optional; +import java.util.function.Function; + +@Service +public class PackagingExcelMapper { + + + private final HeaderGenerator headerGenerator; + private final PackagingRepository packagingRepository; + private final MaterialRepository materialRepository; + private final NodeRepository nodeRepository; + private final PackagingDimensionRepository packagingDimensionRepository; + private final PackagingPropertiesRepository packagingPropertiesRepository; + private final ConstraintGenerator constraintGenerator; + + public PackagingExcelMapper(HeaderGenerator headerGenerator, PackagingRepository packagingRepository, MaterialRepository materialRepository, NodeRepository nodeRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, ConstraintGenerator constraintGenerator) { + this.headerGenerator = headerGenerator; + this.packagingRepository = packagingRepository; + this.materialRepository = materialRepository; + this.nodeRepository = nodeRepository; + this.packagingDimensionRepository = packagingDimensionRepository; + this.packagingPropertiesRepository = packagingPropertiesRepository; + this.constraintGenerator = constraintGenerator; + } + + public void fillSheet(Sheet sheet, CellStyle headerStyle) { + var headers = new ArrayList<>(EnumSet.allOf(PackagingHeader.class).stream().map(PackagingHeader::getHeader).toList()); + headers.addAll(packagingPropertiesRepository.listTypes().stream().map(PropertyType::getExternalMappingId).toList()); + + headerGenerator.generateHeader(sheet, headers.toArray(String[]::new), headerStyle); + packagingRepository.listAllPackaging().forEach(p -> mapToRow(p,headers, sheet.createRow(sheet.getLastRowNum()+1))); + } + + private void mapToRow(Packaging packaging, ArrayList headers, Row row) { + Optional shu = packagingDimensionRepository.getById(packaging.getShuId()); + Optional hu = packagingDimensionRepository.getById(packaging.getShuId()); + + row.createCell(PackagingHeader.PART_NUMBER.ordinal()).setCellValue(materialRepository.getById(packaging.getMaterialId()).orElseThrow().getPartNumber()); + row.createCell(PackagingHeader.SUPPLIER.ordinal()).setCellValue(nodeRepository.getById(packaging.getSupplierId()).orElseThrow().getExternalMappingId()); + + mapToCell(row.createCell(PackagingHeader.SHU_HEIGHT.ordinal()), shu, PackagingDimension::getHeight); + mapToCell(row.createCell(PackagingHeader.SHU_WIDTH.ordinal()), shu, PackagingDimension::getWidth); + mapToCell(row.createCell(PackagingHeader.SHU_LENGTH.ordinal()), shu, PackagingDimension::getLength); + mapToCell(row.createCell(PackagingHeader.SHU_WEIGHT.ordinal()), shu, PackagingDimension::getWeight); + mapToCell(row.createCell(PackagingHeader.SHU_UNIT_COUNT.ordinal()), shu, PackagingDimension::getContentUnitCount); + mapToCell(row.createCell(PackagingHeader.SHU_DIMENSION_UNIT.ordinal()), shu, PackagingDimension::getDimensionUnit); + mapToCell(row.createCell(PackagingHeader.SHU_WEIGHT_UNIT.ordinal()), shu, PackagingDimension::getWeightUnit); + + mapToCell(row.createCell(PackagingHeader.HU_HEIGHT.ordinal()), hu, PackagingDimension::getHeight); + mapToCell(row.createCell(PackagingHeader.HU_WIDTH.ordinal()), hu, PackagingDimension::getWidth); + mapToCell(row.createCell(PackagingHeader.HU_LENGTH.ordinal()), hu, PackagingDimension::getLength); + mapToCell(row.createCell(PackagingHeader.HU_WEIGHT.ordinal()), hu, PackagingDimension::getWeight); + mapToCell(row.createCell(PackagingHeader.HU_UNIT_COUNT.ordinal()), hu, PackagingDimension::getContentUnitCount); + mapToCell(row.createCell(PackagingHeader.HU_DIMENSION_UNIT.ordinal()), hu, PackagingDimension::getDimensionUnit); + mapToCell(row.createCell(PackagingHeader.HU_WEIGHT_UNIT.ordinal()), hu, PackagingDimension::getWeightUnit); + + var properties = packagingPropertiesRepository.getByPackagingId(packaging.getId()); + properties.forEach(p -> row.createCell(headers.indexOf(p.getType().getExternalMappingId())).setCellValue(p.getValue())); + + } + + private void mapToCell(Cell cell, Optional dimension, Function resolver){ + if(dimension.isPresent()) { + if(resolver.apply(dimension.get()) instanceof Integer) + cell.setCellValue((Integer) resolver.apply(dimension.get())); + if(resolver.apply(dimension.get()) instanceof String) + cell.setCellValue((String) resolver.apply(dimension.get())); + if(resolver.apply(dimension.get()) instanceof DimensionUnit) + cell.setCellValue(((DimensionUnit) resolver.apply(dimension.get())).name()); + } + else cell.setBlank(); + } + + public void createConstraints(Workbook workbook, Sheet sheet) { + var namedRange = constraintGenerator.createReference(workbook, HiddenNodeHeader.MAPPING_ID.ordinal(), HiddenTableType.ORIGIN_HIDDEN_TABLE); + + constraintGenerator.createFormulaListConstraint(sheet, PackagingHeader.SUPPLIER.ordinal(), namedRange.getNameName()); + constraintGenerator.createLengthConstraint(sheet, PackagingHeader.PART_NUMBER.ordinal(), 0,12); + + + //TODO: check hu dimensions... + + //todo check propeties? + + } +} diff --git a/src/main/java/de/avatic/lcc/service/configuration/NodeService.java b/src/main/java/de/avatic/lcc/service/configuration/NodeService.java index e723f6d..0ce4af9 100644 --- a/src/main/java/de/avatic/lcc/service/configuration/NodeService.java +++ b/src/main/java/de/avatic/lcc/service/configuration/NodeService.java @@ -42,11 +42,11 @@ public class NodeService { } public SearchQueryResult listNodesView(String filter, int page, int limit) { - return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeDetailTransformer::toNodeViewDTO); + return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeDetailTransformer::toNodeDetailDTO); } public NodeDetailDTO getNode(Integer id) { - return nodeDetailTransformer.toNodeViewDTO(nodeRepository.getById(id).orElseThrow(() -> new NodeNotFoundException(id))); + return nodeDetailTransformer.toNodeDetailDTO(nodeRepository.getById(id).orElseThrow(() -> new NodeNotFoundException(id))); } public Integer deleteNode(Integer id) { @@ -82,7 +82,7 @@ public class NodeService { node.setAddress(dto.getAddress()); node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude())); node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude())); - node.setSink(false); + node.setDestination(false); node.setSource(true); node.setIntermediate(false); node.setDeprecated(false); 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 19b7bd5..bacdc54 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 @@ -25,7 +25,7 @@ public class NodeTransformer { NodeDTO dto = new NodeDTO(); ArrayList types = new ArrayList<>(); - if (entity.getSink()) types.add(NodeType.DESTINATION); + if (entity.getDestination()) types.add(NodeType.DESTINATION); if (entity.getSource()) types.add(NodeType.SOURCE); if (entity.getIntermediate()) types.add(NodeType.INTERMEDIATE); diff --git a/src/main/java/de/avatic/lcc/service/transformer/nodes/NodeDetailTransformer.java b/src/main/java/de/avatic/lcc/service/transformer/nodes/NodeDetailTransformer.java index e1d5728..685d458 100644 --- a/src/main/java/de/avatic/lcc/service/transformer/nodes/NodeDetailTransformer.java +++ b/src/main/java/de/avatic/lcc/service/transformer/nodes/NodeDetailTransformer.java @@ -1,11 +1,11 @@ package de.avatic.lcc.service.transformer.nodes; +import de.avatic.lcc.dto.configuration.nodes.view.NodeDetailDTO; import de.avatic.lcc.dto.generic.NodeDTO; import de.avatic.lcc.dto.generic.NodeType; -import de.avatic.lcc.dto.configuration.nodes.view.NodeDetailDTO; import de.avatic.lcc.model.nodes.Node; -import de.avatic.lcc.repositories.country.CountryRepository; import de.avatic.lcc.repositories.NodeRepository; +import de.avatic.lcc.repositories.country.CountryRepository; import de.avatic.lcc.service.transformer.generic.CountryTransformer; import de.avatic.lcc.service.transformer.generic.LocationTransformer; import de.avatic.lcc.service.transformer.generic.NodeTransformer; @@ -32,13 +32,17 @@ public class NodeDetailTransformer { this.nodeRepository = nodeRepository; } - public NodeDetailDTO toNodeViewDTO(Node node) { + public NodeDetailDTO toNodeDetailDTO(Node node) { NodeDetailDTO dto = new NodeDetailDTO(); - Map predecessors = new HashMap<>(); + var chains = new ArrayList>(); - for (Integer seq : node.getNodePredecessors().keySet()) - predecessors.put(seq, nodeTransformer.toNodeDTO(nodeRepository.getById(node.getNodePredecessors().get(seq)).orElseThrow())); + for (var chain : node.getNodePredecessors()) { + Map predecessorChain = new HashMap<>(); + for (Integer seq : chain.keySet()) + predecessorChain.put(seq, nodeTransformer.toNodeDTO(nodeRepository.getById(chain.get(seq)).orElseThrow())); + chains.add(predecessorChain); + } dto.setId(node.getId()); dto.setDeprecated(node.getDeprecated()); @@ -47,7 +51,7 @@ public class NodeDetailTransformer { dto.setAddress(node.getAddress()); dto.setLocation(locationTransformer.toLocationDTO(node)); dto.setTypes(toNodeTypeArrayList(node)); - dto.setPredecessors(predecessors); + dto.setPredecessors(chains); dto.setOutboundCountries(node.getOutboundCountries().stream().map(id -> countryTransformer.toCountryDTO(countryRepository.getById(id).orElseThrow())).toList()); return dto; @@ -55,7 +59,7 @@ public class NodeDetailTransformer { private ArrayList toNodeTypeArrayList(Node entity) { ArrayList types = new ArrayList<>(); - if (entity.getSink()) types.add(NodeType.DESTINATION); + if (entity.getDestination()) types.add(NodeType.DESTINATION); if (entity.getSource()) types.add(NodeType.SOURCE); if (entity.getIntermediate()) types.add(NodeType.INTERMEDIATE); return types; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bf961ab..a1b02a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,3 +6,4 @@ spring.datasource.password=${DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.sql.init.mode=always #spring.profiles.active=setup +lcc.bulk.sheet_password=secretSheet?! diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 6be5cef..cdf73d0 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -42,10 +42,10 @@ CREATE TABLE IF NOT EXISTS `system_property` -- country CREATE TABLE IF NOT EXISTS `country` ( - `id` INT NOT NULL AUTO_INCREMENT, - `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)', - `is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE, + `id` INT NOT NULL AUTO_INCREMENT, + `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)', + `is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`id`), UNIQUE KEY `uk_country_iso_code` (`iso_code`), CONSTRAINT `chk_country_region_code` @@ -138,7 +138,7 @@ CREATE TABLE IF NOT EXISTS node address VARCHAR(500) NOT NULL, external_mapping_id VARCHAR(32), predecessor_required BOOLEAN NOT NULL DEFAULT FALSE, - is_destination BOOLEAN, + is_destination BOOLEAN, is_source BOOLEAN, is_intermediate BOOLEAN, geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90), @@ -149,16 +149,24 @@ CREATE TABLE IF NOT EXISTS node INDEX idx_country_id (country_id) ) COMMENT ''; -CREATE TABLE IF NOT EXISTS node_predecessor +CREATE TABLE IF NOT EXISTS node_predecessor_chain ( - id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, - node_id INT NOT NULL, - predecessor_node_id INT NOT NULL, - sequence_number INT NOT NULL CHECK (sequence_number > 0), + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + node_id INT NOT NULL, + FOREIGN KEY (node_id) REFERENCES node (id) + +); + +CREATE TABLE IF NOT EXISTS node_predecessor_entry +( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + node_id INT NOT NULL, + node_predecessor_chain_id INT NOT NULL, + sequence_number INT NOT NULL CHECK (sequence_number > 0), FOREIGN KEY (node_id) REFERENCES node (id), - FOREIGN KEY (predecessor_node_id) REFERENCES node (id), - UNIQUE KEY uk_node_predecessor (node_id, predecessor_node_id, sequence_number), - INDEX idx_predecessor_node (predecessor_node_id), + FOREIGN KEY (node_predecessor_chain_id) REFERENCES node_predecessor_chain (id), + UNIQUE KEY uk_node_predecessor (node_predecessor_chain_id, sequence_number), + INDEX idx_node_predecessor (node_predecessor_chain_id), INDEX idx_sequence (sequence_number) ); @@ -210,9 +218,9 @@ CREATE TABLE IF NOT EXISTS container_rate from_node_id INT NOT NULL, to_node_id INT NOT NULL, container_rate_type CHAR(8) CHECK (container_rate_type IN ('RAIL', 'SEA', 'POST-RUN', 'ROAD')), - rate_teu DECIMAL(15, 2) NOT NULL COMMENT 'rate for 20ft container in EUR', - rate_feu DECIMAL(15, 2) NOT NULL COMMENT 'rate for 40ft container in EUR', - rate_hc DECIMAL(15, 2) NOT NULL COMMENT 'rate for 40ft HQ container in EUR', + rate_teu DECIMAL(15, 2) NOT NULL COMMENT 'rate for 20ft container in EUR', + rate_feu DECIMAL(15, 2) NOT NULL COMMENT 'rate for 40ft container in EUR', + rate_hc DECIMAL(15, 2) NOT NULL COMMENT 'rate for 40ft HQ container in EUR', lead_time INT UNSIGNED NOT NULL COMMENT 'lead time in days', validity_period_id INT NOT NULL, FOREIGN KEY (from_node_id) REFERENCES node (id), @@ -290,12 +298,12 @@ CREATE TABLE IF NOT EXISTS packaging 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, - `data_type` VARCHAR(16), - `validation_rule` VARCHAR(64), - `is_required` BOOLEAN NOT NULL DEFAULT FALSE, + `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + external_mapping_id VARCHAR(16) NOT NULL, + `data_type` VARCHAR(16), + `validation_rule` VARCHAR(64), + `is_required` BOOLEAN NOT NULL DEFAULT FALSE, UNIQUE KEY idx_packaging_property_type (`external_mapping_id`), CONSTRAINT `chk_packaging_data_type_values` CHECK (`data_type` IN ('INT', 'PERCENTAGE', 'BOOLEAN', 'CURRENCY', 'ENUMERATION', @@ -361,13 +369,13 @@ CREATE TABLE IF NOT EXISTS premiss CREATE TABLE IF NOT EXISTS premiss_destination ( - id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, - premiss_id INT NOT NULL, - annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces', - destination_node_id INT NOT NULL, - repacking_cost DECIMAL(15, 2) DEFAULT NULL, - handling_cost DECIMAL(15, 2) DEFAULT NULL, - disposal_cost DECIMAL(15, 2) DEFAULT NULL, + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + premiss_id INT NOT NULL, + annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces', + destination_node_id INT NOT NULL, + repacking_cost DECIMAL(15, 2) DEFAULT NULL, + handling_cost DECIMAL(15, 2) DEFAULT NULL, + disposal_cost DECIMAL(15, 2) DEFAULT NULL, FOREIGN KEY (premiss_id) REFERENCES premiss (id), FOREIGN KEY (destination_node_id) REFERENCES node (id), INDEX idx_destination_node_id (destination_node_id), @@ -376,11 +384,11 @@ CREATE TABLE IF NOT EXISTS premiss_destination CREATE TABLE IF NOT EXISTS premiss_route ( - id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, premiss_destination_id INT NOT NULL, - is_fastest BOOLEAN DEFAULT FALSE, - is_cheapest BOOLEAN DEFAULT FALSE, - is_selected BOOLEAN DEFAULT FALSE, + is_fastest BOOLEAN DEFAULT FALSE, + is_cheapest BOOLEAN DEFAULT FALSE, + is_selected BOOLEAN DEFAULT FALSE, FOREIGN KEY (premiss_destination_id) REFERENCES premiss_destination (id) ); @@ -391,7 +399,7 @@ CREATE TABLE IF NOT EXISTS premiss_route_node user_node_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, address VARCHAR(500), - is_destination BOOLEAN DEFAULT FALSE, + is_destination BOOLEAN DEFAULT FALSE, is_intermediate BOOLEAN DEFAULT FALSE, is_source BOOLEAN DEFAULT FALSE, geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90), @@ -554,7 +562,7 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, calculation_job_id INT NOT NULL, - premiss_destination_id INT NOT NULL, + premiss_destination_id INT NOT NULL, safety_stock INT UNSIGNED COMMENT 'safety stock in single pieces ?!?!', shipping_frequency INT UNSIGNED COMMENT 'annual shipping frequency', total_cost DECIMAL(15, 2) COMMENT 'aka MEK_B in EUR',