Remove unused service classes and refactor mappings

Deleted `DestinationService` and `ValidityPeriodService` as they were obsolete. Refactored several classes, including `PremisesService` and `MatrixRateExcelMapper`, to improve package alignment and streamline dependencies. Enhanced `PremiseRepository` with cleaner query construction, better parameter handling, and support for resolving chains of predecessors in `NodeRepository`.
This commit is contained in:
Jan 2025-04-20 21:22:09 +02:00
parent 11bdf3b948
commit e4ab851d7f
47 changed files with 1319 additions and 355 deletions

View file

@ -9,10 +9,12 @@ import de.avatic.lcc.dto.calculation.DestinationDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.*;
import de.avatic.lcc.dto.calculation.PremiseDTO;
import de.avatic.lcc.service.calculation.DestinationService;
import de.avatic.lcc.service.access.DestinationService;
import de.avatic.lcc.service.calculation.ChangeMaterialService;
import de.avatic.lcc.service.calculation.ChangeSupplierService;
import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService;
import de.avatic.lcc.service.calculation.PremiseCreationService;
import de.avatic.lcc.service.calculation.PremisesService;
import de.avatic.lcc.service.access.PremisesService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -27,12 +29,16 @@ public class CalculationController {
private final PremisesService premisesServices;
private final PremiseCreationService premiseCreationService;
private final DestinationService destinationService;
private final ChangeSupplierService changeSupplierService;
private final ChangeMaterialService changeMaterialService;
public CalculationController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService) {
public CalculationController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService, ChangeSupplierService changeSupplierService, ChangeMaterialService changeMaterialService) {
this.premiseSearchStringAnalyzerService = premiseSearchStringAnalyzerService;
this.premisesServices = premisesServices;
this.premiseCreationService = premiseCreationService;
this.destinationService = destinationService;
this.changeSupplierService = changeSupplierService;
this.changeMaterialService = changeMaterialService;
}
@GetMapping("/view")
@ -68,22 +74,22 @@ public class CalculationController {
}
@PutMapping("/packaging")
public ResponseEntity<HashMap<String, String>> updatePackaging(PackagingUpdateDTO packagingDTO) {
public ResponseEntity<HashMap<String, String>> updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) {
return ResponseEntity.ok(premisesServices.updatePackaging(packagingDTO));
}
@PutMapping("/material")
public ResponseEntity<HashMap<String, String>> updateMaterial(MaterialUpdateDTO materialUpdateDTO) {
public ResponseEntity<HashMap<String, String>> updateMaterial(@RequestBody MaterialUpdateDTO materialUpdateDTO) {
return ResponseEntity.ok(premisesServices.updateMaterial(materialUpdateDTO));
}
@PutMapping("/price")
public ResponseEntity<HashMap<String, String>> updatePrice(PriceUpdateDTO priceUpdateDTO) {
public ResponseEntity<HashMap<String, String>> updatePrice(@RequestBody PriceUpdateDTO priceUpdateDTO) {
return ResponseEntity.ok(premisesServices.updatePrice(priceUpdateDTO));
}
@PostMapping("/destination")
public ResponseEntity<HashMap<Integer, DestinationDTO>> createDestination(DestinationCreateDTO destinationCreateDTO) {
public ResponseEntity<HashMap<Integer, DestinationDTO>> createDestination(@RequestBody DestinationCreateDTO destinationCreateDTO) {
return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO));
}
@ -100,18 +106,18 @@ public class CalculationController {
@DeleteMapping("/destination({id}")
public ResponseEntity<Void> deleteDestination(@PathVariable Integer id) {
destinationService.deleteDestination(id);
destinationService.deleteDestinationById(id, false);
return ResponseEntity.ok().build();
}
@PutMapping("/supplier")
public ResponseEntity<List<PremiseDetailDTO>> setSupplier(SetDataDTO setSupplierDTO) {
return ResponseEntity.ok(premisesServices.setSupplier(setSupplierDTO));
public ResponseEntity<List<PremiseDetailDTO>> setSupplier(@RequestBody SetDataDTO setSupplierDTO) {
return ResponseEntity.ok(changeSupplierService.setSupplier(setSupplierDTO));
}
@PutMapping("/material")
public ResponseEntity<List<PremiseDetailDTO>> setMaterial(SetDataDTO setMaterialDTO) {
return ResponseEntity.ok(premisesServices.setMaterial(setMaterialDTO));
return ResponseEntity.ok(changeMaterialService.setMaterial(setMaterialDTO));
}
}

View file

@ -3,7 +3,7 @@ package de.avatic.lcc.controller.configuration;
import de.avatic.lcc.dto.configuration.countries.view.CountryDetailDTO;
import de.avatic.lcc.dto.generic.CountryDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.service.configuration.CountryService;
import de.avatic.lcc.service.access.CountryService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View file

@ -1,11 +1,9 @@
package de.avatic.lcc.controller.configuration;
import de.avatic.lcc.dto.configuration.material.view.MaterialDetailDTO;
import de.avatic.lcc.dto.configuration.material.update.MaterialUpdateDTO;
import de.avatic.lcc.dto.generic.MaterialDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.service.configuration.MaterialService;
import de.avatic.lcc.util.Check;
import de.avatic.lcc.service.access.MaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View file

@ -1,16 +1,15 @@
package de.avatic.lcc.controller.configuration;
import de.avatic.lcc.dto.configuration.nodes.userNodes.LocateNodeDTO;
import de.avatic.lcc.dto.generic.LocationDTO;
import de.avatic.lcc.dto.generic.NodeDTO;
import de.avatic.lcc.dto.configuration.nodes.view.NodeDetailDTO;
import de.avatic.lcc.dto.configuration.nodes.update.NodeUpdateDTO;
import de.avatic.lcc.dto.generic.NodeType;
import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.GeoApiService;
import de.avatic.lcc.service.configuration.NodeService;
import de.avatic.lcc.service.access.NodeService;
import de.avatic.lcc.service.access.UserNodeService;
import de.avatic.lcc.util.Check;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -23,11 +22,12 @@ public class NodeController {
private final NodeService nodeService;
private final GeoApiService geoApiService;
private final UserNodeService userNodeService;
public NodeController(NodeService nodeService, GeoApiService geoApiService) {
public NodeController(NodeService nodeService, GeoApiService geoApiService, UserNodeService userNodeService) {
this.nodeService = nodeService;
this.geoApiService = geoApiService;
this.userNodeService = userNodeService;
}
@GetMapping("/")
@ -71,7 +71,7 @@ public class NodeController {
@PutMapping("/")
public ResponseEntity<Void> addUserNode(@RequestParam AddUserNodeDTO node) {
nodeService.addUserNode(node);
userNodeService.addUserNode(node);
return ResponseEntity.ok().build();
}
}

View file

@ -3,8 +3,8 @@ package de.avatic.lcc.controller.configuration;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.model.country.IsoCode;
import de.avatic.lcc.service.configuration.CountryService;
import de.avatic.lcc.service.configuration.PropertyService;
import de.avatic.lcc.service.access.CountryService;
import de.avatic.lcc.service.access.PropertyService;
import de.avatic.lcc.service.configuration.PropertyApprovalService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View file

@ -4,10 +4,10 @@ import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO;
import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.service.configuration.ContainerRateService;
import de.avatic.lcc.service.configuration.MatrixRateService;
import de.avatic.lcc.service.access.ContainerRateService;
import de.avatic.lcc.service.access.MatrixRateService;
import de.avatic.lcc.service.configuration.RateApprovalService;
import de.avatic.lcc.service.configuration.ValidityPeriodService;
import de.avatic.lcc.service.access.ValidityPeriodService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View file

@ -1,6 +1,6 @@
package de.avatic.lcc.controller.custom;
import de.avatic.lcc.service.calculation.CustomApiService;
import de.avatic.lcc.service.CustomApiService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

View file

@ -15,9 +15,19 @@ public class SetDataDTO {
@JsonProperty("supplier_node_id", required = false)
Integer supplierNodeId;
@JsonProperty("is_user_supplier_node")
boolean isUserSupplierNode;
@JsonProperty(value = "material_id", required = false)
Integer materialId;
public boolean isUserSupplierNode() {
return isUserSupplierNode;
}
public void setUserSupplierNode(boolean userSupplierNode) {
isUserSupplierNode = userSupplierNode;
}
public Integer getMaterialId() {
return materialId;

View file

@ -1,4 +1,47 @@
package de.avatic.lcc.dto.calculation.edit.destination;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DestinationUpdateDTO {
@JsonProperty("repacking_cost")
private Number repackingCost;
@JsonProperty("handling_cost")
private Number handlingCost;
@JsonProperty("disposal_cost")
private Number disposalCost;
@JsonProperty("route_selected_id")
private Integer routeSelectedId;
public Number getRepackingCost() {
return repackingCost;
}
public void setRepackingCost(Number repackingCost) {
this.repackingCost = repackingCost;
}
public Number getHandlingCost() {
return handlingCost;
}
public void setHandlingCost(Number handlingCost) {
this.handlingCost = handlingCost;
}
public Number getDisposalCost() {
return disposalCost;
}
public void setDisposalCost(Number disposalCost) {
this.disposalCost = disposalCost;
}
public Integer getRouteSelectedId() {
return routeSelectedId;
}
public void setRouteSelectedId(Integer routeSelectedId) {
this.routeSelectedId = routeSelectedId;
}
}

View file

@ -0,0 +1,5 @@
package de.avatic.lcc.model.properties;
public enum PackagingPropertyMappingId {
STACKABLE, MIXABLE, RUST_PREVENTION
}

View file

@ -0,0 +1,87 @@
package de.avatic.lcc.model.properties;
public enum SystemPropertyMappingId {
WORKDAYS("Annual working days", "210"),
INTEREST_RATE("Interest rate inventory [%]", "12%"),
FCA_FEE("FCA fee [%]", "0,20%"),
TARIFF_RATE("Default customs duty [%]", "3,0%"),
CUSTOM_FEE("Customs clearance fee per import & HS code", "35,00 €"),
REPORTING("Standard-Report Format", "MEK_B"),
FEU("40'", "Yes"),
TEU("20'", "Yes"),
FEU_HQ("40'HC", "Yes"),
TEU_HQ("20'HC", "No"),
CONTAINER_UTIL("Container utilization in mixed containers [%]", "70%"),
TRUCK_UTIL("Truck utilization road transport EMEA [%]", "70%"),
VALID_DAYS("Max validity of container freight rates [days]", "60"),
RADIUS_REGION("Metropolition region size (diameter) [km]", "20"),
FREQ_MIN("Min delivery frequency / year for containtrer transports", "3"),
FREQ_MAX("Max delivery frequency / year for containtrer transports", "50"),
TEU_LOAD("Max load of 20' container [kg]", "20000"),
FEU_LOAD("Max load of 40' container [kg]", "21000"),
AIR_PRECARRIAGE("Pre-carriage [EUR/kg]", "0,10 €"),
AIR_HANDLING("Pre-carriage handling [EUR]", "80,00 €"),
AIR_MAINCARRIAGE("Main carriage [EUR/kg]", "3,50 €"),
AIR_HANDOVER_FEE("Hand over fee [EUR]", "35,00 €"),
AIR_CUSTOM_FEE("Customs clearance fee [EUR]", "45,00 €"),
AIR_ONCARRIAGE("On-carriage [EUR/kg]", "0,20 €"),
AIR_TERMINAL_FEE("Terminal handling fee [EUR/kg]", "0,20 €"),
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 €"),
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 €"),
KLT_DISPATCH("KLT dispacth [EUR/KLT dispatch]", "0,33 €"),
KLT_REPACK_S("Repacking KLT, HU <15kg [EUR/HU]", "2,08 €"),
KLT_REPACK_M("Repacking KLT, HU >=15kg [EUR/HU]", "3,02 €"),
GLT_REPACK_S("Repacking GLT, HU <15kg [EUR/HU]", "3,02 €"),
GLT_REPACK_M("Repacking GLT, HU 15 - 2000kg [EUR/HU]", "7,76 €"),
GLT_REPACK_L("Repacking GLT, HU > 2000kg [EUR/HU]", "14,00 €"),
DISPOSAL("GLT disposal [EUR/GLT]", "6,00 €"),
SPACE_COST("Space costs / m3 per night [EUR/m3]", "0,26 €"),
UNION("Customs Union", ""),
SAFTY_STOCK("Safety Stock [working days]", "30"),
AIR_SHARE("Air freight share [%]", "2"),
WAGE("Wage factor [%]", "100%");
private final String description;
private final String defaultValue;
SystemPropertyMappingId(String description, String defaultValue) {
this.description = description;
this.defaultValue = defaultValue;
}
public String getDescription() {
return description;
}
public String getDefaultValue() {
return defaultValue;
}
public Integer getDefaultAsInteger() {
try {
return Integer.parseInt(defaultValue);
} catch (NumberFormatException e) {
return null;
}
}
}

View file

@ -59,7 +59,9 @@ public class NodeRepository {
return predecessors;
}, id);
}, id);
};
}
;
private Collection<Integer> getOutboundCountriesOf(Integer id) {
String query = """
@ -107,7 +109,7 @@ public class NodeRepository {
private String buildQuery(String filter, Boolean excludeDeprecated, SearchQueryPagination searchQueryPagination) {
StringBuilder queryBuilder = new StringBuilder("""
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,
country.iso_code AS country_iso_code, country.region_code AS country_region_code, country.name AS country_name, country.is_deprecated AS country_is_deprecated
FROM node
LEFT JOIN country ON country.id = node.country_id
@ -182,6 +184,91 @@ public class NodeRepository {
return Optional.ofNullable(node);
}
/**
* Resolves chains of predecessors for a specified destination node by its ID.
* If the destination node does not require predecessors, an empty list is returned.
* Otherwise, it constructs a list of chains, where each chain represents a sequence of predecessor nodes.
*
* @param destinationId The ID of the destination node whose predecessor chains need to be resolved.
* Must not be null and must correspond to an existing node.
* @return A list of chains, where each chain is a list of nodes.
* Each list represents a sequence of predecessor nodes for the given destination node.
* If the destination node does not require predecessors, a list containing an empty list is returned.
* @throws RuntimeException If a predecessor node is not found for a given sequence number in the chain.
*/
@Transactional
public List<List<Node>> resolveChainsById(Integer destinationId) {
List<List<Node>> resolvedChains = new ArrayList<>();
Node destination = getById(destinationId).orElseThrow();
if (!destination.getPredecessorRequired())
resolvedChains.add(Collections.emptyList());
List<Map<Integer, Integer>> chains = destination.getNodePredecessors();
chains.forEach(chain -> {
var currentChain = new ArrayList<Node>();
resolvedChains.add(currentChain);
chain.keySet().forEach(sequenceNumber -> {
var predecessor = getById(chain.get(sequenceNumber));
if (predecessor.isEmpty()) {
throw new RuntimeException("Predecessor not found for chain " + chain + " and sequence number " + sequenceNumber);
}
currentChain.add(sequenceNumber, predecessor.get());
});
});
return resolvedChains;
}
public List<Node> getByDistance(Node node, Integer regionRadius) {
String query = """
SELECT * FROM node
WHERE is_deprecated = FALSE AND
(
6371 * acos(
cos(radians(?)) *
cos(radians(geo_lat)) *
cos(radians(geo_lng) - radians(?)) +
sin(radians(?)) *
sin(radians(geo_lat))
)
) <= ?
""";
return jdbcTemplate.query(query, new NodeMapper(), node.getGeoLat(), node.getGeoLng(), node.getGeoLat());
}
/**
* Retrieves a list of nodes that are outbound from a given country.
* The method considers nodes that are either explicitly mapped to the given country
* or are intermediate nodes within the same country.
*
* @param countryId The ID of the country for which outbound nodes are to be retrieved.
* Must not be null.
* @return A list of nodes that are outbound for the specified country.
* Returns an empty list if no outbound nodes are found.
*/
public List<Node> getAllOutboundFor(Integer countryId) {
String query = """
SELECT node.id AS id, node.name AS name, node.address as address, node.is_source as is_source,
node.is_destination as is_destination, node.is_intermediate as is_intermediate, node.country_id as country_id, node.predecessor_required as predecessor_required
FROM node
LEFT JOIN outbound_country_mapping ON outbound_country_mapping.node_id = node.id
WHERE node.is_deprecated = FALSE AND (outbound_country_mapping.country_id = ? OR (node.is_intermediate = TRUE AND node.country_id = ?))
""";
return jdbcTemplate.query(query, new NodeMapper(), countryId);
}
private class NodeMapper implements RowMapper<Node> {
@Override

View file

@ -1,12 +1,17 @@
package de.avatic.lcc.repositories.packaging;
import com.azure.spring.cloud.core.implementation.properties.PropertyMapper;
import de.avatic.lcc.model.properties.PackagingProperty;
import de.avatic.lcc.model.properties.PropertyDataType;
import de.avatic.lcc.model.properties.PropertyType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
@Repository
public class PackagingPropertiesRepository {
@ -25,8 +30,30 @@ public class PackagingPropertiesRepository {
type.external_mapping_id AS type_external_mapping_id, type.validation_rule AS type_validation_rule
FROM packaging_property property LEFT JOIN packaging_property_type type ON property.packaging_property_type_id = type.id WHERE packaging_id = ?""";
return jdbcTemplate.query(query, (rs, rowNum) -> {
return jdbcTemplate.query(query, new PropertyMapper(), id);
}
public Optional<PackagingProperty> getByPackagingIdAndType(Integer id, String type) {
String query = """
SELECT property.id as id, property.property_value AS value, property.packaging_id as packaging_id,
type.name AS type_name, type.data_type AS type_data_type, type.is_required AS type_is_required,
type.external_mapping_id AS type_external_mapping_id, type.validation_rule AS type_validation_rule
FROM packaging_property property LEFT JOIN packaging_property_type type ON property.packaging_property_type_id = type.id WHERE packaging_id = ? AND type.external_mapping_id = ?""";
var property = jdbcTemplate.query(query, new PropertyMapper(), id, type);
if(property.isEmpty())
return Optional.empty();
return Optional.of(property.getFirst());
}
private static class PropertyMapper implements RowMapper<PackagingProperty> {
@Override
public PackagingProperty mapRow(ResultSet rs, int rowNum) throws SQLException {
PackagingProperty property = new PackagingProperty();
PropertyType type = new PropertyType();
@ -43,10 +70,8 @@ public class PackagingPropertiesRepository {
property.setValue(rs.getString("value"));
return property;
}, id);
}
}
public List<PropertyType> listTypes() {

View file

@ -180,4 +180,13 @@ public class PackagingRepository {
return jdbcTemplate.query(query, new PackagingMapper(), materialId);
}
public List<Packaging> getByMaterialIdAndSupplierId(Integer materialId, Integer supplierId) {
String query = """
SELECT id, supplier_node_id, material_id, hu_dimension_id, shu_dimension_id, is_deprecated
FROM packaging
WHERE packaging.material_id = ? AND packaging.supplier_node_id = ?""";
return jdbcTemplate.query(query, new PackagingMapper(), materialId, supplierId);
}
}

View file

@ -3,6 +3,7 @@ package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.premises.route.Destination;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
@ -37,6 +38,39 @@ public class DestinationRepository {
return jdbcTemplate.query(query, new DestinationMapper(), id);
}
public void update(Integer id, Integer userId, Number repackingCost, Number disposalCost, Number handlingCost) {
String query = "UPDATE premise_destination SET repacking_cost = ?, disposal_cost = ?, handling_cost = ? WHERE id = ? AND user_id = ?";
//TODO
}
public void deleteById(Integer id) {
if (id == null) {
return;
}
String sql = "DELETE FROM premise_destination WHERE id = ?";
jdbcTemplate.update(sql, id);
}
public Optional<Integer> getOwnerIdById(Integer id) {
String query = """
SELECT p.user_id AS user_id
FROM premise_destination pd
JOIN premise p ON pd.premise_id = p.id
WHERE pd.id = ?""";
List<Integer> userId = jdbcTemplate.query(query, (rs, rowNum) -> {
return rs.getInt("userId");
}, id);
if (userId.isEmpty())
return Optional.empty();
else
return Optional.of(userId.getFirst());
}
private static class DestinationMapper implements RowMapper<Destination> {
@Override
public Destination mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -11,6 +11,8 @@ import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@ -25,89 +27,87 @@ import java.util.Optional;
public class PremiseRepository {
private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public PremiseRepository(JdbcTemplate jdbcTemplate) {
public PremiseRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
public SearchQueryResult<PremiseListEntry> listPremises(String filter, SearchQueryPagination pagination, Integer userId, Boolean deleted, Boolean archived, Boolean done) {
String query = buildQuery(filter, deleted, archived, done);
String countQuery = buildCountQuery(filter, deleted, archived, done);
public SearchQueryResult<PremiseListEntry> listPremises(String filter, SearchQueryPagination pagination,
Integer userId, Boolean deleted, Boolean archived, Boolean done) {
List<PremiseListEntry> entities = null;
Integer totalCount = 0;
QueryBuilder queryBuilder = new QueryBuilder()
.withFilter(filter)
.withDeleted(deleted)
.withArchived(archived)
.withDone(done);
if (filter == null || filter.isBlank()) {
entities = jdbcTemplate.query(query, new PremiseListEntryMapper(), userId, pagination.getLimit(), pagination.getOffset());
totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class, userId);
String query = queryBuilder.buildSelectQuery();
String countQuery = queryBuilder.buildCountQuery();
List<PremiseListEntry> entities;
Integer totalCount;
if (isEmptyFilter(filter)) {
entities = executeQueryWithoutFilter(query, userId, pagination);
totalCount = executeCountQueryWithoutFilter(countQuery, userId);
} else {
entities = jdbcTemplate.query(query, new PremiseListEntryMapper(), userId, "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", pagination.getLimit(), pagination.getOffset());
totalCount = jdbcTemplate.queryForObject(countQuery, Integer.class, userId, "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%", "%" + filter + "%");
entities = executeQueryWithFilter(query, userId, filter, pagination);
totalCount = executeCountQueryWithFilter(countQuery, userId, filter);
}
return new SearchQueryResult<>(entities, pagination.getPage(), totalCount, pagination.getLimit());
}
private String buildCountQuery(String filter, Boolean deleted, Boolean archived, Boolean done) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("""
SELECT COUNT(*) FROM premise AS p
LEFT JOIN material as m ON p.material_id = m.id
LEFT JOIN node as n ON p.supplier_node_id = n.id
LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id
WHERE p.userId = ?""");
if (filter != null && !filter.isBlank()) {
queryBuilder.append(" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)");
private boolean isEmptyFilter(String filter) {
return filter == null || filter.isBlank();
}
if (deleted != null && deleted) {
queryBuilder.append(" AND p.deleted = TRUE");
private List<PremiseListEntry> executeQueryWithoutFilter(String query, Integer userId,
SearchQueryPagination pagination) {
return jdbcTemplate.query(
query,
new PremiseListEntryMapper(),
userId,
pagination.getLimit(),
pagination.getOffset()
);
}
if (archived != null && archived) {
queryBuilder.append(" AND p.archived = TRUE");
private Integer executeCountQueryWithoutFilter(String countQuery, Integer userId) {
return jdbcTemplate.queryForObject(countQuery, Integer.class, userId);
}
if (done != null && done) {
queryBuilder.append(" AND p.done = TRUE");
private List<PremiseListEntry> executeQueryWithFilter(String query, Integer userId,
String filter, SearchQueryPagination pagination) {
String wildcardFilter = "%" + filter + "%";
Object[] params = createFilterParams(userId, wildcardFilter, pagination);
return jdbcTemplate.query(query, new PremiseListEntryMapper(), params);
}
return queryBuilder.toString();
private Integer executeCountQueryWithFilter(String countQuery, Integer userId, String filter) {
String wildcardFilter = "%" + filter + "%";
Object[] params = createFilterParamsForCount(userId, wildcardFilter);
return jdbcTemplate.queryForObject(countQuery, Integer.class, params);
}
private String buildQuery(String filter, Boolean deleted, Boolean archived, Boolean done) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("""
SELECT * FROM premise AS p
LEFT JOIN material as m ON p.material_id = m.id
LEFT JOIN node as n ON p.supplier_node_id = n.id
LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id
WHERE p.userId = ?""");
if (filter != null && !filter.isBlank()) {
queryBuilder.append(" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)");
}
if (deleted != null && deleted) {
queryBuilder.append(" AND p.deleted = TRUE");
private Object[] createFilterParams(Integer userId, String wildcardFilter, SearchQueryPagination pagination) {
return new Object[]{
userId,
wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter,
wildcardFilter, wildcardFilter, wildcardFilter,
pagination.getLimit(), pagination.getOffset()
};
}
if (archived != null && archived) {
queryBuilder.append(" AND p.archived = TRUE");
}
if (done != null && done) {
queryBuilder.append(" AND p.done = TRUE");
}
queryBuilder.append(" LIMIT ? OFFSET ?");
queryBuilder.append(" ORDER BY p.updated_at DESC");
return queryBuilder.toString();
private Object[] createFilterParamsForCount(Integer userId, String wildcardFilter) {
return new Object[]{
userId,
wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter,
wildcardFilter, wildcardFilter, wildcardFilter
};
}
@ -148,49 +148,44 @@ public class PremiseRepository {
}
}
@Transactional
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension dimensionEntity, Boolean stackable, Boolean mixable) {
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
if (premiseIds == null || premiseIds.isEmpty() || userId == null || dimensionEntity == null) {
return;
}
String query = """
boolean isStackable = stackable != null ? stackable : false;
boolean isMixable = mixable != null ? mixable : false;
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("length", dimensionEntity.getLength());
params.addValue("height", dimensionEntity.getHeight());
params.addValue("width", dimensionEntity.getWidth());
params.addValue("weight", dimensionEntity.getWeight());
params.addValue("dimensionUnit", dimensionEntity.getDimensionUnit().name());
params.addValue("weightUnit", dimensionEntity.getWeightUnit().name());
params.addValue("unitCount", dimensionEntity.getContentUnitCount());
params.addValue("stackable", isStackable);
params.addValue("mixable", isMixable);
params.addValue("userId", userId);
params.addValue("premiseIds", premiseIds);
String sql = """
UPDATE premise
SET individual_hu_length = ?,
individual_hu_height = ?,
individual_hu_width = ?,
individual_hu_weight = ?,
hu_displayed_dimension_unit = ?,
hu_displayed_weight_unit = ?,
hu_unit_count = ?,
hu_stackable = ?,
hu_mixable = ?,
WHERE user_id = ? AND id IN (""" + placeholders + ")" ;
jdbcTemplate.update(
query,
ps -> {
ps.setInt(1, dimensionEntity.getLength());
ps.setInt(2, dimensionEntity.getHeight());
ps.setInt(3, dimensionEntity.getWidth());
ps.setInt(4, dimensionEntity.getWeight());
ps.setString(5, dimensionEntity.getDimensionUnit().name());
ps.setString(6, dimensionEntity.getWeightUnit().name());
ps.setInt(7, dimensionEntity.getContentUnitCount());
ps.setBoolean(8, stackable);
ps.setBoolean(9, mixable);
ps.setInt(10, userId);
for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) {
ps.setInt(parameterIndex+10, premiseIds.get(parameterIndex));
}
}
);
SET individual_hu_length = :length,
individual_hu_height = :height,
individual_hu_width = :width,
individual_hu_weight = :weight,
hu_displayed_dimension_unit = :dimensionUnit,
hu_displayed_weight_unit = :weightUnit,
hu_unit_count = :unitCount,
hu_stackable = :stackable,
hu_mixable = :mixable
WHERE user_id = :userId AND id IN (:premiseIds)
""";
namedParameterJdbcTemplate.update(sql, params);
}
@ -202,7 +197,7 @@ public class PremiseRepository {
UPDATE premise
SET hs_code = ?,
tariff_rate = ?
WHERE user_id = ? id IN ("""+placeholders+")";
WHERE user_id = ? AND id IN (""" + placeholders + ")";
jdbcTemplate.update(
query,
@ -212,8 +207,8 @@ public class PremiseRepository {
ps.setBigDecimal(2, tariffRate);
ps.setInt(3, userId);
for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) {
ps.setInt(parameterIndex+3, premiseIds.get(parameterIndex));
for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
ps.setInt(parameterIndex + 4, premiseIds.get(parameterIndex));
}
}
);
@ -238,61 +233,171 @@ public class PremiseRepository {
ps.setBigDecimal(3, overseaShare);
ps.setInt(4, userId);
for (int parameterIndex = 0; parameterIndex <= premiseIds.size(); parameterIndex++) {
ps.setInt(parameterIndex+4, premiseIds.get(parameterIndex));
for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
ps.setInt(parameterIndex + 6, premiseIds.get(parameterIndex));
}
}
);
}
/**
* Retrieves all draft premises that would conflict with updating the material or supplier for a given premise.
* <p>
* A conflict arises when multiple premises share the same combination of material ID and supplier ID while
* being in the DRAFT state. This method enables the identification of such potential duplicates, excluding
* the premise being evaluated to avoid self-collision.
*
* @param userId The ID of the user who owns the premises.
* @param premiseId The ID of the premise being checked; this premise will be excluded from the results.
* @param materialId The material ID to verify for potential conflicts.
* @param supplierId The supplier node ID to verify for potential conflicts.
* @return A list of premises in the DRAFT state with the same material and supplier combination (excluding the specified premise).
* @throws IllegalArgumentException If any of the provided parameters are null.
*/
public List<Premise> getCollidingPremisesOnChange(Integer userId, Integer premiseId, Integer materialId, Integer supplierId) {
if( premiseId == null || materialId == null || supplierId == null )
return Collections.emptyList();
String sql = "SELECT * FROM premise " +
"WHERE material_id = ? " +
"AND supplier_node_id = ? " +
"AND id != ? " +
"AND user_id = ? " +
"AND state = 'DRAFT'";
return jdbcTemplate.query(sql, new PremiseMapper(), materialId, supplierId, premiseId, userId);
}
public void deletePremisesById(List<Integer> premiseIds) {
if(premiseIds == null || premiseIds.isEmpty()) return;
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
String query = """
DELETE FROM premise
WHERE id IN (""" + placeholders + ") AND state = " + PremiseState.DRAFT.name();
jdbcTemplate.update(query, premiseIds.toArray());
}
public void updateTariffRate(Integer premiseId, Number tariffRate) {
String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?";
jdbcTemplate.update(sql, tariffRate, premiseId);
}
public void setSupplierId(List<Integer> premiseId, Integer supplierId, boolean userSupplierNode) {
String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ? WHERE id IN ("+placeholders+")";
if(userSupplierNode) {
jdbcTemplate.update(sql, 0, supplierId, premiseId.toArray());
} else {
jdbcTemplate.update(sql, supplierId, 0, premiseId.toArray());
}
}
/**
* Encapsulates SQL query building logic
*/
private static class QueryBuilder {
private static final String BASE_JOIN_QUERY = """
FROM premise AS p
LEFT JOIN material as m ON p.material_id = m.id
LEFT JOIN node as n ON p.supplier_node_id = n.id
LEFT JOIN sys_user_node as user_n ON p.user_supplier_node_id = user_n.id
WHERE p.userId = ?""";
private static final String FILTER_CONDITION =
" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR n.note LIKE ? OR " +
"user_n.name LIKE ? OR m.name LIKE ? OR m.description LIKE ? OR m.part_number LIKE ?)";
private String filter;
private Boolean deleted;
private Boolean archived;
private Boolean done;
public QueryBuilder withFilter(String filter) {
this.filter = filter;
return this;
}
public QueryBuilder withDeleted(Boolean deleted) {
this.deleted = deleted;
return this;
}
public QueryBuilder withArchived(Boolean archived) {
this.archived = archived;
return this;
}
public QueryBuilder withDone(Boolean done) {
this.done = done;
return this;
}
public String buildCountQuery() {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT COUNT(*) ").append(BASE_JOIN_QUERY);
appendConditions(queryBuilder);
return queryBuilder.toString();
}
public String buildSelectQuery() {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT * ").append(BASE_JOIN_QUERY);
appendConditions(queryBuilder);
queryBuilder.append(" ORDER BY p.updated_at DESC");
queryBuilder.append(" LIMIT ? OFFSET ?");
return queryBuilder.toString();
}
private void appendConditions(StringBuilder queryBuilder) {
if (filter != null && !filter.isBlank()) {
queryBuilder.append(FILTER_CONDITION);
}
appendBooleanCondition(queryBuilder, deleted, "p.deleted");
appendBooleanCondition(queryBuilder, archived, "p.archived");
appendBooleanCondition(queryBuilder, done, "p.done");
}
private void appendBooleanCondition(StringBuilder queryBuilder, Boolean condition, String field) {
if (condition != null && condition) {
queryBuilder.append(" AND ").append(field).append(" = TRUE");
}
}
}
private static class PremiseListEntryMapper implements RowMapper<PremiseListEntry> {
@Override
public PremiseListEntry mapRow(ResultSet rs, int rowNum) throws SQLException {
PremiseListEntry entity = new PremiseListEntry();
entity.setId(rs.getInt("p.id"));
entity.setState(de.avatic.lcc.dto.calculation.PremiseState.valueOf(rs.getString("p.state")));
entity.setOwnerId(rs.getInt("p.user_id"));
entity.setCreatedAt(rs.getTimestamp("p.created_at").toLocalDateTime());
entity.setUpdatedAt(rs.getTimestamp("p.created_at").toLocalDateTime());
// Map base premise properties
mapBasePremiseProperties(entity, rs);
// Map material
entity.setMaterial(mapMaterial(rs));
if (rs.getInt("p.supplier_node_id") != 0) {
entity.setSupplierId(rs.getInt("n.id"));
entity.setSupplierName(rs.getString("n.name"));
entity.setSupplierAddress(rs.getString("n.address"));
entity.setSupplierCountryId(rs.getInt("n.country_id"));
entity.setUserSupplier(false);
entity.setSupplierGeoLatitude(rs.getBigDecimal("n.geo_latitude"));
entity.setSupplierGeoLongitude(rs.getBigDecimal("n.geo_longitude"));
entity.setSupplierIsDestination(rs.getBoolean("n.is_destination"));
entity.setSupplierIsIntermediate(rs.getBoolean("n.is_intermediate"));
entity.setSupplierIsSource(rs.getBoolean("n.is_source"));
} else if (rs.getInt("p.user_supplier_node_id") != 0) {
entity.setSupplierId(rs.getInt("user_n.id"));
entity.setSupplierName(rs.getString("user_n.name"));
entity.setSupplierAddress(rs.getString("user_n.address"));
entity.setSupplierCountryId(rs.getInt("user_n.country_id"));
entity.setUserSupplier(false);
entity.setSupplierGeoLatitude(rs.getBigDecimal("user_n.geo_latitude"));
entity.setSupplierGeoLongitude(rs.getBigDecimal("user_n.geo_longitude"));
entity.setSupplierIsDestination(rs.getBoolean("user_n.is_destination"));
entity.setSupplierIsIntermediate(rs.getBoolean("user_n.is_intermediate"));
entity.setSupplierIsSource(rs.getBoolean("user_n.is_source"));
}
// Map supplier (either regular node or user node)
mapSupplierProperties(entity, rs);
return entity;
}
private void mapBasePremiseProperties(PremiseListEntry entity, ResultSet rs) throws SQLException {
entity.setId(rs.getInt("p.id"));
entity.setState(de.avatic.lcc.dto.calculation.PremiseState.valueOf(rs.getString("p.state")));
entity.setOwnerId(rs.getInt("p.user_id"));
entity.setCreatedAt(rs.getTimestamp("p.created_at").toLocalDateTime());
entity.setUpdatedAt(rs.getTimestamp("p.created_at").toLocalDateTime()); // Note: This looks like a bug, should be "p.updated_at"
}
private Material mapMaterial(ResultSet rs) throws SQLException {
var data = new Material();
Material data = new Material();
data.setId(rs.getInt("m.id"));
data.setName(rs.getString("m.name"));
@ -302,6 +407,29 @@ public class PremiseRepository {
return data;
}
private void mapSupplierProperties(PremiseListEntry entity, ResultSet rs) throws SQLException {
if (rs.getInt("p.supplier_node_id") != 0) {
mapSupplier(entity, rs, "n");
} else if (rs.getInt("p.user_supplier_node_id") != 0) {
mapSupplier(entity, rs, "user_n");
entity.setUserSupplier(true);
}
}
private void mapSupplier(PremiseListEntry entity, ResultSet rs, String tablePrefix) throws SQLException {
entity.setSupplierId(rs.getInt(tablePrefix + ".id"));
entity.setSupplierName(rs.getString(tablePrefix + ".name"));
entity.setSupplierAddress(rs.getString(tablePrefix + ".address"));
entity.setSupplierCountryId(rs.getInt(tablePrefix + ".country_id"));
entity.setSupplierGeoLatitude(rs.getBigDecimal(tablePrefix + ".geo_latitude"));
entity.setSupplierGeoLongitude(rs.getBigDecimal(tablePrefix + ".geo_longitude"));
entity.setSupplierIsDestination(rs.getBoolean(tablePrefix + ".is_destination"));
entity.setSupplierIsIntermediate(rs.getBoolean(tablePrefix + ".is_intermediate"));
entity.setSupplierIsSource(rs.getBoolean(tablePrefix + ".is_source"));
}
}
private static class PremiseMapper implements RowMapper<Premise> {

View file

@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -31,6 +32,17 @@ public class RouteNodeRepository {
return Optional.of(nodes.getFirst());
}
public void deleteAllById(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String sql = "DELETE FROM premise_route_node WHERE id IN (" + placeholders + ")";
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
private static class RouteNodeMapper implements RowMapper<RouteNode> {
@Override

View file

@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
@Repository
@ -23,6 +24,18 @@ public class RouteRepository {
return jdbcTemplate.query(query, new RouteMapper(), id);
}
public void deleteAllById(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
// Create a parameterized SQL statement with placeholders for each ID
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String sql = "DELETE FROM premise_route WHERE id IN (" + placeholders + ")";
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
private static class RouteMapper implements RowMapper<Route> {
@Override
public Route mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -2,13 +2,13 @@ package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.dto.generic.RouteType;
import de.avatic.lcc.model.premises.route.RouteSection;
import de.avatic.lcc.model.premises.route.RouteSectionType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
@Repository
@ -27,6 +27,17 @@ public class RouteSectionRepository {
return jdbcTemplate.query(query, new RouteSectionMapper(), routeId);
}
public void deleteAllById(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
String sql = "DELETE FROM premise_route_section WHERE id IN (" + placeholders + ")";
jdbcTemplate.update(sql, ids.toArray(new Object[0]));
}
private static class RouteSectionMapper implements RowMapper<RouteSection> {
@Override
public RouteSection mapRow(ResultSet rs, int rowNum) throws SQLException {

View file

@ -1,6 +1,7 @@
package de.avatic.lcc.repositories.properties;
import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.model.rates.ValidityPeriodState;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@ -10,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
/**
@ -99,23 +101,22 @@ public class PropertyRepository {
return jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.EXPIRED.name(), propertySetId);
}
private static class PropertyMapper implements RowMapper<PropertyDTO> {
@Override
public PropertyDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
var dto = new PropertyDTO();
public Optional<PropertyDTO> 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,
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
LEFT JOIN property_set AS propertySet ON propertySet.id = property.property_set_id
WHERE propertySet.state = ? AND type.external_mapping_id = ?
""";
dto.setName(rs.getString("name"));
dto.setDraftValue(rs.getString("draftValue"));
dto.setCurrentValue(rs.getString("validValue"));
dto.setValidationRule(rs.getString("validationRule"));
dto.setExternalMappingId(rs.getString("externalMappingId"));
dto.setRequired(true);
dto.setDataType(rs.getString("dataType"));
var property = jdbcTemplate.query(query, new PropertyMapper(), ValidityPeriodState.VALID.name(), mappingId.name());
if (property.isEmpty()) return Optional.empty();
else return Optional.of(property.getFirst());
return dto;
}
}
/**
* Fills the draft property set with values from a valid property set.
@ -137,4 +138,21 @@ public class PropertyRepository {
});
}
private static class PropertyMapper implements RowMapper<PropertyDTO> {
@Override
public PropertyDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
var dto = new PropertyDTO();
dto.setName(rs.getString("name"));
dto.setDraftValue(rs.getString("draftValue"));
dto.setCurrentValue(rs.getString("validValue"));
dto.setValidationRule(rs.getString("validationRule"));
dto.setExternalMappingId(rs.getString("externalMappingId"));
dto.setRequired(true);
dto.setDataType(rs.getString("dataType"));
return dto;
}
}
}

View file

@ -11,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
/**
* Repository for managing {@link MatrixRate} entities, including retrieval and mapping of MatrixRate rows
@ -76,6 +77,16 @@ public class MatrixRateRepository {
return jdbcTemplate.query(query, new MatrixRateMapper());
}
public Optional<MatrixRate> getByCountryIds(Integer fromCountryId, Integer toCountryId) {
String query = "SELECT * FROM country_matrix_rate WHERE from_country_id = ? AND to_country_id = ?";
var rates = jdbcTemplate.query(query, new MatrixRateMapper(), fromCountryId, toCountryId);
if(rates.isEmpty())
return Optional.empty();
else
return Optional.of(rates.getFirst());
}
/**
* Maps rows of a {@link ResultSet} to {@link MatrixRate} objects as required by
* the {@link JdbcTemplate}.

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.calculation;
package de.avatic.lcc.service;
import org.springframework.stereotype.Service;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.rates.ContainerRateDTO;
import de.avatic.lcc.dto.generic.ContainerType;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.countries.view.CountryDetailDTO;
import de.avatic.lcc.dto.generic.CountryDTO;

View file

@ -0,0 +1,100 @@
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
import de.avatic.lcc.dto.calculation.DestinationDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import de.avatic.lcc.model.premises.route.Route;
import de.avatic.lcc.model.premises.route.RouteSection;
import de.avatic.lcc.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
import de.avatic.lcc.repositories.premise.RouteRepository;
import de.avatic.lcc.repositories.premise.RouteSectionRepository;
import de.avatic.lcc.service.transformer.premise.DestinationTransformer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@Service
public class DestinationService {
private final DestinationRepository destinationRepository;
private final DestinationTransformer destinationTransformer;
private final RouteRepository routeRepository;
private final RouteSectionRepository routeSectionRepository;
private final RouteNodeRepository routeNodeRepository;
public DestinationService(DestinationRepository destinationRepository, DestinationTransformer destinationTransformer, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository) {
this.destinationRepository = destinationRepository;
this.destinationTransformer = destinationTransformer;
this.routeRepository = routeRepository;
this.routeSectionRepository = routeSectionRepository;
this.routeNodeRepository = routeNodeRepository;
}
public HashMap<Integer, DestinationDTO> createDestination(DestinationCreateDTO destinationCreateDTO) {
// do some checks
// - no duplicates
// do routing.
// create database entries.
return null;
}
public DestinationDTO getDestination(Integer id) {
//todo check authorization
return destinationTransformer.toDestinationDTO(destinationRepository.getById(id).orElseThrow());
}
public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) {
//todo check authorization
Integer userId = 1;
destinationRepository.update(id, userId, destinationUpdateDTO.getRepackingCost(), destinationUpdateDTO.getDisposalCost(), destinationUpdateDTO.getHandlingCost());
}
@Transactional
public void deleteAllDestinationsByPremiseId(List<Integer> ids, boolean deleteRoutesOnly) {
ids.forEach(id -> deleteDestinationById(id, deleteRoutesOnly));
}
@Transactional
public void deleteDestinationById(Integer id, boolean deleteRoutesOnly) {
//todo check authorization
Integer userId = 1;
Optional<Integer> ownerId = destinationRepository.getOwnerIdById(id);
if(ownerId.isPresent() && ownerId.get().equals(userId)) {
List<Route> routes = routeRepository.getByDestinationId(id);
for(var route : routes) {
List<RouteSection> sections = routeSectionRepository.getByRouteId(route.getId());
routeSectionRepository.deleteAllById(sections.stream().map(RouteSection::getId).toList());
routeNodeRepository.deleteAllById(sections.stream().flatMap(section -> Stream.of(section.getFromRouteNodeId(), section.getToRouteNodeId())).toList());
}
routeRepository.deleteAllById(routes.stream().map(Route::getId).toList());
if(!deleteRoutesOnly)
destinationRepository.deleteById(id);
return;
}
throw new RuntimeException("Not authorized to delete destination with id " + id);
}
}

View file

@ -1,6 +1,5 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.material.update.MaterialUpdateDTO;
import de.avatic.lcc.dto.configuration.material.view.MaterialDetailDTO;
import de.avatic.lcc.dto.generic.MaterialDTO;
import de.avatic.lcc.model.materials.Material;
@ -8,7 +7,6 @@ import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.service.transformer.generic.MaterialTransformer;
import de.avatic.lcc.service.transformer.material.MaterialUpdateDTOTransformer;
import de.avatic.lcc.service.transformer.material.MaterialDetailTransformer;
import de.avatic.lcc.util.exception.clienterror.MaterialNotFoundException;
import org.springframework.stereotype.Service;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO;
import de.avatic.lcc.model.rates.MatrixRate;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO;
import de.avatic.lcc.dto.generic.NodeDTO;
@ -12,7 +12,6 @@ import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.GeoApiService;
import de.avatic.lcc.service.transformer.nodes.NodeUpdateDTOTransformer;
import de.avatic.lcc.service.transformer.nodes.NodeDetailTransformer;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
@ -23,6 +22,10 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* Service class for managing lifecycle operations for nodes and related entities.
* Provides methods to list, retrieve, update, delete, and create nodes in the system.
*/
@Service
public class NodeService {
@ -43,26 +46,72 @@ public class NodeService {
}
/**
* Retrieves a paginated list of nodes filtered by the provided keyword.
*
* @param filter the filter keyword to search nodes.
* @param page the page number to retrieve.
* @param limit the number of nodes per page.
* @return a {@link SearchQueryResult} containing the paginated list of nodes.
*/
public SearchQueryResult<NodeDTO> listNodes(String filter, int page, int limit) {
return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeTransformer::toNodeDTO);
}
/**
* Retrieves a paginated detailed view of nodes based on the provided filter.
*
* @param filter the filter keyword to search nodes.
* @param page the page number to retrieve.
* @param limit the number of nodes per page.
* @return a {@link SearchQueryResult} containing detailed information of nodes.
*/
public SearchQueryResult<NodeDetailDTO> listNodesView(String filter, int page, int limit) {
return SearchQueryResult.map(nodeRepository.listNodes(filter, true, new SearchQueryPagination(page, limit)), nodeDetailTransformer::toNodeDetailDTO);
}
/**
* Retrieves the detailed view of a specific node by its ID.
*
* @param id the ID of the node to retrieve.
* @return a {@link NodeDetailDTO} containing detailed information of the node.
* @throws NodeNotFoundException if no node is found with the specified ID.
*/
public NodeDetailDTO getNode(Integer id) {
return nodeDetailTransformer.toNodeDetailDTO(nodeRepository.getById(id).orElseThrow(() -> new NodeNotFoundException(id)));
}
/**
* Marks a node as deprecated by its ID.
*
* @param id the ID of the node to delete.
* @return the ID of the deprecated node.
* @throws NodeNotFoundException if no node is found with the specified ID.
*/
public Integer deleteNode(Integer id) {
return nodeRepository.setDeprecatedById(id).orElseThrow(() -> new NodeNotFoundException(id));
}
/**
* Updates an existing node with the provided data.
*
* @param dto the {@link NodeUpdateDTO} containing the update data for the node.
* @return the ID of the updated node.
* @throws NodeNotFoundException if no node is found for the update.
*/
public Integer updateNode(NodeUpdateDTO dto) {
return nodeRepository.update(nodeUpdateDTOTransformer.fromNodeUpdateDTO(dto)).orElseThrow(() -> new NodeNotFoundException(dto.getId()));
}
/**
* Searches for nodes by filter and type with an option to include user-specific nodes.
*
* @param filter the filter keyword to search nodes.
* @param limit the maximum number of nodes to retrieve.
* @param nodeType the type of node to restrict the search to.
* @param includeUserNode if true, includes user-specific nodes in the search results.
* @return a list of {@link NodeDTO} objects representing the search results.
*/
public List<NodeDTO> searchNode(String filter, int limit, NodeType nodeType, boolean includeUserNode) {
List<NodeDTO> nodes = new ArrayList<>();
int userId = 1; //TODO get current user's id
@ -80,19 +129,5 @@ public class NodeService {
return nodes;
}
public void addUserNode(AddUserNodeDTO dto) {
Node node = new Node();
node.setName(dto.getName());
node.setAddress(dto.getAddress());
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude()));
node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude()));
node.setDestination(false);
node.setSource(true);
node.setIntermediate(false);
node.setDeprecated(false);
node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow().getId());
userNodeRepository.add(node);
}
}

View file

@ -1,12 +1,13 @@
package de.avatic.lcc.service.calculation;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.calculation.PremiseDTO;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.*;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.dto.calculation.edit.masterData.MaterialUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.PackagingUpdateDTO;
import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.premise.*;
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import org.springframework.stereotype.Service;
@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;
@Service
public class PremisesService {
@ -22,11 +24,13 @@ public class PremisesService {
private final PremiseRepository premiseRepository;
private final PremiseTransformer premiseTransformer;
private final DimensionTransformer dimensionTransformer;
private final DestinationService destinationService;
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer) {
public PremisesService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DimensionTransformer dimensionTransformer, DestinationService destinationService, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, DestinationService destinationService) {
this.premiseRepository = premiseRepository;
this.premiseTransformer = premiseTransformer;
this.dimensionTransformer = dimensionTransformer;
this.destinationService = destinationService;
}
@Transactional(readOnly = true)
@ -39,7 +43,6 @@ public class PremisesService {
}
@Transactional(readOnly = true)
public List<PremiseDetailDTO> getPremises(List<Integer> premiseIds) {
//TODO check if user authorized
@ -47,42 +50,12 @@ public class PremisesService {
return premiseRepository.getPremisesById(premiseIds).stream().map(premiseTransformer::toPremiseDetailDTO).toList();
}
//TODO move to other service
public Integer startCalculation(List<Integer> premises) {
}
@Transactional
public List<PremiseDetailDTO> setSupplier(SetDataDTO setSupplierDTO) {
//0. check if one of the given premises contains the same part number, and abort if any duplicate would emerge
//1. delete all routes.
//2. recalculate routes
//3 if updateMasterDate is set:
//3.1 copy packaging data (if exists) of new supplier to premise
//3.2 set new tariff rate (if supplier country changed)
//4. Deliver Premise Detail
return null;
}
@Transactional
public List<PremiseDetailDTO> setMaterial(SetDataDTO setMaterialDTO) {
//0. check if one of the given premises contains the same supplier id, and abort if any duplicate would emerge
//3 if updateMasterDate is set:
//3.1 copy packaging data (if exists) of new material to premise
//3.2 set new tariff rate and hs code (if supplier country changed)
//4. Deliver Premise Detail
return null;
}
public HashMap<String, String> updatePackaging(PackagingUpdateDTO packagingDTO) {
//TODO check values. and return errors if needed
@ -111,7 +84,17 @@ public class PremisesService {
premiseRepository.updatePrice(priceUpdateDTO.getPremiseIds(), userId, BigDecimal.valueOf(priceUpdateDTO.getPrice().doubleValue()), priceUpdateDTO.getIncludeFcaFee(), BigDecimal.valueOf(priceUpdateDTO.getOverseaShare().doubleValue()));
return null;
}
@Transactional
public void delete(List<Integer> premiseIds) {
//TODO check authorization
destinationService.deleteAllDestinationsByPremiseId(premiseIds, false);
premiseRepository.deletePremisesById(premiseIds);
}
public void fillPackaging(List<Integer> premiseIds) {
}
}

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.configuration;
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.dto.generic.PropertyDTO;
@ -10,6 +10,20 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* Service for managing properties and property sets within the application.
* <p>
* This service provides functionality to:
* <ul>
* <li>Set property values for specific mappings</li>
* <li>List properties filtered by property set ID</li>
* <li>List all property sets with their validity periods</li>
* <li>Invalidate property sets</li>
* </ul>
* <p>
* The service interacts with {@link PropertyRepository} and {@link PropertySetRepository}
* to perform CRUD operations on properties and property sets.
*/
@Service
public class PropertyService {
@ -23,6 +37,7 @@ public class PropertyService {
*
* @param propertyRepository the repository to manage properties
* @param propertySetRepository the repository to manage property sets
* @param validityPeriodTransformer the transformer to convert property sets to validity period DTOs
*/
public PropertyService(PropertyRepository propertyRepository, PropertySetRepository propertySetRepository, ValidityPeriodTransformer validityPeriodTransformer) {
this.propertyRepository = propertyRepository;
@ -32,6 +47,9 @@ public class PropertyService {
/**
* Sets a property value for a given mapping ID.
* <p>
* This method updates the property value in the current draft set for the specified mapping ID.
* Note: Property value validation is pending implementation.
*
* @param mappingId the identifier of the property to be updated
* @param value the new value to be set for the property
@ -45,12 +63,14 @@ public class PropertyService {
}
/**
* Retrieves a list of properties. If a specific property set ID is provided, only
* properties from that set are returned. Otherwise, the properties from the current
* valid set and current draft set are returned.
* Retrieves a list of properties based on the specified property set ID.
* <p>
* If the property set ID is 0, all properties from both the current valid set
* and current draft set are returned. Otherwise, only properties from the
* specified property set are returned.
*
* @param propertySetId the ID of the property set (0 for all properties)
* @return a list of properties as {@link PropertyDTO}
* @return a list of properties as {@link PropertyDTO} objects
*/
public List<PropertyDTO> listProperties(Integer propertySetId) {
if (propertySetId == 0)
@ -61,8 +81,11 @@ public class PropertyService {
/**
* Retrieves a list of all property sets, mapping them to validity period data transfer objects.
* <p>
* This method fetches all property sets from the repository and transforms each of them
* into a {@link ValidityPeriodDTO} using the injected transformer.
*
* @return a list of property sets as {@link ValidityPeriodDTO}
* @return a list of property sets as {@link ValidityPeriodDTO} objects
*/
public List<ValidityPeriodDTO> listPropertySets() {
return propertySetRepository.listPropertySets().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList();

View file

@ -0,0 +1,48 @@
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO;
import de.avatic.lcc.model.country.IsoCode;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class UserNodeService {
private final CountryRepository countryRepository;
private final UserNodeRepository userNodeRepository;
public UserNodeService(CountryRepository countryRepository, UserNodeRepository userNodeRepository) {
this.countryRepository = countryRepository;
this.userNodeRepository = userNodeRepository;
}
public Node getUserNode(Integer id) {
return userNodeRepository.getById(id).orElseThrow();
}
/**
* Adds a user-specific node to the system based on the provided {@link AddUserNodeDTO}.
*
* @param dto the {@link AddUserNodeDTO} containing details about the user node to create, including name,
* address, geographic location, and country information.
* @throws IllegalArgumentException if any required information in the DTO is missing or invalid.
*/
public void addUserNode(AddUserNodeDTO dto) {
Node node = new Node();
node.setName(dto.getName());
node.setAddress(dto.getAddress());
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude()));
node.setGeoLat(BigDecimal.valueOf(dto.getLocation().getLatitude()));
node.setDestination(false);
node.setSource(true);
node.setIntermediate(false);
node.setDeprecated(false);
node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow().getId());
userNodeRepository.add(node);
}
}

View file

@ -0,0 +1,61 @@
package de.avatic.lcc.service.access;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Service class responsible for managing validity periods in the system.
* <p>
* This service provides operations for retrieving and manipulating validity periods,
* acting as an intermediary between the repository layer and the application layer.
* It uses a transformer to convert between entity and DTO representations.
* </p>
*
*/
@Service
public class ValidityPeriodService {
private final ValidityPeriodRepository validityPeriodRepository;
private final ValidityPeriodTransformer validityPeriodTransformer;
/**
* Constructs a new ValidityPeriodService with the required dependencies.
*
* @param validityPeriodRepository Repository for accessing validity period data
* @param validityPeriodTransformer Transformer for converting between entity and DTO representations
*/
public ValidityPeriodService(ValidityPeriodRepository validityPeriodRepository, ValidityPeriodTransformer validityPeriodTransformer) {
this.validityPeriodRepository = validityPeriodRepository;
this.validityPeriodTransformer = validityPeriodTransformer;
}
/**
* Retrieves all validity periods from the repository and transforms them to DTOs.
* <p>
* Fetches the list of validity periods from the repository and transforms each entity
* to its corresponding DTO representation using the transformer.
* </p>
*
* @return A list of {@link ValidityPeriodDTO} objects representing all validity periods
*/
public List<ValidityPeriodDTO> listPeriods() {
return validityPeriodRepository.listPeriods().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList();
}
/**
* Invalidates a validity period with the specified ID.
* <p>
* Marks the validity period as invalid in the repository based on its ID.
* </p>
*
* @param id The unique identifier of the validity period to invalidate
*/
public void invalidate(Integer id) {
validityPeriodRepository.invalidateById(id);
}
}

View file

@ -5,7 +5,7 @@ import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
import de.avatic.lcc.service.bulk.mapper.*;
import de.avatic.lcc.service.excelMapper.*;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.SheetVisibility;

View file

@ -4,10 +4,7 @@ import de.avatic.lcc.dto.bulk.BulkFileType;
import de.avatic.lcc.dto.bulk.BulkProcessingType;
import de.avatic.lcc.dto.bulk.BulkStatus;
import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.model.materials.Material;
import de.avatic.lcc.model.rates.ContainerRate;
import de.avatic.lcc.model.rates.MatrixRate;
import de.avatic.lcc.service.bulk.mapper.*;
import de.avatic.lcc.service.excelMapper.*;
import de.avatic.lcc.util.exception.clienterror.FileFormatNotSupportedException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
@ -16,7 +13,6 @@ import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
@Service
public class BulkFileProcessingService {
@ -65,6 +61,7 @@ public class BulkFileProcessingService {
break;
case NODE:
var nodes = nodeExcelMapper.extractSheet(sheet);
// check predecessors chains for loops or contradictions
break;
default:

View file

@ -4,10 +4,8 @@ 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 de.avatic.lcc.service.excelMapper.*;
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;

View file

@ -0,0 +1,27 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class ChangeMaterialService {
@Transactional
public List<PremiseDetailDTO> setMaterial(SetDataDTO setMaterialDTO) {
//0. check if one of the given premises contains the same supplier id, and abort if any duplicate would emerge
//3 if updateMasterDate is set:
//3.1 copy packaging data (if exists) of new material to premise
//3.2 set new tariff rate and hs code (if supplier country changed)
//4. Deliver Premise Detail
return null;
}
}

View file

@ -0,0 +1,135 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.packaging.PackagingDimension;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.model.properties.PackagingProperty;
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.repositories.premise.DestinationRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.CustomApiService;
import de.avatic.lcc.service.access.DestinationService;
import de.avatic.lcc.service.access.PremisesService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
public class ChangeSupplierService {
private final PremiseRepository premiseRepository;
private final DestinationService destinationService;
private final RoutingService routingService;
private final PremisesService premisesService;
private final CustomApiService customApiService;
private final NodeRepository nodeRepository;
private final UserNodeRepository userNodeRepository;
private final MaterialRepository materialRepository;
private final PackagingRepository packagingRepository;
private final PackagingDimensionRepository packagingDimensionRepository;
private final PackagingPropertiesRepository packagingPropertiesRepository;
private final DestinationRepository destinationRepository;
public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, MaterialRepository materialRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository) {
this.premiseRepository = premiseRepository;
this.destinationService = destinationService;
this.routingService = routingService;
this.premisesService = premisesService;
this.customApiService = customApiService;
this.nodeRepository = nodeRepository;
this.userNodeRepository = userNodeRepository;
this.materialRepository = materialRepository;
this.packagingRepository = packagingRepository;
this.packagingDimensionRepository = packagingDimensionRepository;
this.packagingPropertiesRepository = packagingPropertiesRepository;
this.destinationRepository = destinationRepository;
}
@Transactional
public List<PremiseDetailDTO> setSupplier(SetDataDTO dto) {
Integer userId = 1; // todo get current user;
Integer supplierNodeId = dto.getSupplierNodeId();
List<Integer> premiseIds = dto.getPremiseId();
if (supplierNodeId == null || premiseIds == null || premiseIds.isEmpty())
throw new IllegalArgumentException("No supplier supplierNodeId or premises given");
Node supplier = dto.isUserSupplierNode() ? userNodeRepository.getById(supplierNodeId).orElseThrow() : nodeRepository.getById(supplierNodeId).orElseThrow();
// get all premises first.
List<Premise> allPremises = premiseRepository.getPremisesById(premiseIds);
// find resulting duplicates, split into "keep" and "to be deleted".
Map<Integer, Premise> uniqueMap = new HashMap<>();
List<Premise> premisesToBeDeleted = new ArrayList<>();
allPremises.forEach(p -> {
if (null != uniqueMap.putIfAbsent(p.getMaterialId(), p)) premisesToBeDeleted.add(p);
});
Collection<Premise> premisesToProcess = uniqueMap.values();
// check if user owns all premises:
if (allPremises.stream().anyMatch(p -> !p.getUserId().equals(userId)))
throw new IllegalArgumentException("Not authorized to change suppliers of premises owned by other users");
// check for any other collisions, and mark as "to be deleted":
premisesToBeDeleted.addAll(premisesToProcess.stream().map(p -> premiseRepository.getCollidingPremisesOnChange(userId, p.getId(), p.getMaterialId(), supplierNodeId)).flatMap(List::stream).toList());
// delete all routes of all destinations
destinationService.deleteAllDestinationsByPremiseId(premisesToProcess.stream().map(Premise::getId).toList(), true);
//recalculate routes:
for (Premise premise : premisesToProcess) {
List<Destination> destination = destinationRepository.getByPremiseId(premise.getId());
for( Destination d : destination ) {
routingService.findRoutes(d.getId(), supplierNodeId);
}
}
//update master data:
if (dto.isUpdateMasterData()) {
for (var premise : premisesToProcess) {
var tariffRate = customApiService.getTariffRate(premise.getHsCode(), supplier.getCountryId());
premiseRepository.updateTariffRate(premise.getId(), tariffRate);
if (!dto.isUserSupplierNode()) {
var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), supplierNodeId);
Optional<PackagingDimension> dimension = packagingDimensionRepository.getById(packaging.getFirst().getHuId());
if (dimension.isPresent()) {
Optional<PackagingProperty> stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name());
Optional<PackagingProperty> mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.MIXABLE.name());
//TODO check optional
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), userId, dimension.get(), Boolean.valueOf(stackable.get().getValue()), Boolean.valueOf(mixable.get().getValue()));
}
}
premisesService.fillPackaging(premisesToProcess.stream().map(Premise::getId).toList());
}
}
// actually update supplier supplierNodeId.
premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplierNodeId, dto.isUserSupplierNode());
//delete all conflicting premises:
premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
}
}

View file

@ -1,43 +0,0 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
import de.avatic.lcc.dto.calculation.DestinationDTO;
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
import de.avatic.lcc.repositories.premise.DestinationRepository;
import org.springframework.stereotype.Service;
import java.util.HashMap;
@Service
public class DestinationService {
private final DestinationRepository destinationRepository;
public DestinationService(DestinationRepository destinationRepository) {
this.destinationRepository = destinationRepository;
}
public HashMap<Integer, DestinationDTO> createDestination(DestinationCreateDTO destinationCreateDTO) {
// do some checks
// - no duplicates
// do routing.
// create database entries.
return null;
}
public DestinationDTO getDestination(Integer id) {
return null;
}
public void updateDestination(Integer id, DestinationUpdateDTO destinationUpdateDTO) {
//destinationRepository.update(id, destinationUpdateDTO.)
}
public void deleteDestination(Integer id) {
}
}

View file

@ -0,0 +1,135 @@
package de.avatic.lcc.service.calculation;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.repositories.rates.MatrixRateRepository;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class RoutingService {
private final NodeRepository nodeRepository;
private final CountryRepository countryRepository;
private final MatrixRateRepository matrixRateRepository;
private final PropertyRepository propertyRepository;
public RoutingService(NodeRepository nodeRepository, CountryRepository countryRepository, MatrixRateRepository matrixRateRepository, PropertyRepository propertyRepository) {
this.nodeRepository = nodeRepository;
this.countryRepository = countryRepository;
this.matrixRateRepository = matrixRateRepository;
this.propertyRepository = propertyRepository;
}
public void findRoutes(Integer destinationId, Integer sourceId) {
var foundRoutes = new ArrayList<FoundRoute>();
Node source = nodeRepository.getById(sourceId).orElseThrow();
Node destination = nodeRepository.getById(destinationId).orElseThrow();
List<Node> regionNodes = nodeRepository.getByDistance(source, getRegionRadius());
List<Node> outboundNodes = nodeRepository.getAllOutboundFor(source.getCountryId());
List<List<Node>> destinationChains = resolveChains(destinationId);
Map<Integer, List<List<Node>>> outboundChains = getOutboundChains(outboundNodes);
foundRoutes.addAll(constructRoutesWithChains(destinationChains, source, destination));
}
private Map<Integer, List<List<Node>>> getOutboundChains(List<Node> outboundNodes) {
Map<Integer, List<List<Node>>> outboundChains = new HashMap<>();
for(var outboundNode : outboundNodes) {
outboundChains.put(outboundNode.getId(), resolveChains(outboundNode.getId()));
}
return outboundChains;
}
private Integer getRegionRadius() {
var property = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.RADIUS_REGION);
return property.map(propertyDTO -> Integer.valueOf(propertyDTO.getCurrentValue())).orElseGet(SystemPropertyMappingId.RADIUS_REGION::getDefaultAsInteger);
}
private ArrayList<FoundRoute> constructRoutesWithChains(List<List<Node>> chains, Node source, Node destination) {
ArrayList<FoundRoute> foundRoutes = new ArrayList<>();
for (var chain : chains) {
if(!chain.isEmpty()) {
var matrixRate = matrixRateRepository.getByCountryIds(chain.getLast().getCountryId(), source.getCountryId());
if(matrixRate.isPresent()) {
foundRoutes.add(new FoundRoute(chain, destination, source));
}
}
}
return foundRoutes;
}
/**
* Resolves all possible chains of nodes starting from the specified node ID.
* This method recursively retrieves predecessor chains and appends successor chains
* to form complete resolution paths.
*
* @param nodeId The ID of the node from which the resolution process starts.
* Must not be null and must correspond to an existing node.
* @return A list of lists, where each inner list represents a fully resolved chain
* of nodes including predecessors and successors for the specified node ID.
*/
private List<List<Node>> resolveChains(Integer nodeId) {
var resolvedChains = new ArrayList<List<Node>>();
var predecessorChains = nodeRepository.resolveChainsById(nodeId);
for (var predecessorChain : predecessorChains) {
if (!predecessorChain.isEmpty()) {
var successorChains = resolveChains(predecessorChain.getLast().getId());
successorChains.forEach(successorChain -> {
successorChain.addAll(0, predecessorChain);
resolvedChains.add(successorChain);
});
}
else {
resolvedChains.add(predecessorChain);
}
}
return resolvedChains;
}
private static class FoundRoute {
private List<Node> nodes;
/**
* Constructs a FoundRoute object that represents a route consisting of a chain of nodes,
* starting with the source node, followed by the reversed chain of nodes, and ending with the destination node.
*
* @param chain The list of intermediary nodes in the route, in original order before reversing.
* Must not be null.
* @param destination The destination node in the route. Must not be null.
* @param source The source node in the route. Must not be null.
*/
public FoundRoute(List<Node> chain, Node destination, Node source) {
this.nodes = new ArrayList<>();
this.nodes.add(source);
this.nodes.addAll(chain.reversed());
this.nodes.add(destination);
}
}
}

View file

@ -1,28 +0,0 @@
package de.avatic.lcc.service.configuration;
import de.avatic.lcc.dto.generic.ValidityPeriodDTO;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ValidityPeriodService {
private final ValidityPeriodRepository validityPeriodRepository;
private final ValidityPeriodTransformer validityPeriodTransformer;
public ValidityPeriodService(ValidityPeriodRepository validityPeriodRepository, ValidityPeriodTransformer validityPeriodTransformer) {
this.validityPeriodRepository = validityPeriodRepository;
this.validityPeriodTransformer = validityPeriodTransformer;
}
public List<ValidityPeriodDTO> listPeriods() {
return validityPeriodRepository.listPeriods().stream().map(validityPeriodTransformer::toValidityPeriodDTO).toList();
}
public void invalidate(Integer id) {
validityPeriodRepository.invalidateById(id);
}
}

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.ContainerRateHeader;
import de.avatic.lcc.model.bulk.HiddenNodeHeader;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.HiddenCountryHeader;
import de.avatic.lcc.model.country.Country;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.HiddenNodeHeader;
import de.avatic.lcc.model.nodes.Node;

View file

@ -1,6 +1,5 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.ContainerRateHeader;
import de.avatic.lcc.model.bulk.MaterialHeader;
import de.avatic.lcc.model.materials.Material;
import de.avatic.lcc.repositories.MaterialRepository;

View file

@ -1,8 +1,7 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.*;
import de.avatic.lcc.model.country.IsoCode;
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;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.excelmodel.ExcelNode;
import de.avatic.lcc.model.bulk.HiddenCountryHeader;

View file

@ -1,4 +1,4 @@
package de.avatic.lcc.service.bulk.mapper;
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.excelmodel.ExcelPackaging;