Added Premise Controller Integration Tests
This commit is contained in:
parent
438fabefb9
commit
a9275a012a
67 changed files with 7747 additions and 2727 deletions
|
|
@ -3,13 +3,16 @@ package de.avatic.lcc.controller;
|
|||
import de.avatic.lcc.dto.error.ErrorDTO;
|
||||
import de.avatic.lcc.dto.error.ErrorResponseDTO;
|
||||
import de.avatic.lcc.util.exception.base.BadRequestException;
|
||||
import de.avatic.lcc.util.exception.base.ForbiddenException;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
@ -18,15 +21,40 @@ import java.util.Map;
|
|||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public ResponseEntity<ErrorResponseDTO> handleValidationExceptions(
|
||||
HttpMessageNotReadableException exception) {
|
||||
|
||||
ErrorDTO error = new ErrorDTO(
|
||||
exception.getClass().getSimpleName(),
|
||||
"Malformed Request",
|
||||
exception.getMessage(),
|
||||
new HashMap<>() {{
|
||||
put("errorMessage", exception.getMessage());
|
||||
put("stackTrace", exception.getStackTrace());
|
||||
}}
|
||||
);
|
||||
|
||||
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, String>> handleValidationExceptions(
|
||||
MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getFieldErrors().forEach(error ->
|
||||
errors.put(error.getField(), error.getDefaultMessage()));
|
||||
return ResponseEntity.badRequest().body(errors);
|
||||
public ResponseEntity<ErrorResponseDTO> handleValidationExceptions(
|
||||
MethodArgumentNotValidException exception) {
|
||||
|
||||
ErrorDTO error = new ErrorDTO(
|
||||
exception.getClass().getSimpleName(),
|
||||
"Constraint Violation",
|
||||
exception.getMessage(),
|
||||
new HashMap<>() {{
|
||||
put("errorMessage", exception.getMessage());
|
||||
put("stackTrace", exception.getStackTrace());
|
||||
}}
|
||||
);
|
||||
|
||||
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// @ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
|
|
@ -82,7 +110,22 @@ public class GlobalExceptionHandler {
|
|||
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
@ExceptionHandler(ForbiddenException.class)
|
||||
public ResponseEntity<ErrorResponseDTO> handleForbiddenException(ForbiddenException exception) { //
|
||||
|
||||
ErrorDTO error = new ErrorDTO(
|
||||
exception.getClass().getSimpleName(),
|
||||
"Forbidden Error",
|
||||
exception.getMessage(),
|
||||
new HashMap<>() {{
|
||||
put("errorMessage", exception.getMessage());
|
||||
put("stackTrace", exception.getStackTrace());
|
||||
}}
|
||||
);
|
||||
|
||||
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
package de.avatic.lcc.controller.calculation;
|
||||
|
||||
|
||||
import de.avatic.lcc.dto.bulk.BulkStatus;
|
||||
import de.avatic.lcc.dto.calculation.CalculationStatus;
|
||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
||||
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.*;
|
||||
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.dto.calculation.edit.masterData.*;
|
||||
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
||||
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
|
||||
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.dto.generic.LocationDTO;
|
||||
import de.avatic.lcc.service.access.DestinationService;
|
||||
import de.avatic.lcc.service.access.PremisesService;
|
||||
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.access.PremisesService;
|
||||
import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService;
|
||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/calculation")
|
||||
@Validated
|
||||
public class PremiseController {
|
||||
|
||||
private final PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService;
|
||||
|
|
@ -44,8 +51,15 @@ public class PremiseController {
|
|||
this.changeMaterialService = changeMaterialService;
|
||||
}
|
||||
|
||||
@GetMapping("/view")
|
||||
public ResponseEntity<List<PremiseDTO>> listPremises(@RequestParam String filter, @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer limit, @RequestParam(name = "user", required = false) Integer userId, @RequestParam(required = false) Boolean deleted, @RequestParam(required = false) Boolean archived, @RequestParam(required = false) Boolean done) {
|
||||
@GetMapping({"/view", "/view/"})
|
||||
public ResponseEntity<List<PremiseDTO>> listPremises(@RequestParam(required = false) String filter,
|
||||
@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page,
|
||||
@RequestParam(name = "user", required = false) Integer userId,
|
||||
@RequestParam(required = false) Boolean deleted,
|
||||
@RequestParam(required = false) Boolean archived,
|
||||
@RequestParam(required = false) Boolean done) {
|
||||
|
||||
var premises = premisesServices.listPremises(filter, page, limit, userId, deleted, archived, done);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
|
|
@ -55,22 +69,56 @@ public class PremiseController {
|
|||
.body(premises.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
@GetMapping({"/search", "/search/"})
|
||||
public ResponseEntity<PremiseSearchResultDTO> findMaterialsAndSuppliers(@RequestParam String search) {
|
||||
return ResponseEntity.ok(premiseSearchStringAnalyzerService.findMaterialAndSuppliers(search));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
public ResponseEntity<List<PremiseDetailDTO>> createPremises(@RequestParam("material") List<Integer> materialIds, @RequestParam("supplier") List<Integer> supplierIds, @RequestParam("user_supplier") List<Integer> userSupplierIds, @RequestParam("from_scratch") boolean createEmpty) {
|
||||
return ResponseEntity.ok(premiseCreationService.createPremises(materialIds, supplierIds, userSupplierIds, createEmpty));
|
||||
@PostMapping({"/create", "/create/"})
|
||||
public ResponseEntity<List<PremiseDetailDTO>> createPremises(@RequestParam("material") List<String> materialIds,
|
||||
@RequestParam(value = "supplier", required = false) List<String> supplierIds,
|
||||
@RequestParam(name = "user_supplier", required = false) List<String> userSupplierIds,
|
||||
@RequestParam(name = "from_scratch", defaultValue = "true") boolean createEmpty) {
|
||||
|
||||
List<Integer> suppliers;
|
||||
List<Integer> userSuppliers;
|
||||
List<Integer> materials;
|
||||
|
||||
try {
|
||||
suppliers = supplierIds == null ? Collections.emptyList() : supplierIds.stream().map(Integer::parseInt).toList();
|
||||
userSuppliers = userSupplierIds == null ? Collections.emptyList() : userSupplierIds.stream().map(Integer::parseInt).toList();
|
||||
materials = materialIds.stream().map(Integer::parseInt).toList();
|
||||
|
||||
if(suppliers.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("Supplier ID must be greater than or equal to 1");
|
||||
|
||||
if(userSuppliers.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("User supplier ID must be greater than or equal to 1");
|
||||
|
||||
if(materials.stream().anyMatch(s -> s < 1))
|
||||
throw new InvalidArgumentException("Material ID must be greater than or equal to 1");
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidArgumentException("Invalid material or supplier ID provided. Suppliers: "
|
||||
+ supplierIds + ", userSuppliers: " + userSupplierIds + ", materials: " + materialIds + ".");
|
||||
}
|
||||
|
||||
@GetMapping("/edit")
|
||||
if (suppliers.isEmpty() && userSuppliers.isEmpty()) {
|
||||
throw new InvalidArgumentException("Either suppliers or userSuppliers must be provided. Suppliers: "
|
||||
+ supplierIds + ", userSuppliers: " + userSupplierIds + ".");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(premiseCreationService.createPremises(materials, suppliers, userSuppliers, createEmpty));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping({"/edit", "/edit/"})
|
||||
public ResponseEntity<List<PremiseDetailDTO>> getPremises(@RequestParam("premiss_ids") List<Integer> premissIds) {
|
||||
return ResponseEntity.ok(premisesServices.getPremises(premissIds));
|
||||
}
|
||||
|
||||
@PutMapping("/start")
|
||||
@PutMapping({"/start", "/start/"})
|
||||
public ResponseEntity<Integer> startCalculation(@RequestBody List<Integer> premiseIds) {
|
||||
return ResponseEntity.ok(premisesServices.startCalculation(premiseIds));
|
||||
}
|
||||
|
|
@ -81,50 +129,50 @@ public class PremiseController {
|
|||
* @param id The unique identifier of the operation (processing_id) to check its status.
|
||||
* @return A ResponseEntity with the bulk processing status payload.
|
||||
*/
|
||||
@GetMapping("/status/{processing_id}")
|
||||
public ResponseEntity<CalculationStatus> getUploadStatus(@PathVariable("processing_id") Integer id) {
|
||||
@GetMapping({"/status/{processing_id}", "/status/{processing_id}/"})
|
||||
public ResponseEntity<CalculationStatus> getCalculationStatus(@PathVariable("processing_id") Integer id) {
|
||||
return ResponseEntity.ok(premisesServices.getCalculationStatus(id));
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/packaging")
|
||||
@PutMapping({"/status/{processing_id}", "/status/{processing_id}/"})
|
||||
public ResponseEntity<HashMap<String, String>> updatePackaging(@RequestBody PackagingUpdateDTO packagingDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updatePackaging(packagingDTO));
|
||||
}
|
||||
|
||||
@PostMapping("/material")
|
||||
@PostMapping({"/material", "/material/"})
|
||||
public ResponseEntity<HashMap<String, String>> updateMaterial(@RequestBody MaterialUpdateDTO materialUpdateDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updateMaterial(materialUpdateDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/price")
|
||||
@PutMapping({"/price", "/price/"})
|
||||
public ResponseEntity<HashMap<String, String>> updatePrice(@RequestBody PriceUpdateDTO priceUpdateDTO) {
|
||||
return ResponseEntity.ok(premisesServices.updatePrice(priceUpdateDTO));
|
||||
}
|
||||
|
||||
@PostMapping("/destination")
|
||||
@PostMapping({"/destination", "/destination/"})
|
||||
public ResponseEntity<Map<Integer, DestinationDTO>> createDestination(@RequestBody DestinationCreateDTO destinationCreateDTO) {
|
||||
return ResponseEntity.ok(destinationService.createDestination(destinationCreateDTO));
|
||||
}
|
||||
|
||||
@GetMapping("/destination({id}")
|
||||
@GetMapping({"/destination({id}", "/destination({id}/"})
|
||||
public ResponseEntity<DestinationDTO> getDestination(@PathVariable Integer id) {
|
||||
return ResponseEntity.ok(destinationService.getDestination(id));
|
||||
}
|
||||
|
||||
@PutMapping("/destination({id}")
|
||||
@PutMapping({"/destination({id}", "/destination({id}/"})
|
||||
public ResponseEntity<Void> updateDestination(@PathVariable Integer id, @RequestParam DestinationUpdateDTO destinationUpdateDTO) {
|
||||
destinationService.updateDestination(id, destinationUpdateDTO);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@DeleteMapping("/destination({id}")
|
||||
@DeleteMapping({"/destination({id}", "/destination({id}/"})
|
||||
public ResponseEntity<Void> deleteDestination(@PathVariable Integer id) {
|
||||
destinationService.deleteDestinationById(id, false);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PutMapping("/supplier")
|
||||
@PutMapping({"/supplier", "/supplier/"})
|
||||
public ResponseEntity<List<PremiseDetailDTO>> setSupplier(@RequestBody SetDataDTO setSupplierDTO) {
|
||||
return ResponseEntity.ok(changeSupplierService.setSupplier(setSupplierDTO));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ import de.avatic.lcc.service.GeoApiService;
|
|||
import de.avatic.lcc.service.access.NodeService;
|
||||
import de.avatic.lcc.service.access.UserNodeService;
|
||||
import de.avatic.lcc.util.Check;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/nodes")
|
||||
@Validated
|
||||
public class NodeController {
|
||||
|
||||
private final NodeService nodeService;
|
||||
|
|
@ -31,7 +34,7 @@ public class NodeController {
|
|||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<List<NodeDTO>> listNodes(@RequestParam(required = false) String filter, @RequestParam(required = false) int page, @RequestParam(required = false) int limit) {
|
||||
public ResponseEntity<List<NodeDTO>> listNodes(@RequestParam(required = false) String filter, @RequestParam(defaultValue = "1") @Min(1) Integer page, @RequestParam(defaultValue = "20") @Min(1) Integer limit) {
|
||||
nodeService.listNodes(filter, page, limit);
|
||||
|
||||
SearchQueryResult<NodeDTO> nodes = nodeService.listNodes(filter, page, limit);
|
||||
|
|
@ -44,7 +47,7 @@ public class NodeController {
|
|||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<NodeDTO>> searchNodes(@RequestParam(required = false) String filter, @RequestParam(required = false) int limit, @RequestParam(name = "node_type", required = false) NodeType nodeType, @RequestParam(name = "include_user_node", defaultValue = "false", required = false) boolean includeUserNode) {
|
||||
public ResponseEntity<List<NodeDTO>> searchNodes(@RequestParam(required = false) String filter, @RequestParam(defaultValue = "1") @Min(1) int limit, @RequestParam(name = "node_type", required = false) NodeType nodeType, @RequestParam(name = "include_user_node", defaultValue = "false") boolean includeUserNode) {
|
||||
return ResponseEntity.ok(nodeService.searchNode(filter, limit, nodeType, includeUserNode));
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +61,11 @@ public class NodeController {
|
|||
return ResponseEntity.ok(nodeService.deleteNode(id));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Integer> updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) {
|
||||
Check.equals(id, node.getId());
|
||||
return ResponseEntity.ok(nodeService.updateNode(node));
|
||||
}
|
||||
// @PutMapping("/{id}")
|
||||
// public ResponseEntity<Integer> updateNode(@PathVariable Integer id, @RequestBody NodeUpdateDTO node) {
|
||||
// Check.equals(id, node.getId());
|
||||
// return ResponseEntity.ok(nodeService.updateNode(node));
|
||||
// }
|
||||
|
||||
@GetMapping("/locate")
|
||||
public ResponseEntity<LocateNodeDTO> locateNode(@RequestParam String address) {
|
||||
|
|
@ -70,7 +73,7 @@ public class NodeController {
|
|||
}
|
||||
|
||||
@PutMapping("/")
|
||||
public ResponseEntity<Void> addUserNode(@RequestParam AddUserNodeDTO node) {
|
||||
public ResponseEntity<Void> addUserNode(@RequestBody AddUserNodeDTO node) {
|
||||
userNodeService.addUserNode(node);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ 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.access.ValidityPeriodService;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
|
@ -17,6 +19,7 @@ import java.util.List;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/rates")
|
||||
@Validated
|
||||
public class RateController {
|
||||
|
||||
private final MatrixRateService matrixRateService;
|
||||
|
|
@ -50,8 +53,8 @@ public class RateController {
|
|||
*/
|
||||
@GetMapping("/container")
|
||||
public ResponseEntity<List<ContainerRateDTO>> listContainerRates(
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page,
|
||||
@RequestParam(name= "valid", required = false) Integer validityPeriodId,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validAt) {
|
||||
|
||||
|
|
@ -97,8 +100,8 @@ public class RateController {
|
|||
*/
|
||||
@GetMapping("/matrix")
|
||||
public ResponseEntity<List<MatrixRateDTO>> listMatrixRates(
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page,
|
||||
@RequestParam(required = false) Integer valid,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validAt) {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package de.avatic.lcc.controller.users;
|
|||
import de.avatic.lcc.dto.users.GroupDTO;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
import de.avatic.lcc.service.users.GroupService;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
|
@ -31,8 +32,8 @@ public class GroupController {
|
|||
* @return A ResponseEntity containing the list of groups and pagination headers.
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<List<GroupDTO>> listGroups(@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "1") int page) {
|
||||
public ResponseEntity<List<GroupDTO>> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page) {
|
||||
|
||||
SearchQueryResult<GroupDTO> groups = groupService.listGroups(page, limit);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ package de.avatic.lcc.controller.users;
|
|||
import de.avatic.lcc.dto.users.UserDTO;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
import de.avatic.lcc.service.users.UserService;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -15,6 +17,7 @@ import java.util.List;
|
|||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@Validated
|
||||
public class UserController {
|
||||
|
||||
|
||||
|
|
@ -33,8 +36,8 @@ public class UserController {
|
|||
*/
|
||||
@GetMapping("/")
|
||||
public ResponseEntity<List<UserDTO>> listUsers(
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "0") int page) {
|
||||
@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "0") @Min(1) int page) {
|
||||
|
||||
SearchQueryResult<UserDTO> users = userService.listUsers(page, limit);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.dto.calculation.edit;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.avatic.lcc.dto.calculation.DestinationDTO;
|
||||
import de.avatic.lcc.dto.generic.DimensionDTO;
|
||||
|
|
@ -59,6 +60,7 @@ public class PremiseDetailDTO {
|
|||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean getMixable() {
|
||||
return isMixable;
|
||||
}
|
||||
|
|
@ -67,6 +69,7 @@ public class PremiseDetailDTO {
|
|||
isMixable = mixable;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean getStackable() {
|
||||
return isStackable;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,74 @@ public class NodeUpdateDTO {
|
|||
private List<CountryDTO> outboundCountries;
|
||||
|
||||
public Integer getId() {
|
||||
return null;
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public CountryDTO getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(CountryDTO country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public List<NodeType> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
public void setTypes(List<NodeType> types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public LocationDTO getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(LocationDTO location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Boolean getDeprecated() {
|
||||
return isDeprecated;
|
||||
}
|
||||
|
||||
public void setDeprecated(Boolean deprecated) {
|
||||
isDeprecated = deprecated;
|
||||
}
|
||||
|
||||
public Map<Integer, NodeDTO> getPredecessors() {
|
||||
return predecessors;
|
||||
}
|
||||
|
||||
public void setPredecessors(Map<Integer, NodeDTO> predecessors) {
|
||||
this.predecessors = predecessors;
|
||||
}
|
||||
|
||||
public List<CountryDTO> getOutboundCountries() {
|
||||
return outboundCountries;
|
||||
}
|
||||
|
||||
public void setOutboundCountries(List<CountryDTO> outboundCountries) {
|
||||
this.outboundCountries = outboundCountries;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ public class NodeDetailDTO {
|
|||
@JsonProperty("outbound_countries")
|
||||
private List<CountryDTO> outboundCountries;
|
||||
|
||||
@JsonProperty("external_mapping_id")
|
||||
private String externalMappingId;
|
||||
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
|
|
@ -72,6 +75,7 @@ public class NodeDetailDTO {
|
|||
this.location = location;
|
||||
}
|
||||
|
||||
@JsonProperty("is_deprecated")
|
||||
public Boolean getDeprecated() {
|
||||
return isDeprecated;
|
||||
}
|
||||
|
|
@ -95,4 +99,12 @@ public class NodeDetailDTO {
|
|||
public void setOutboundCountries(List<CountryDTO> outboundCountries) {
|
||||
this.outboundCountries = outboundCountries;
|
||||
}
|
||||
|
||||
public void setExternalMappingId(String externalMappingId) {
|
||||
this.externalMappingId = externalMappingId;
|
||||
}
|
||||
|
||||
public String getExternalMappingId() {
|
||||
return externalMappingId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public class ContainerRateDTO {
|
|||
private Integer id;
|
||||
|
||||
private NodeDTO origin;
|
||||
|
||||
private NodeDTO destination;
|
||||
|
||||
private TransportType type;
|
||||
|
|
@ -21,6 +22,7 @@ public class ContainerRateDTO {
|
|||
@JsonProperty("lead_time")
|
||||
private Number leadTime;
|
||||
|
||||
@JsonProperty("validity_period")
|
||||
private ValidityPeriodDTO validityPeriod;
|
||||
|
||||
public Integer getId() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package de.avatic.lcc.dto.generic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,12 +13,16 @@ public class NodeDTO {
|
|||
private List<NodeType> types;
|
||||
private LocationDTO location;
|
||||
|
||||
@JsonProperty("external_mapping_id")
|
||||
private String externalMappingId;
|
||||
|
||||
@JsonProperty("is_user_node")
|
||||
private Boolean isUserNode;
|
||||
|
||||
@JsonProperty("is_deprecated")
|
||||
private Boolean isDeprecated;
|
||||
|
||||
@JsonProperty("is_user_node")
|
||||
public Boolean getUserNode() {
|
||||
return isUserNode;
|
||||
}
|
||||
|
|
@ -83,4 +87,12 @@ public class NodeDTO {
|
|||
public void setLocation(LocationDTO location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getExternalMappingId() {
|
||||
return externalMappingId;
|
||||
}
|
||||
|
||||
public void setExternalMappingId(String externalMappingId) {
|
||||
this.externalMappingId = externalMappingId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package de.avatic.lcc.model.nodes;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
|
|
@ -9,6 +10,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
|
||||
@Validated
|
||||
public class Node {
|
||||
|
||||
private Integer id;
|
||||
|
|
|
|||
|
|
@ -30,8 +30,17 @@ public class PremiseListEntry {
|
|||
private boolean supplierIsSource;
|
||||
private boolean supplierIsIntermediate;
|
||||
|
||||
private Boolean isDeprecated;
|
||||
|
||||
private Integer ownerId;
|
||||
|
||||
public Boolean getDeprecated() {
|
||||
return isDeprecated;
|
||||
}
|
||||
|
||||
public void setDeprecated(Boolean deprecated) {
|
||||
isDeprecated = deprecated;
|
||||
}
|
||||
|
||||
public BigDecimal getSupplierGeoLongitude() {
|
||||
return supplierGeoLongitude;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Repository
|
||||
public class MaterialRepository {
|
||||
|
|
@ -146,6 +147,33 @@ public class MaterialRepository {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all IDs from the input list that don't exist in the material table
|
||||
* @param ids List of integers to check
|
||||
* @return List of integers that don't have corresponding rows in material table
|
||||
*/
|
||||
public List<Integer> findMissingIds(List<Integer> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// Create placeholders for the IN clause
|
||||
String placeholders = ids.stream()
|
||||
.map(id -> "?")
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
// Query to find existing IDs
|
||||
String sql = "SELECT id FROM material WHERE id IN (" + placeholders + ")";
|
||||
|
||||
// Execute query and get existing IDs
|
||||
Set<Integer> existingIds = new HashSet<>(jdbcTemplate.queryForList(sql, Integer.class, ids.toArray()));
|
||||
|
||||
// Return IDs that are in the input list but not in the database
|
||||
return ids.stream()
|
||||
.filter(id -> !existingIds.contains(id))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static class MaterialMapper implements RowMapper<Material> {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import de.avatic.lcc.dto.generic.NodeType;
|
|||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
|
@ -31,9 +32,11 @@ public class NodeRepository {
|
|||
FROM node
|
||||
WHERE node.id = ?""";
|
||||
|
||||
var node = jdbcTemplate.queryForObject(query, new NodeMapper(), id);
|
||||
var node = jdbcTemplate.query(query, new NodeMapper(), id);
|
||||
|
||||
return Optional.ofNullable(node);
|
||||
if (node.isEmpty()) return Optional.empty();
|
||||
|
||||
return Optional.ofNullable(node.getFirst());
|
||||
}
|
||||
|
||||
public List<Node> getByIds(List<Integer> nodeIds) {
|
||||
|
|
@ -42,9 +45,9 @@ public class NodeRepository {
|
|||
String query = """
|
||||
SELECT *
|
||||
FROM node
|
||||
WHERE node.id IN (?)""";
|
||||
WHERE node.id IN (""" + placeholders + ")";
|
||||
|
||||
return jdbcTemplate.query(query, new NodeMapper(), nodeIds.toArray());
|
||||
return nodeIds.isEmpty() ? Collections.emptyList() : jdbcTemplate.query(query, new NodeMapper(), nodeIds.toArray());
|
||||
}
|
||||
|
||||
private List<Map<Integer, Integer>> getPredecessorsOf(Integer id) {
|
||||
|
|
@ -54,8 +57,11 @@ public class NodeRepository {
|
|||
""";
|
||||
|
||||
return jdbcTemplate.query(queryChains, (chainRs, rowNum) -> {
|
||||
|
||||
Integer chainId = chainRs.getInt("id");
|
||||
|
||||
String query = """
|
||||
SELECT entry.node_id AS predecessor , entry.sequence_number as sequence_number
|
||||
SELECT entry.node_id AS predecessor_node_id , entry.sequence_number as sequence_number
|
||||
FROM node_predecessor_entry AS entry
|
||||
WHERE entry.node_predecessor_chain_id = ? ORDER BY entry.sequence_number""";
|
||||
|
||||
|
|
@ -67,12 +73,10 @@ public class NodeRepository {
|
|||
}
|
||||
|
||||
return predecessors;
|
||||
}, id);
|
||||
}, chainId);
|
||||
}, id);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
private Collection<Integer> getOutboundCountriesOf(Integer id) {
|
||||
String query = """
|
||||
SELECT outbound_country_mapping.country_id
|
||||
|
|
@ -104,13 +108,15 @@ public class NodeRepository {
|
|||
private String buildCountQuery(String filter, boolean excludeDeprecated) {
|
||||
StringBuilder queryBuilder = new StringBuilder("""
|
||||
SELECT COUNT(*)
|
||||
FROM WHERE 1=1""");
|
||||
FROM node
|
||||
LEFT JOIN country ON country.id = node.country_id
|
||||
WHERE 1=1""");
|
||||
|
||||
if (excludeDeprecated) {
|
||||
queryBuilder.append(" AND is_deprecated = FALSE");
|
||||
queryBuilder.append(" AND node.is_deprecated = FALSE");
|
||||
}
|
||||
if (filter != null) {
|
||||
queryBuilder.append(" AND (name LIKE ? OR address LIKE ? OR country_iso_code LIKE ?)");
|
||||
queryBuilder.append(" AND (node.name LIKE ? OR node.address LIKE ? OR country.iso_code LIKE ?)");
|
||||
}
|
||||
|
||||
return queryBuilder.toString();
|
||||
|
|
@ -118,20 +124,19 @@ public class NodeRepository {
|
|||
|
||||
private String buildQuery(String filter, Boolean excludeDeprecated, SearchQueryPagination searchQueryPagination) {
|
||||
StringBuilder queryBuilder = new StringBuilder("""
|
||||
SELECT chain.id AS id, chain.name AS name, chain.address as address, chain.is_source as is_source,
|
||||
chain.is_destination as is_destination, chain.is_intermediate as is_intermediate, chain.country_id as country_id, chain.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 chain
|
||||
LEFT JOIN country ON country.id = chain.country_id
|
||||
SELECT *
|
||||
FROM node
|
||||
LEFT JOIN country ON country.id = node.country_id
|
||||
WHERE 1=1
|
||||
""");
|
||||
|
||||
if (excludeDeprecated) {
|
||||
queryBuilder.append(" AND is_deprecated = FALSE");
|
||||
queryBuilder.append(" AND node.is_deprecated = FALSE");
|
||||
}
|
||||
if (filter != null) {
|
||||
queryBuilder.append(" AND (name LIKE ? OR address LIKE ? OR country_iso_code LIKE ?)");
|
||||
queryBuilder.append(" AND (node.name LIKE ? OR node.address LIKE ? OR country.iso_code LIKE ?)");
|
||||
}
|
||||
queryBuilder.append(" ORDER BY id LIMIT ? OFFSET ?");
|
||||
queryBuilder.append(" ORDER BY node.id LIMIT ? OFFSET ?");
|
||||
return queryBuilder.toString();
|
||||
}
|
||||
|
||||
|
|
@ -142,21 +147,39 @@ public class NodeRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<Integer> update(Node chain) {
|
||||
public Optional<Integer> update(Node node) {
|
||||
|
||||
throw new NotImplementedException("Update of nodes is not yet implemented!");
|
||||
//TODO update predecessors and outbound_countries too
|
||||
//TODO implement correctly
|
||||
|
||||
//TODO if chain is updated set all linked RouteNodes to outdated!
|
||||
//TODO if node is updated set all linked RouteNodes to outdated!
|
||||
|
||||
String query = "UPDATE node SET name = ?, address = ?, country_id = ?, is_source = ?, is_destination = ?, is_intermediate = ?, predecessor_required = ? WHERE id = ?";
|
||||
return Optional.ofNullable(jdbcTemplate.update(query, chain.getId()) == 0 ? null : chain.getId());
|
||||
// String query = "UPDATE node SET name = ?, address = ?, country_id = ?, is_source = ?, is_destination = ?, is_intermediate = ?, predecessor_required = ? WHERE id = ?";
|
||||
//
|
||||
// var nodeId = jdbcTemplate.update(query,
|
||||
// node.getName(),
|
||||
// node.getAddress(),
|
||||
// node.getCountryId(),
|
||||
// node.getSource(),
|
||||
// node.getDestination(),
|
||||
// node.getIntermediate(),
|
||||
// node.getPredecessorRequired(),
|
||||
// node.getId()) == 0 ? null : node.getId();
|
||||
//
|
||||
// return Optional.ofNullable(nodeId);
|
||||
}
|
||||
|
||||
public List<Node> searchNode(String filter, int limit, NodeType nodeType, boolean excludeDeprecated) {
|
||||
StringBuilder queryBuilder = new StringBuilder().append("SELECT * FROM chain WHERE (name LIKE ? OR address LIKE ?)");
|
||||
StringBuilder queryBuilder = new StringBuilder().append("SELECT * FROM node WHERE (name LIKE ? OR address LIKE ?)");
|
||||
|
||||
if (nodeType != null) {
|
||||
queryBuilder.append(" AND node_type = ?");
|
||||
if (nodeType.equals(NodeType.SOURCE))
|
||||
queryBuilder.append(" AND is_source = true");
|
||||
if (nodeType.equals(NodeType.DESTINATION))
|
||||
queryBuilder.append(" AND is_destination = true");
|
||||
if (nodeType.equals(NodeType.INTERMEDIATE))
|
||||
queryBuilder.append(" AND is_intermediate = true");
|
||||
}
|
||||
|
||||
if (excludeDeprecated) {
|
||||
|
|
@ -165,15 +188,12 @@ public class NodeRepository {
|
|||
|
||||
queryBuilder.append(" 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);
|
||||
}
|
||||
|
||||
public List<Node> listAllNodes(boolean onlySources) {
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT * FROM chain");
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT * FROM node");
|
||||
if (onlySources) {
|
||||
queryBuilder.append(" WHERE is_source = true");
|
||||
}
|
||||
|
|
@ -188,9 +208,9 @@ public class NodeRepository {
|
|||
FROM node
|
||||
WHERE node.external_mapping_id = ?""";
|
||||
|
||||
var chain = jdbcTemplate.queryForObject(query, new NodeMapper(), mappingId);
|
||||
var node = jdbcTemplate.queryForObject(query, new NodeMapper(), mappingId);
|
||||
|
||||
return Optional.ofNullable(chain);
|
||||
return Optional.ofNullable(node);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
@ -358,7 +378,7 @@ public class NodeRepository {
|
|||
|
||||
var node = jdbcTemplate.query(query, new NodeMapper(), id);
|
||||
|
||||
if(node.isEmpty()) {
|
||||
if (node.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,19 +91,20 @@ public class DestinationRepository {
|
|||
public Integer insert(Destination destination) {
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
String query = "INSERT INTO premise_destination (annual_amount, premise_id, destination_node_id, rate_d2d, lead_time_d2d, is_d2d, repacking_cost, handling_cost, disposal_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String query = "INSERT INTO premise_destination (annual_amount, premise_id, destination_node_id, country_id, rate_d2d, lead_time_d2d, is_d2d, repacking_cost, handling_cost, disposal_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
var ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
ps.setInt(1, destination.getAnnualAmount());
|
||||
ps.setInt(2, destination.getPremiseId());
|
||||
ps.setInt(3, destination.getDestinationNodeId());
|
||||
ps.setBigDecimal(4, destination.getRateD2d());
|
||||
ps.setInt(5, destination.getLeadTimeD2d());
|
||||
ps.setBoolean(6, destination.getD2d());
|
||||
ps.setBigDecimal(7, destination.getRepackingCost());
|
||||
ps.setBigDecimal(8, destination.getHandlingCost());
|
||||
ps.setBigDecimal(9, destination.getDisposalCost());
|
||||
ps.setInt(4, destination.getCountryId());
|
||||
ps.setBigDecimal(5, destination.getRateD2d());
|
||||
ps.setInt(6, destination.getLeadTimeD2d());
|
||||
ps.setBoolean(7, destination.getD2d());
|
||||
ps.setBigDecimal(8, destination.getRepackingCost());
|
||||
ps.setBigDecimal(9, destination.getHandlingCost());
|
||||
ps.setBigDecimal(10, destination.getDisposalCost());
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import de.avatic.lcc.model.utils.DimensionUnit;
|
|||
import de.avatic.lcc.model.utils.WeightUnit;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
|
|
@ -105,7 +106,7 @@ public class PremiseRepository {
|
|||
return new Object[]{
|
||||
userId,
|
||||
wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter,
|
||||
wildcardFilter, wildcardFilter, wildcardFilter,
|
||||
wildcardFilter, wildcardFilter,
|
||||
pagination.getLimit(), pagination.getOffset()
|
||||
};
|
||||
}
|
||||
|
|
@ -113,7 +114,7 @@ public class PremiseRepository {
|
|||
private Object[] createFilterParamsForCount(Integer userId, String wildcardFilter) {
|
||||
return new Object[]{
|
||||
userId,
|
||||
wildcardFilter, wildcardFilter, wildcardFilter, wildcardFilter,
|
||||
wildcardFilter, wildcardFilter, wildcardFilter,
|
||||
wildcardFilter, wildcardFilter, wildcardFilter
|
||||
};
|
||||
}
|
||||
|
|
@ -159,9 +160,9 @@ public class PremiseRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension dimensionEntity, Boolean stackable, Boolean mixable) {
|
||||
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension hu, PackagingDimension shu, Boolean stackable, Boolean mixable) {
|
||||
|
||||
if (premiseIds == null || premiseIds.isEmpty() || userId == null || dimensionEntity == null) {
|
||||
if (premiseIds == null || premiseIds.isEmpty() || userId == null || hu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -169,13 +170,54 @@ public class PremiseRepository {
|
|||
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("length", hu.getLength());
|
||||
params.addValue("height", hu.getHeight());
|
||||
params.addValue("width", hu.getWidth());
|
||||
params.addValue("weight", hu.getWeight());
|
||||
params.addValue("dimensionUnit", hu.getDimensionUnit().name());
|
||||
params.addValue("weightUnit", hu.getWeightUnit().name());
|
||||
params.addValue("unitCount", hu.getContentUnitCount()*shu.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 = :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);
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updatePackaging(List<Integer> premiseIds, Integer userId, PackagingDimension hu, Boolean stackable, Boolean mixable) {
|
||||
|
||||
if (premiseIds == null || premiseIds.isEmpty() || userId == null || hu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isStackable = stackable != null ? stackable : false;
|
||||
boolean isMixable = mixable != null ? mixable : false;
|
||||
|
||||
MapSqlParameterSource params = new MapSqlParameterSource();
|
||||
params.addValue("length", hu.getLength());
|
||||
params.addValue("height", hu.getHeight());
|
||||
params.addValue("width", hu.getWidth());
|
||||
params.addValue("weight", hu.getWeight());
|
||||
params.addValue("dimensionUnit", hu.getDimensionUnit().name());
|
||||
params.addValue("weightUnit", hu.getWeightUnit().name());
|
||||
params.addValue("unitCount", hu.getContentUnitCount());
|
||||
params.addValue("stackable", isStackable);
|
||||
params.addValue("mixable", isMixable);
|
||||
params.addValue("userId", userId);
|
||||
|
|
@ -231,7 +273,7 @@ public class PremiseRepository {
|
|||
|
||||
String query = """
|
||||
UPDATE premise
|
||||
SET price = ?,
|
||||
SET material_cost = ?,
|
||||
is_fca_enabled = ?,
|
||||
oversea_share = ?
|
||||
WHERE user_id = ? AND id IN (""" + placeholders + ")";
|
||||
|
|
@ -246,12 +288,19 @@ public class PremiseRepository {
|
|||
ps.setInt(4, userId);
|
||||
|
||||
for (int parameterIndex = 0; parameterIndex < premiseIds.size(); parameterIndex++) {
|
||||
ps.setInt(parameterIndex + 6, premiseIds.get(parameterIndex));
|
||||
ps.setInt(parameterIndex + 5, premiseIds.get(parameterIndex));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateTariffRate(Integer premiseId, Number tariffRate) {
|
||||
String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?";
|
||||
|
||||
jdbcTemplate.update(sql, tariffRate, premiseId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all draft premises that would conflict with updating the material or supplier for a given premise.
|
||||
|
|
@ -263,14 +312,14 @@ public class PremiseRepository {
|
|||
* @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 chain 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.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<Premise> getCollidingPremisesOnChange(Integer userId, Integer premiseId, Integer materialId, Integer supplierId) {
|
||||
|
||||
if( premiseId == null || materialId == null || supplierId == null )
|
||||
if (premiseId == null || materialId == null || supplierId == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
String sql = "SELECT * FROM premise " +
|
||||
|
|
@ -285,7 +334,7 @@ public class PremiseRepository {
|
|||
|
||||
@Transactional
|
||||
public void deletePremisesById(List<Integer> premiseIds) {
|
||||
if(premiseIds == null || premiseIds.isEmpty()) return;
|
||||
if (premiseIds == null || premiseIds.isEmpty()) return;
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseIds.size(), "?"));
|
||||
String query = """
|
||||
|
|
@ -295,19 +344,13 @@ public class PremiseRepository {
|
|||
jdbcTemplate.update(query, premiseIds.toArray());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateTariffRate(Integer premiseId, Number tariffRate) {
|
||||
String sql = "UPDATE premise SET tariff_rate = ? WHERE id = ?";
|
||||
|
||||
jdbcTemplate.update(sql, tariffRate, premiseId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setSupplierId(List<Integer> premiseId, Node supplier, boolean userSupplierNode) {
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
|
||||
String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ?, geo_lat = ?, geo_lng = ?, country_id = ? WHERE id IN ("+placeholders+")";
|
||||
String sql = "UPDATE premise SET supplier_node_id = ?, user_supplier_node_id = ?, geo_lat = ?, geo_lng = ?, country_id = ? WHERE id IN (" + placeholders + ")";
|
||||
|
||||
if(userSupplierNode) {
|
||||
if (userSupplierNode) {
|
||||
jdbcTemplate.update(sql, 0, supplier.getId(), supplier.getGeoLat(), supplier.getGeoLng(), supplier.getCountryId(), premiseId.toArray());
|
||||
} else {
|
||||
jdbcTemplate.update(sql, supplier.getId(), 0, supplier.getGeoLat(), supplier.getGeoLng(), supplier.getCountryId(), premiseId.toArray());
|
||||
|
|
@ -317,11 +360,32 @@ public class PremiseRepository {
|
|||
@Transactional
|
||||
public void setMaterialId(List<Integer> premiseId, Integer materialId) {
|
||||
String placeholders = String.join(",", Collections.nCopies(premiseId.size(), "?"));
|
||||
String sql = "UPDATE premise SET material_id = ? WHERE id IN ("+placeholders+")";
|
||||
String sql = "UPDATE premise SET material_id = ? WHERE id IN (" + placeholders + ")";
|
||||
|
||||
jdbcTemplate.update(sql, materialId, premiseId.toArray());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Premise> findByMaterialIdAndSupplierId(Integer materialId, Integer supplierId, Integer userSupplierId, Integer userId) {
|
||||
|
||||
if((supplierId == null && userSupplierId == null) || (supplierId != null && userSupplierId != null))
|
||||
throw new DatabaseException("Either supplierId or userSupplierId must be null");
|
||||
|
||||
String query = """
|
||||
SELECT * FROM premise
|
||||
WHERE material_id = ? AND (user_id = ?
|
||||
""";
|
||||
|
||||
if(userSupplierId != null)
|
||||
query += ") AND user_supplier_node_id = ?";
|
||||
else query += "OR state = 'COMPLETED') AND supplier_node_id = ?";
|
||||
|
||||
|
||||
query += " ORDER BY updated_at DESC";
|
||||
|
||||
return jdbcTemplate.query(query, new PremiseMapper(), materialId, userId, userSupplierId != null ? userSupplierId : supplierId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Premise> getPremisesByMaterialIdsAndSupplierIds(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, Integer userId, boolean draftsOnly) {
|
||||
|
||||
|
|
@ -336,7 +400,7 @@ public class PremiseRepository {
|
|||
WHERE 1=1
|
||||
""");
|
||||
|
||||
if(draftsOnly)
|
||||
if (draftsOnly)
|
||||
query.append(" AND user_id = ? AND state = ?");
|
||||
|
||||
// Append conditions for IDs
|
||||
|
|
@ -358,7 +422,7 @@ public class PremiseRepository {
|
|||
// Combine parameters for prepared statement
|
||||
var params = new ArrayList<>();
|
||||
|
||||
if(draftsOnly) {
|
||||
if (draftsOnly) {
|
||||
params.add(userId);
|
||||
params.add(PremiseState.DRAFT.name());
|
||||
}
|
||||
|
|
@ -371,20 +435,23 @@ public class PremiseRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public Integer insert(Integer materialId, Integer supplierId, Integer userSupplierId, Integer userId) {
|
||||
public Integer insert(Integer materialId, Integer supplierId, Integer userSupplierId, BigDecimal geoLat, BigDecimal geoLng, Integer countryId, Integer userId) {
|
||||
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, created_at, updated_at)" +
|
||||
" VALUES (?, ?, ?, ?, 'DRAFT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||
"INSERT INTO premise (material_id, supplier_node_id, user_supplier_node_id, user_id, state, created_at, updated_at, geo_lat, geo_lng, country_id)" +
|
||||
" VALUES (?, ?, ?, ?, 'DRAFT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?)",
|
||||
Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
ps.setInt(1, materialId);
|
||||
ps.setObject(2, supplierId);
|
||||
ps.setObject(3, userSupplierId);
|
||||
ps.setInt(4, userId);
|
||||
ps.setBigDecimal(5, geoLat);
|
||||
ps.setBigDecimal(6, geoLng);
|
||||
ps.setInt(7, countryId);
|
||||
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
|
@ -413,25 +480,39 @@ public class PremiseRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public List<Integer> findAssociatedUserSuppliers(List<Integer> materialIds) {
|
||||
public List<Integer> findAssociatedUserSuppliers(List<Integer> materialIds, Integer userId) {
|
||||
|
||||
if (materialIds == null || materialIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> params = new ArrayList<>(materialIds);
|
||||
params.add(userId);
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(materialIds.size(), "?"));
|
||||
String query = """
|
||||
SELECT DISTINCT user_supplier_node_id
|
||||
FROM premise
|
||||
WHERE material_id IN (""" + placeholders + ")";
|
||||
SELECT DISTINCT p.user_supplier_node_id AS user_supplier_node_id
|
||||
FROM premise p
|
||||
JOIN sys_user_node sun ON p.user_supplier_node_id = sun.id
|
||||
WHERE p.material_id IN (""" + placeholders + ") AND sun.user_id = ? ";
|
||||
|
||||
return jdbcTemplate.query(
|
||||
query,
|
||||
(rs, rowNum) -> rs.getInt("user_supplier_node_id"),
|
||||
materialIds.toArray()
|
||||
params.toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public void setPackagingId(Integer id, Integer packagingId) {
|
||||
String query = "UPDATE premise SET packaging_id = ? WHERE id = ?";
|
||||
|
||||
var affectedRows = jdbcTemplate.update(query, packagingId, id);
|
||||
|
||||
if(affectedRows == 0)
|
||||
throw new DatabaseException("No premise found with id " + id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates SQL query building logic
|
||||
*/
|
||||
|
|
@ -439,13 +520,13 @@ public class PremiseRepository {
|
|||
private static final String BASE_JOIN_QUERY = """
|
||||
FROM premise AS p
|
||||
LEFT JOIN material as m ON p.material_id = m.id
|
||||
LEFT JOIN chain as n ON p.supplier_node_id = n.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 = ?""";
|
||||
WHERE p.user_id = ?""";
|
||||
|
||||
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 ?)";
|
||||
" AND (n.name LIKE ? OR n.external_mapping_id LIKE ? OR " +
|
||||
"user_n.name LIKE ? OR m.name LIKE ? OR m.name LIKE ? OR m.part_number LIKE ?)";
|
||||
|
||||
private String filter;
|
||||
private Boolean deleted;
|
||||
|
|
@ -481,7 +562,19 @@ public class PremiseRepository {
|
|||
|
||||
public String buildSelectQuery() {
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
queryBuilder.append("SELECT * ").append(BASE_JOIN_QUERY);
|
||||
queryBuilder.append("""
|
||||
SELECT p.id as 'p.id', p.state as 'p.state', p.user_id as 'p.user_id',
|
||||
p.created_at as 'p.created_at', p.updated_at as 'p.updated_at',
|
||||
p.supplier_node_id as 'p.supplier_node_id', p.user_supplier_node_id as 'p.user_supplier_node_id',
|
||||
m.id as 'm.id', m.name as 'm.name', m.is_deprecated as 'm.is_deprecated',
|
||||
m.hs_code as 'm.hs_code', m.part_number as 'm.part_number',
|
||||
n.id as 'n.id', n.name as 'n.name', n.address as 'n.address',
|
||||
n.country_id as 'n.country_id', n.geo_lat as 'n.geo_lat', n.geo_lng as 'n.geo_lng',
|
||||
n.is_destination as 'n.is_destination', n.is_intermediate as 'n.is_intermediate', n.is_source as 'n.is_source',
|
||||
n.is_deprecated as 'n.is_deprecated',
|
||||
user_n.id as 'user_n.id', user_n.name as 'user_n.name', user_n.address as 'user_n.address',
|
||||
user_n.country_id as 'user_n.country_id', user_n.geo_lat as 'user_n.geo_lat', user_n.geo_lng as 'user_n.geo_lng'
|
||||
""").append(BASE_JOIN_QUERY);
|
||||
appendConditions(queryBuilder);
|
||||
queryBuilder.append(" ORDER BY p.updated_at DESC");
|
||||
queryBuilder.append(" LIMIT ? OFFSET ?");
|
||||
|
|
@ -493,14 +586,39 @@ public class PremiseRepository {
|
|||
queryBuilder.append(FILTER_CONDITION);
|
||||
}
|
||||
|
||||
appendBooleanCondition(queryBuilder, deleted, "p.deleted");
|
||||
appendBooleanCondition(queryBuilder, archived, "p.archived");
|
||||
appendBooleanCondition(queryBuilder, done, "p.done");
|
||||
if (deleted != null && deleted || archived != null && archived || done != null && done) {
|
||||
boolean concat = false;
|
||||
|
||||
queryBuilder.append(" AND (");
|
||||
|
||||
if (deleted != null && deleted) {
|
||||
queryBuilder.append(" p.state").append(" = 'DELETED'");
|
||||
concat = true;
|
||||
}
|
||||
|
||||
if (archived != null && archived) {
|
||||
if (concat)
|
||||
queryBuilder.append(" OR ");
|
||||
queryBuilder.append(" p.state").append(" = 'ARCHIVED'");
|
||||
concat = true;
|
||||
}
|
||||
|
||||
if (done != null && done) {
|
||||
if (concat)
|
||||
queryBuilder.append(" OR ");
|
||||
queryBuilder.append(" p.state").append(" = 'COMPLETED'");
|
||||
concat = true;
|
||||
}
|
||||
|
||||
queryBuilder.append(")");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void appendBooleanCondition(StringBuilder queryBuilder, Boolean condition, String field) {
|
||||
if (condition != null && condition) {
|
||||
queryBuilder.append(" AND ").append(field).append(" = TRUE");
|
||||
queryBuilder.append(" OR ").append(field).append(" = TRUE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -516,7 +634,7 @@ public class PremiseRepository {
|
|||
// Map material
|
||||
entity.setMaterial(mapMaterial(rs));
|
||||
|
||||
// Map supplier (either regular chain or user chain)
|
||||
// Map supplier (either regular node or user node)
|
||||
mapSupplierProperties(entity, rs);
|
||||
|
||||
return entity;
|
||||
|
|
@ -544,25 +662,34 @@ public class PremiseRepository {
|
|||
|
||||
private void mapSupplierProperties(PremiseListEntry entity, ResultSet rs) throws SQLException {
|
||||
if (rs.getInt("p.supplier_node_id") != 0) {
|
||||
mapSupplier(entity, rs, "n");
|
||||
mapSupplier(false, entity, rs, "n");
|
||||
} else if (rs.getInt("p.user_supplier_node_id") != 0) {
|
||||
mapSupplier(entity, rs, "user_n");
|
||||
mapSupplier(true, entity, rs, "user_n");
|
||||
entity.setUserSupplier(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void mapSupplier(PremiseListEntry entity, ResultSet rs, String tablePrefix) throws SQLException {
|
||||
private void mapSupplier(boolean isUserNode, 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.setSupplierGeoLatitude(rs.getBigDecimal(tablePrefix + ".geo_lat"));
|
||||
entity.setSupplierGeoLongitude(rs.getBigDecimal(tablePrefix + ".geo_lng"));
|
||||
|
||||
if (!isUserNode) {
|
||||
entity.setSupplierIsDestination(rs.getBoolean(tablePrefix + ".is_destination"));
|
||||
entity.setSupplierIsIntermediate(rs.getBoolean(tablePrefix + ".is_intermediate"));
|
||||
entity.setSupplierIsSource(rs.getBoolean(tablePrefix + ".is_source"));
|
||||
entity.setDeprecated(rs.getBoolean(tablePrefix + ".is_deprecated"));
|
||||
} else {
|
||||
entity.setSupplierIsDestination(false);
|
||||
entity.setSupplierIsIntermediate(false);
|
||||
entity.setSupplierIsSource(true);
|
||||
entity.setDeprecated(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,36 +701,78 @@ public class PremiseRepository {
|
|||
entity.setId(rs.getInt("id"));
|
||||
|
||||
entity.setPackagingId(rs.getInt("packaging_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setPackagingId(null);
|
||||
|
||||
entity.setMaterialId(rs.getInt("material_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setMaterialId(null);
|
||||
|
||||
entity.setSupplierNodeId(rs.getInt("supplier_node_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setSupplierNodeId(null);
|
||||
|
||||
entity.setUserSupplierNodeId(rs.getInt("user_supplier_node_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setUserSupplierNodeId(null);
|
||||
|
||||
entity.setLocation(new Location(rs.getBigDecimal("geo_lng").doubleValue(), rs.getBigDecimal("geo_lat").doubleValue()));
|
||||
|
||||
entity.setCountryId(rs.getInt("country_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setCountryId(null);
|
||||
|
||||
entity.setUserId(rs.getInt("user_id"));
|
||||
if(rs.wasNull())
|
||||
entity.setUserId(null);
|
||||
|
||||
entity.setMaterialCost(rs.getBigDecimal("material_cost"));
|
||||
|
||||
entity.setHsCode(rs.getString("hs_code"));
|
||||
|
||||
entity.setCustomRate(rs.getBigDecimal("tariff_rate"));
|
||||
|
||||
entity.setFcaEnabled(rs.getBoolean("is_fca_enabled"));
|
||||
if(rs.wasNull())
|
||||
entity.setFcaEnabled(null);
|
||||
|
||||
entity.setOverseaShare(rs.getBigDecimal("oversea_share"));
|
||||
|
||||
entity.setIndividualHuHeight(rs.getInt("individual_hu_height"));
|
||||
if(rs.wasNull())
|
||||
entity.setIndividualHuHeight(null);
|
||||
|
||||
entity.setIndividualHuWidth(rs.getInt("individual_hu_width"));
|
||||
if(rs.wasNull())
|
||||
entity.setIndividualHuWidth(null);
|
||||
|
||||
entity.setIndividualHuLength(rs.getInt("individual_hu_length"));
|
||||
if(rs.wasNull())
|
||||
entity.setIndividualHuLength(null);
|
||||
|
||||
entity.setIndividualHuWeight(rs.getInt("individual_hu_weight"));
|
||||
if(rs.wasNull())
|
||||
entity.setIndividualHuWeight(null);
|
||||
|
||||
entity.setHuDisplayedDimensionUnit(DimensionUnit.valueOf(rs.getString("hu_displayed_dimension_unit")));
|
||||
entity.setHuDisplayedWeightUnit(WeightUnit.valueOf(rs.getString("hu_displayed_weight_unit")));
|
||||
|
||||
entity.setHuStackable(rs.getBoolean("hu_stackable"));
|
||||
if(rs.wasNull())
|
||||
entity.setHuStackable(null);
|
||||
|
||||
entity.setHuMixable(rs.getBoolean("hu_mixable"));
|
||||
if(rs.wasNull())
|
||||
entity.setHuMixable(null);
|
||||
|
||||
entity.setHuUnitCount(rs.getInt("hu_unit_count"));
|
||||
if(rs.wasNull())
|
||||
entity.setHuUnitCount(null);
|
||||
|
||||
entity.setState(PremiseState.valueOf(rs.getString("state")));
|
||||
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
|
||||
entity.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ public class RouteNodeRepository {
|
|||
}
|
||||
|
||||
public Integer insert(RouteNode node) {
|
||||
String sql = "INSERT INTO premise_route_node (name, address, geo_lat, geo_lng, is_destination, is_intermediate, " +
|
||||
"is_source, node_id, user_node_id, is_outdated) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String sql = """
|
||||
INSERT INTO premise_route_node (name, address, geo_lat, geo_lng, is_destination, is_intermediate,
|
||||
is_source, node_id, user_node_id, is_outdated, country_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
|
|
@ -64,6 +66,7 @@ public class RouteNodeRepository {
|
|||
ps.setObject(8, node.getNodeId(), java.sql.Types.INTEGER);
|
||||
ps.setObject(9, node.getUserNodeId(), java.sql.Types.INTEGER);
|
||||
ps.setBoolean(10, Boolean.TRUE.equals(node.getOutdated()));
|
||||
ps.setObject(11, node.getCountryId(), java.sql.Types.INTEGER);
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
|
|
@ -71,7 +74,9 @@ public class RouteNodeRepository {
|
|||
}
|
||||
|
||||
public Optional<RouteNode> getFromNodeBySectionId(Integer id) {
|
||||
String sql = "SELECT * FROM premise_route_section LEFT JOIN premise_route_node ON premise_route_node.id = premise_route_section.from_route_node_id WHERE premise_route_section.id = ?";
|
||||
String sql = """
|
||||
SELECT * FROM premise_route_section LEFT JOIN premise_route_node ON premise_route_node.id = premise_route_section.from_route_node_id WHERE premise_route_section.id = ?
|
||||
""";
|
||||
|
||||
var node = jdbcTemplate.query(sql, new RouteNodeMapper(), id);
|
||||
|
||||
|
|
@ -112,6 +117,8 @@ public class RouteNodeRepository {
|
|||
entity.setNodeId(rs.getInt("node_id"));
|
||||
entity.setUserNodeId(rs.getInt("user_node_id"));
|
||||
|
||||
entity.setCountryId(rs.getInt("country_id"));
|
||||
|
||||
entity.setOutdated(rs.getBoolean("is_outdated"));
|
||||
|
||||
return entity;
|
||||
|
|
|
|||
|
|
@ -34,9 +34,15 @@ public class ContainerRateRepository {
|
|||
return new SearchQueryResult<>(jdbcTemplate.query(query, new ContainerRateMapper(), periodId, pagination.getLimit(), pagination.getOffset()), pagination.getPage(), totalCount, pagination.getLimit());
|
||||
}
|
||||
|
||||
public ContainerRate getById(Integer id) {
|
||||
public Optional<ContainerRate> getById(Integer id) {
|
||||
String query = "SELECT * FROM container_rate WHERE id = ?";
|
||||
return jdbcTemplate.queryForObject(query, new ContainerRateMapper(), id);
|
||||
|
||||
var rate = jdbcTemplate.query(query, new ContainerRateMapper(), id);
|
||||
|
||||
if(rate.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(rate.getFirst());
|
||||
}
|
||||
|
||||
public List<ContainerRate> listAllRatesByPeriodId(Integer periodId) {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class MatrixRateRepository {
|
|||
@Transactional
|
||||
public SearchQueryResult<MatrixRate> listRatesByPeriodId(SearchQueryPagination pagination, Integer periodId) {
|
||||
String query = "SELECT * FROM country_matrix_rate WHERE validity_period_id = ? ORDER BY id LIMIT ? OFFSET ?";
|
||||
var totalCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM country_matrix_rate WHERE validity_period_id = ?", Integer.class);
|
||||
var totalCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM country_matrix_rate WHERE validity_period_id = ?", Integer.class, periodId);
|
||||
return new SearchQueryResult<>(jdbcTemplate.query(query, new MatrixRateMapper(), periodId, pagination.getLimit(), pagination.getOffset()), pagination.getPage(), totalCount, pagination.getLimit());
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +108,8 @@ public class MatrixRateRepository {
|
|||
|
||||
entity.setId(rs.getInt("id"));
|
||||
entity.setRate(rs.getBigDecimal("rate"));
|
||||
entity.setFromCountry(rs.getInt("from_country"));
|
||||
entity.setToCountry(rs.getInt("to_country"));
|
||||
entity.setFromCountry(rs.getInt("from_country_id"));
|
||||
entity.setToCountry(rs.getInt("to_country_id"));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,8 +68,9 @@ public class ValidityPeriodRepository {
|
|||
* @param id the ID of the validity period to invalidate.
|
||||
*/
|
||||
@Transactional
|
||||
public void invalidateById(Integer id) {
|
||||
jdbcTemplate.update("UPDATE validity_period SET state = ? WHERE id = ? AND state = ? ", ValidityPeriodState.INVALID.name(), id, ValidityPeriodState.EXPIRED.name());
|
||||
public boolean invalidateById(Integer id) {
|
||||
var affectedRows = jdbcTemplate.update("UPDATE validity_period SET state = ? WHERE id = ? AND state = ? ", ValidityPeriodState.INVALID.name(), id, ValidityPeriodState.EXPIRED.name());
|
||||
return affectedRows > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,8 +96,8 @@ public class ValidityPeriodRepository {
|
|||
*
|
||||
* @return the ID of the valid {@link ValidityPeriod}.
|
||||
*/
|
||||
public Integer getValidPeriodId() {
|
||||
return getValidPeriod().getId();
|
||||
public Optional<Integer> getValidPeriodId() {
|
||||
return getValidPeriod().map(ValidityPeriod::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,9 +105,14 @@ public class ValidityPeriodRepository {
|
|||
*
|
||||
* @return the {@link ValidityPeriod} in the {@code VALID} state.
|
||||
*/
|
||||
public ValidityPeriod getValidPeriod() {
|
||||
public Optional<ValidityPeriod> getValidPeriod() {
|
||||
String query = "SELECT * FROM validity_period WHERE state = ?";
|
||||
return jdbcTemplate.queryForObject(query, new ValidityPeriodMapper(), ValidityPeriodState.VALID.name());
|
||||
var period = jdbcTemplate.query(query, new ValidityPeriodMapper(), ValidityPeriodState.VALID.name());
|
||||
|
||||
if(period.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(period.getFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,7 +163,7 @@ public class ValidityPeriodRepository {
|
|||
|
||||
/* set current to expired */
|
||||
jdbcTemplate.update("UPDATE validity_period SET state = ?, end_date = ? WHERE state = ? ", ValidityPeriodState.EXPIRED.name(), currentTimestamp, ValidityPeriodState.VALID.name());
|
||||
jdbcTemplate.update("UPDATE validity_period SET state = ?, start_date = ? WHERE id = ? AND state = ? ", ValidityPeriodState.VALID.name(), currentTimestamp, ValidityPeriodState.DRAFT.name());
|
||||
jdbcTemplate.update("UPDATE validity_period SET state = ?, start_date = ? WHERE state = ? ", ValidityPeriodState.VALID.name(), currentTimestamp, ValidityPeriodState.DRAFT.name());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package de.avatic.lcc.repositories.users;
|
||||
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
|
@ -8,6 +10,7 @@ import org.springframework.stereotype.Repository;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -35,8 +38,26 @@ public class UserNodeRepository {
|
|||
return jdbcTemplate.query(queryBuilder.toString(), new NodeMapper(), userId, '%' + filter + '%', '%' + filter + '%', limit);
|
||||
}
|
||||
|
||||
public void add(Node node) {
|
||||
// todo insert user node
|
||||
public void add(Integer userId, Node node) {
|
||||
String sql = """
|
||||
INSERT INTO sys_user_node (
|
||||
name, address, geo_lat, geo_lng,
|
||||
is_deprecated, country_id, user_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
var affectedRows = jdbcTemplate.update(sql,
|
||||
node.getName(),
|
||||
node.getAddress(),
|
||||
node.getGeoLat(),
|
||||
node.getGeoLng(),
|
||||
node.getDeprecated(),
|
||||
node.getCountryId(),
|
||||
userId
|
||||
);
|
||||
|
||||
if(affectedRows != 1)
|
||||
throw new DatabaseException("Could not add node to user");
|
||||
}
|
||||
|
||||
public Optional<Node> getById(Integer id) {
|
||||
|
|
@ -65,6 +86,30 @@ public class UserNodeRepository {
|
|||
|
||||
}
|
||||
|
||||
public Collection<Node> getByIds(List<Integer> nodeIds) {
|
||||
String placeholders = String.join(",", Collections.nCopies(nodeIds.size(), "?"));
|
||||
String query = """
|
||||
SELECT *
|
||||
FROM sys_user_node
|
||||
WHERE sys_user_node.id IN (""" + placeholders + ")";
|
||||
|
||||
return nodeIds.isEmpty() ? Collections.emptyList() : jdbcTemplate.query(query, new NodeMapper(), nodeIds.toArray());
|
||||
}
|
||||
|
||||
public Optional<Integer> getOwnerById(Integer userSupplierId) {
|
||||
|
||||
String query = """
|
||||
SELECT user_id FROM sys_user_node WHERE id = ?;
|
||||
""";
|
||||
|
||||
var ids = jdbcTemplate.queryForList(query, Integer.class, userSupplierId);
|
||||
|
||||
if(ids.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(ids.getFirst());
|
||||
}
|
||||
|
||||
private static class NodeMapper implements RowMapper<Node> {
|
||||
|
||||
@Override
|
||||
|
|
@ -80,6 +125,10 @@ public class UserNodeRepository {
|
|||
node.setDeprecated(rs.getBoolean("is_deprecated"));
|
||||
node.setCountryId(rs.getInt("country_id"));
|
||||
|
||||
node.setDestination(false);
|
||||
node.setIntermediate(false);
|
||||
node.setSource(true);
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package de.avatic.lcc.service;
|
|||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Service
|
||||
public class CustomApiService {
|
||||
public Double getTariffRate(String hsCode, Integer countryId) {
|
||||
return Double.valueOf(3);
|
||||
public BigDecimal getTariffRate(String hsCode, Integer countryId) {
|
||||
return BigDecimal.valueOf(3);
|
||||
|
||||
//TODO implement me
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import de.avatic.lcc.repositories.rates.ContainerRateRepository;
|
|||
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
|
||||
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
|
||||
import de.avatic.lcc.service.transformer.rates.ValidityPeriodTransformer;
|
||||
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -80,8 +82,8 @@ public class ContainerRateService {
|
|||
*/
|
||||
@Transactional
|
||||
public SearchQueryResult<ContainerRateDTO> listRates(int limit, int page) {
|
||||
Integer id = validityPeriodRepository.getValidPeriodId();
|
||||
return SearchQueryResult.map(containerRateRepository.listRatesByPeriodId(new SearchQueryPagination(limit, page), id), this::toContainerRateDTO);
|
||||
var data = validityPeriodRepository.getValidPeriodId().map(id -> containerRateRepository.listRatesByPeriodId(new SearchQueryPagination(page, limit), id)).orElseThrow(() -> new InternalErrorException("No validity period that is VALID"));
|
||||
return SearchQueryResult.map(data, this::toContainerRateDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,7 +94,7 @@ public class ContainerRateService {
|
|||
*/
|
||||
@Transactional
|
||||
public ContainerRateDTO getContainerRate(Integer id) {
|
||||
return toContainerRateDTO(containerRateRepository.getById(id));
|
||||
return toContainerRateDTO(containerRateRepository.getById(id).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.CONTAINER_RATE, "id", String.valueOf(id))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.service.access;
|
|||
|
||||
import de.avatic.lcc.dto.configuration.matrixrates.MatrixRateDTO;
|
||||
import de.avatic.lcc.model.rates.MatrixRate;
|
||||
import de.avatic.lcc.model.rates.ValidityPeriod;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
|
|
@ -62,7 +63,7 @@ public class MatrixRateService {
|
|||
if (null == periodId)
|
||||
return listRates(limit, page);
|
||||
|
||||
return SearchQueryResult.map(matrixRateRepository.listRatesByPeriodId(new SearchQueryPagination(limit, page), periodId), this::toMatrixRateDTO);
|
||||
return SearchQueryResult.map(matrixRateRepository.listRatesByPeriodId(new SearchQueryPagination(page, limit), periodId), this::toMatrixRateDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,8 +75,8 @@ public class MatrixRateService {
|
|||
*/
|
||||
@Transactional
|
||||
public SearchQueryResult<MatrixRateDTO> listRates(int limit, int page) {
|
||||
Integer id = validityPeriodRepository.getValidPeriodId();
|
||||
return SearchQueryResult.map(matrixRateRepository.listRatesByPeriodId(new SearchQueryPagination(limit, page), id), this::toMatrixRateDTO);
|
||||
Integer id = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new IllegalStateException("No valid period found that is VALID"));
|
||||
return SearchQueryResult.map(matrixRateRepository.listRatesByPeriodId(new SearchQueryPagination(page, limit), id), this::toMatrixRateDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import de.avatic.lcc.service.transformer.nodes.NodeUpdateDTOTransformer;
|
|||
import de.avatic.lcc.service.transformer.nodes.NodeDetailTransformer;
|
||||
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
|
||||
import de.avatic.lcc.util.exception.badrequest.NodeNotFoundException;
|
||||
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -30,15 +31,13 @@ public class NodeService {
|
|||
private final NodeDetailTransformer nodeDetailTransformer;
|
||||
private final NodeUpdateDTOTransformer nodeUpdateDTOTransformer;
|
||||
private final UserNodeRepository userNodeRepository;
|
||||
private final CountryRepository countryRepository;
|
||||
|
||||
public NodeService(NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeDetailTransformer nodeDetailTransformer, NodeUpdateDTOTransformer nodeUpdateDTOTransformer, UserNodeRepository userNodeRepository, CountryRepository countryRepository) {
|
||||
public NodeService(NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeDetailTransformer nodeDetailTransformer, NodeUpdateDTOTransformer nodeUpdateDTOTransformer, UserNodeRepository userNodeRepository) {
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.nodeTransformer = nodeTransformer;
|
||||
this.nodeDetailTransformer = nodeDetailTransformer;
|
||||
this.nodeUpdateDTOTransformer = nodeUpdateDTOTransformer;
|
||||
this.userNodeRepository = userNodeRepository;
|
||||
this.countryRepository = countryRepository;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +73,7 @@ public class NodeService {
|
|||
* @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)));
|
||||
return nodeDetailTransformer.toNodeDetailDTO(nodeRepository.getById(id).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.NODE, "id", id.toString())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,7 +84,7 @@ public class NodeService {
|
|||
* @throws NodeNotFoundException if no node is found with the specified ID.
|
||||
*/
|
||||
public Integer deleteNode(Integer id) {
|
||||
return nodeRepository.setDeprecatedById(id).orElseThrow(() -> new NodeNotFoundException(id));
|
||||
return nodeRepository.setDeprecatedById(id).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.NODE, "id", id.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,7 +95,7 @@ public class NodeService {
|
|||
* @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()));
|
||||
return nodeRepository.update(nodeUpdateDTOTransformer.fromNodeUpdateDTO(dto)).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.NODE, "id", dto.getId().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package de.avatic.lcc.service.access;
|
||||
|
||||
import de.avatic.lcc.dto.bulk.BulkStatus;
|
||||
import de.avatic.lcc.dto.calculation.CalculationStatus;
|
||||
import de.avatic.lcc.dto.calculation.PremiseDTO;
|
||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||
|
|
@ -19,6 +18,7 @@ import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
|
|||
import de.avatic.lcc.service.calculation.execution.CalculationStatusService;
|
||||
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
||||
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
||||
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -58,6 +58,9 @@ public class PremisesService {
|
|||
//TODO check if user is admin. if not: use the user_id of currently authorized user.
|
||||
var admin = false;
|
||||
|
||||
//TODO use actual user.
|
||||
userId = 1;
|
||||
|
||||
return SearchQueryResult.map(premiseRepository.listPremises(filter, new SearchQueryPagination(page, limit), userId, deleted, archived, done), admin ? premiseTransformer::toPremiseDTOWithUserInfo : premiseTransformer::toPremiseDTO);
|
||||
|
||||
}
|
||||
|
|
@ -76,7 +79,7 @@ public class PremisesService {
|
|||
var userId = 1; // TODO get current user id
|
||||
|
||||
var validSetId = propertySetRepository.getValidSetId();
|
||||
var validPeriodId = validityPeriodRepository.getValidPeriodId();
|
||||
var validPeriodId = validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("no valid period found that is VALID"));
|
||||
|
||||
var calculationIds = new ArrayList<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ 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 de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
|
@ -31,8 +33,19 @@ public class UserNodeService {
|
|||
* @throws IllegalArgumentException if any required information in the DTO is missing or invalid.
|
||||
*/
|
||||
public void addUserNode(AddUserNodeDTO dto) {
|
||||
|
||||
// TODO: get real user id.
|
||||
var userId = 1;
|
||||
|
||||
Node node = new Node();
|
||||
|
||||
if (dto.getName() == null || dto.getName().isBlank())
|
||||
throw new InvalidArgumentException("name set to blank value");
|
||||
if (dto.getAddress() == null || dto.getAddress().isBlank())
|
||||
throw new IllegalArgumentException("address set to blank value");
|
||||
if (dto.getLocation() == null)
|
||||
throw new InvalidArgumentException("location set to null value");
|
||||
|
||||
node.setName(dto.getName());
|
||||
node.setAddress(dto.getAddress());
|
||||
node.setGeoLng(BigDecimal.valueOf(dto.getLocation().getLongitude()));
|
||||
|
|
@ -41,8 +54,9 @@ public class UserNodeService {
|
|||
node.setSource(true);
|
||||
node.setIntermediate(false);
|
||||
node.setDeprecated(false);
|
||||
node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow().getId());
|
||||
|
||||
userNodeRepository.add(node);
|
||||
node.setCountryId(countryRepository.getByIsoCode(IsoCode.valueOf(dto.getCountry().getIsoCode())).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.COUNTRY, "iso code", dto.getCountry().getIsoCode())).getId());
|
||||
|
||||
userNodeRepository.add(userId, node);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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 de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -56,6 +57,8 @@ public class ValidityPeriodService {
|
|||
* @param id The unique identifier of the validity period to invalidate
|
||||
*/
|
||||
public void invalidate(Integer id) {
|
||||
validityPeriodRepository.invalidateById(id);
|
||||
if (!validityPeriodRepository.invalidateById(id))
|
||||
throw new NotFoundException(NotFoundException.NotFoundType.EXPIRED_VALIDITY_PERIOD, "id", id.toString());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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.excelMapper.*;
|
||||
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.SheetVisibility;
|
||||
|
|
@ -48,7 +49,7 @@ public class BulkExportService {
|
|||
}
|
||||
|
||||
public InputStreamSource generateExport(BulkFileType bulkFileType) throws IOException {
|
||||
return generateExport(bulkFileType, validityPeriodRepository.getValidPeriodId());
|
||||
return generateExport(bulkFileType, validityPeriodRepository.getValidPeriodId().orElseThrow(() -> new InternalErrorException("No valid period found that is VALID")));
|
||||
}
|
||||
|
||||
public InputStreamSource generateExport(BulkFileType bulkFileType, Integer periodId) throws IOException {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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.premises.route.RouteSectionInformation;
|
||||
import de.avatic.lcc.model.properties.PackagingProperty;
|
||||
import de.avatic.lcc.model.properties.PackagingPropertyMappingId;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
package de.avatic.lcc.service.calculation;
|
||||
|
||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||
import de.avatic.lcc.model.packaging.PackagingDimension;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.PremiseState;
|
||||
import de.avatic.lcc.repositories.premise.DestinationRepository;
|
||||
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.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteRepository;
|
||||
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.transformer.generic.DimensionTransformer;
|
||||
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||
import de.avatic.lcc.util.exception.badrequest.NotFoundException;
|
||||
import de.avatic.lcc.util.exception.base.ForbiddenException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
|
|
@ -20,65 +32,188 @@ public class PremiseCreationService {
|
|||
private final PremiseRepository premiseRepository;
|
||||
private final PremiseTransformer premiseTransformer;
|
||||
private final DestinationService destinationService;
|
||||
private final UserNodeRepository userNodeRepository;
|
||||
private final NodeRepository nodeRepository;
|
||||
private final MaterialRepository materialRepository;
|
||||
private final DimensionTransformer dimensionTransformer;
|
||||
private final PackagingRepository packagingRepository;
|
||||
private final PackagingDimensionRepository packagingDimensionRepository;
|
||||
private final PackagingPropertiesRepository packagingPropertiesRepository;
|
||||
private final CustomApiService customApiService;
|
||||
|
||||
public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService) {
|
||||
public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService, UserNodeRepository userNodeRepository, NodeRepository nodeRepository, MaterialRepository materialRepository, DimensionTransformer dimensionTransformer, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, CustomApiService customApiService) {
|
||||
this.premiseRepository = premiseRepository;
|
||||
this.premiseTransformer = premiseTransformer;
|
||||
this.destinationService = destinationService;
|
||||
this.userNodeRepository = userNodeRepository;
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.materialRepository = materialRepository;
|
||||
this.dimensionTransformer = dimensionTransformer;
|
||||
this.packagingRepository = packagingRepository;
|
||||
this.packagingDimensionRepository = packagingDimensionRepository;
|
||||
this.packagingPropertiesRepository = packagingPropertiesRepository;
|
||||
this.customApiService = customApiService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<PremiseDetailDTO> createPremises(List<Integer> materialIds, List<Integer> supplierIds, List<Integer> userSupplierIds, boolean createEmpty) {
|
||||
|
||||
Integer userId = 1; //TODO get user id
|
||||
|
||||
List<TemporaryPremise> foundPremises = new ArrayList<>(premiseRepository.getPremisesByMaterialIdsAndSupplierIds(materialIds, supplierIds, userSupplierIds, userId, createEmpty).stream().map(TemporaryPremise::new).toList());
|
||||
/* Build all resulting premises */
|
||||
List<TemporaryPremise> premises = Stream.concat(
|
||||
supplierIds.stream().flatMap(id -> materialIds.stream().map(materialId -> new TemporaryPremise(materialId, id, null, false))),
|
||||
userSupplierIds.stream().flatMap(id -> materialIds.stream().map(materialId -> new TemporaryPremise(materialId, null, id, true)))).toList();
|
||||
|
||||
List<TemporaryPremise> toBeCopied = foundPremises.stream().filter(p -> (!p.getPremise().getState().equals(PremiseState.DRAFT) || !Objects.equals(p.getPremise().getUserId(), userId))).toList();
|
||||
premises.forEach(p -> findExistingPremise(p, createEmpty, userId));
|
||||
|
||||
List<TemporaryPremise> toBeCreated = materialIds.stream()
|
||||
.flatMap(materialId -> {
|
||||
Stream<TemporaryPremise> supplierCombinations = supplierIds.stream()
|
||||
.map(supplierId -> new TemporaryPremise(materialId, supplierId, null, false));
|
||||
premises.forEach(p -> verifyNode(p, userId));
|
||||
verifyMaterial(materialIds);
|
||||
|
||||
Stream<TemporaryPremise> userSupplierCombinations =
|
||||
(userSupplierIds != null && !userSupplierIds.isEmpty()) ?
|
||||
userSupplierIds.stream()
|
||||
.map(userSupplierId -> new TemporaryPremise(materialId, null, userSupplierId, true)) :
|
||||
Stream.empty();
|
||||
premises.forEach(p -> {
|
||||
if (p.getPremise() == null) { // create new
|
||||
|
||||
return Stream.concat(supplierCombinations, userSupplierCombinations);
|
||||
})
|
||||
// filter existing combinations.
|
||||
.filter(p -> foundPremises.stream()
|
||||
.noneMatch(found ->
|
||||
Objects.equals(found.materialId, p.materialId) &&
|
||||
Objects.equals(found.supplierId, p.supplierId) &&
|
||||
Objects.equals(found.userSupplierId, p.userSupplierId)
|
||||
)
|
||||
)
|
||||
.toList();
|
||||
p.setId(premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), p.getGeoLat(), p.getGeoLng(), p.getCountryId(), userId));
|
||||
fillPremise(p, userId);
|
||||
|
||||
List<Integer> premiseIds = new ArrayList<>(toBeCreated.stream().map(p -> premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), userId)).toList());
|
||||
|
||||
for( TemporaryPremise p : toBeCopied ) {
|
||||
Integer id = premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), userId);
|
||||
destinationService.duplicate(p.getPremise().getId(), id);
|
||||
premiseIds.add(id);
|
||||
} else if (p.getPremise().getState().equals(PremiseState.DRAFT)) { // recycle
|
||||
p.setId(p.getPremise().getId());
|
||||
if (createEmpty) {
|
||||
// reset to defaults.
|
||||
fillPremise(p, userId);
|
||||
}
|
||||
|
||||
return premiseRepository.getPremisesById(premiseIds).stream().map(premiseTransformer::toPremiseDetailDTO).toList();
|
||||
} else if (p.getPremise().getState().equals(PremiseState.COMPLETED)) {
|
||||
// create a copy
|
||||
p.setId(premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), p.getGeoLat(), p.getGeoLng(), p.getCountryId(), userId));
|
||||
copyPremise(p, userId);
|
||||
destinationService.duplicate(p.getPremise().getId(), p.getId());
|
||||
}
|
||||
});
|
||||
|
||||
return premiseRepository.getPremisesById(premises.stream().map(TemporaryPremise::getId).toList()).stream().map(premiseTransformer::toPremiseDetailDTO).toList();
|
||||
}
|
||||
|
||||
private void copyPremise(TemporaryPremise p, Integer userId) {
|
||||
var old = p.getPremise();
|
||||
|
||||
premiseRepository.updateMaterial(Collections.singletonList(p.getId()), userId, old.getHsCode(), old.getCustomRate());
|
||||
premiseRepository.updatePrice(Collections.singletonList(p.getId()), userId, old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
|
||||
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), userId, dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
|
||||
premiseRepository.setPackagingId(p.getId(), old.getId());
|
||||
}
|
||||
|
||||
private void fillPremise(TemporaryPremise p, Integer userId) {
|
||||
|
||||
if (!p.isUserSupplier()) {
|
||||
var packaging = packagingRepository.getByMaterialIdAndSupplierId(p.getMaterialId(), p.getSupplierId());
|
||||
Optional<PackagingDimension> hu = packagingDimensionRepository.getById(packaging.getFirst().getHuId());
|
||||
Optional<PackagingDimension> shu = packagingDimensionRepository.getById(packaging.getFirst().getShuId());
|
||||
|
||||
if (hu.isPresent() && shu.isPresent()) {
|
||||
boolean stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
||||
boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.getFirst().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
||||
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), userId, hu.get(), shu.get(), stackable, mixable); //TODO clarify if the hu unit count in packaging data is total unit count or shu count (shu*hu or hu)
|
||||
premiseRepository.setPackagingId(p.getId(), packaging.getFirst().getId());
|
||||
}
|
||||
}
|
||||
|
||||
var material = materialRepository.getById(p.getMaterialId());
|
||||
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), userId, value.getHsCode(), customApiService.getTariffRate(value.getHsCode(), getCountryId(p))));
|
||||
|
||||
}
|
||||
|
||||
private Integer getCountryId(TemporaryPremise p) {
|
||||
|
||||
if (p.isUserSupplier()) {
|
||||
return userNodeRepository.getById(p.getUserSupplierId()).orElseThrow().getCountryId();
|
||||
} else {
|
||||
return nodeRepository.getById(p.getSupplierId()).orElseThrow().getCountryId();
|
||||
}
|
||||
}
|
||||
|
||||
private void findExistingPremise(TemporaryPremise premise, boolean createEmpty, Integer userId) {
|
||||
var existingPremises = premiseRepository.findByMaterialIdAndSupplierId(premise.getMaterialId(), premise.getSupplierId(), premise.getUserSupplierId(), userId);
|
||||
Premise existingDraft = existingPremises.stream().filter(p -> p.getState().equals(PremiseState.DRAFT)).findFirst().orElse(null);
|
||||
|
||||
if (existingDraft != null) {
|
||||
premise.setPremise(existingDraft);
|
||||
} else if (!createEmpty) {
|
||||
Premise youngestCompleted = existingPremises.stream().filter(p -> p.getState().equals(PremiseState.COMPLETED) && p.getUserId().equals(userId)).findFirst().orElse(null);
|
||||
|
||||
// no completed from current user, check if completed from other user.
|
||||
if (youngestCompleted == null) {
|
||||
youngestCompleted = existingPremises.stream().filter(p -> p.getState().equals(PremiseState.COMPLETED)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
premise.setPremise(youngestCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node of the given temporary premiss exists, and updates data in
|
||||
* temporary premiss.
|
||||
*
|
||||
* @param temporaryPremise
|
||||
*/
|
||||
private void verifyNode(TemporaryPremise temporaryPremise, Integer userId) {
|
||||
|
||||
var node = temporaryPremise.isUserSupplier() ?
|
||||
userNodeRepository.getById(temporaryPremise.getUserSupplierId()).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.USER_NODE, "id", String.valueOf(temporaryPremise.getUserSupplierId()))) :
|
||||
nodeRepository.getById(temporaryPremise.getSupplierId()).orElseThrow(() -> new NotFoundException(NotFoundException.NotFoundType.NODE, "id", String.valueOf(temporaryPremise.getSupplierId())));
|
||||
|
||||
if(temporaryPremise.isUserSupplier()) {
|
||||
var id = userNodeRepository.getOwnerById(temporaryPremise.getUserSupplierId());
|
||||
|
||||
if(id.isPresent() && !id.get().equals(userId)) {
|
||||
throw new ForbiddenException("Unable to access this node id " + temporaryPremise.getUserSupplierId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.getSource())
|
||||
throw new InvalidArgumentException((temporaryPremise.isUserSupplier() ? "User node" : "Node") + " with id " + node.getId() + " is not a source node.");
|
||||
|
||||
temporaryPremise.setGeoLat(node.getGeoLat());
|
||||
temporaryPremise.setGeoLng(node.getGeoLng());
|
||||
temporaryPremise.setCountryId(node.getCountryId());
|
||||
|
||||
}
|
||||
|
||||
private void verifyMaterial(List<Integer> materialIds) {
|
||||
var missingMaterials = materialRepository.findMissingIds(materialIds);
|
||||
|
||||
if (!missingMaterials.isEmpty())
|
||||
throw new InvalidArgumentException("Material with ids " + missingMaterials + " does not exist.");
|
||||
}
|
||||
|
||||
private static class TemporaryPremise {
|
||||
|
||||
|
||||
private Integer materialId;
|
||||
private Integer supplierId;
|
||||
private Integer userSupplierId;
|
||||
private boolean isUserSupplier;
|
||||
|
||||
/* found premise */
|
||||
private Premise premise;
|
||||
|
||||
private BigDecimal geoLat;
|
||||
private BigDecimal geoLng;
|
||||
|
||||
private Integer countryId;
|
||||
|
||||
/* id of the premise used (can be newly created or recycled one)*/
|
||||
private Integer id;
|
||||
|
||||
|
||||
public TemporaryPremise(Integer materialId, Integer supplierId, Integer userSupplierId, boolean isUserSupplier) {
|
||||
this.materialId = materialId;
|
||||
this.supplierId = supplierId;
|
||||
this.userSupplierId = userSupplierId;
|
||||
this.isUserSupplier = isUserSupplier;
|
||||
this.premise = null;
|
||||
|
||||
}
|
||||
|
||||
public Integer getMaterialId() {
|
||||
return materialId;
|
||||
}
|
||||
|
|
@ -111,29 +246,48 @@ public class PremiseCreationService {
|
|||
isUserSupplier = userSupplier;
|
||||
}
|
||||
|
||||
public Premise getPremise() {
|
||||
return premise;
|
||||
}
|
||||
|
||||
public void setPremise(Premise premise) {
|
||||
this.premise = premise;
|
||||
}
|
||||
|
||||
public TemporaryPremise(Premise premise) {
|
||||
this.premise = premise;
|
||||
this.materialId = premise.getMaterialId();
|
||||
this.supplierId = premise.getSupplierNodeId();
|
||||
this.userSupplierId = premise.getUserSupplierNodeId();
|
||||
this.isUserSupplier = premise.getUserSupplierNodeId() != null;
|
||||
public BigDecimal getGeoLat() {
|
||||
return this.geoLat;
|
||||
}
|
||||
|
||||
public void setGeoLat(BigDecimal geoLat) {
|
||||
this.geoLat = geoLat;
|
||||
}
|
||||
|
||||
public BigDecimal getGeoLng() {
|
||||
return this.geoLng;
|
||||
}
|
||||
|
||||
public void setGeoLng(BigDecimal geoLng) {
|
||||
this.geoLng = geoLng;
|
||||
}
|
||||
|
||||
public Integer getCountryId() {
|
||||
return this.countryId;
|
||||
}
|
||||
|
||||
public void setCountryId(Integer countryId) {
|
||||
this.countryId = countryId;
|
||||
}
|
||||
|
||||
public void addFoundPremises(List<Premise> foundPremises) {
|
||||
|
||||
}
|
||||
|
||||
public TemporaryPremise(Integer materialId, Integer supplierId, Integer userSupplierId, boolean isUserSupplier) {
|
||||
this.materialId = materialId;
|
||||
this.supplierId = supplierId;
|
||||
this.userSupplierId = userSupplierId;
|
||||
this.isUserSupplier = isUserSupplier;
|
||||
this.premise = null;
|
||||
public Integer getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public Premise getPremise() {
|
||||
return premise;
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import de.avatic.lcc.model.materials.Material;
|
|||
import de.avatic.lcc.repositories.MaterialRepository;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||
import de.avatic.lcc.service.transformer.generic.MaterialTransformer;
|
||||
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -31,6 +32,7 @@ public class PremiseSearchStringAnalyzerService {
|
|||
private final PremiseRepository premiseRepository;
|
||||
private final NodeTransformer nodeTransformer;
|
||||
private final MaterialTransformer materialTransformer;
|
||||
private final UserNodeRepository userNodeRepository;
|
||||
|
||||
/**
|
||||
* Constructor for the PremiseSearchStringAnalyzerService, initializing the required repositories
|
||||
|
|
@ -42,12 +44,13 @@ public class PremiseSearchStringAnalyzerService {
|
|||
* @param nodeTransformer Transformer for mapping database node entities (suppliers) to DTOs.
|
||||
* @param materialTransformer Transformer for mapping database material entities to DTOs.
|
||||
*/
|
||||
public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer) {
|
||||
public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer, UserNodeRepository userNodeRepository) {
|
||||
this.materialRepository = materialRepository;
|
||||
this.nodeRepository = nodeRepository;
|
||||
this.premiseRepository = premiseRepository;
|
||||
this.nodeTransformer = nodeTransformer;
|
||||
this.materialTransformer = materialTransformer;
|
||||
this.userNodeRepository = userNodeRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,18 +66,21 @@ public class PremiseSearchStringAnalyzerService {
|
|||
* and user-specific suppliers related to the identified materials.
|
||||
*/
|
||||
public PremiseSearchResultDTO findMaterialAndSuppliers(String search) {
|
||||
|
||||
var userId = 1; //TODO get actual user id.
|
||||
|
||||
List<Material> material = materialRepository.getByPartNumbers(findPartNumbers(search));
|
||||
List<Integer> materialIds = material.stream().map(Material::getId).toList();
|
||||
|
||||
// find suppliers associated with this material.
|
||||
List<Integer> supplierIds = premiseRepository.findAssociatedSuppliers(materialIds);
|
||||
List<Integer> userSupplierIds = premiseRepository.findAssociatedUserSuppliers(materialIds);
|
||||
List<Integer> userSupplierIds = premiseRepository.findAssociatedUserSuppliers(materialIds, userId);
|
||||
|
||||
var dto = new PremiseSearchResultDTO();
|
||||
|
||||
dto.setMaterials(material.stream().map(materialTransformer::toMaterialDTO).toList());
|
||||
dto.setSupplier(nodeRepository.getByIds(supplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
|
||||
dto.setUserSupplier(nodeRepository.getByIds(userSupplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
|
||||
dto.setUserSupplier(userNodeRepository.getByIds(userSupplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,19 +76,19 @@ public class CustomCostCalculationService {
|
|||
}
|
||||
|
||||
var customValue = materialCost.add(fcaFee).add(transportationCost);
|
||||
var customDuties = customValue.multiply(BigDecimal.valueOf(tariffRate));
|
||||
var customDuties = customValue.multiply(tariffRate);
|
||||
var annualCustomFee = shippingFrequency * customFee;
|
||||
var annualCost = customDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
|
||||
var customRiskValue = materialCost.add(fcaFee).add(transportationRiskCost);
|
||||
var customRiskDuties = customRiskValue.multiply(BigDecimal.valueOf(tariffRate));
|
||||
var customRiskDuties = customRiskValue.multiply(tariffRate);
|
||||
var annualRiskCost = customRiskDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
|
||||
var customChanceValue = materialCost.add(fcaFee).add(transportationChanceCost);
|
||||
var customChanceDuties = customChanceValue.multiply(BigDecimal.valueOf(tariffRate));
|
||||
var customChanceDuties = customChanceValue.multiply(tariffRate);
|
||||
var annualChanceCost = customChanceDuties.add(BigDecimal.valueOf(annualCustomFee));
|
||||
|
||||
return new CustomResult(customValue, customRiskValue, customChanceValue, customDuties, BigDecimal.valueOf(tariffRate), annualCost, annualRiskCost, annualChanceCost);
|
||||
return new CustomResult(customValue, customRiskValue, customChanceValue, customDuties, tariffRate, annualCost, annualRiskCost, annualChanceCost);
|
||||
}
|
||||
|
||||
private List<SectionInfo> getCustomRelevantRouteSections(List<SectionInfo> sections) {
|
||||
|
|
|
|||
|
|
@ -42,10 +42,27 @@ public class DimensionTransformer {
|
|||
return entity;
|
||||
}
|
||||
|
||||
public PackagingDimension toDimensionEntity(Premise entity) {
|
||||
var packaging = new PackagingDimension();
|
||||
|
||||
packaging.setId(null);
|
||||
packaging.setType(PackagingType.HU);
|
||||
packaging.setLength(entity.getIndividualHuLength());
|
||||
packaging.setWidth(entity.getIndividualHuWidth());
|
||||
packaging.setHeight(entity.getIndividualHuHeight());
|
||||
packaging.setDimensionUnit(entity.getHuDisplayedDimensionUnit());
|
||||
packaging.setWeight(entity.getIndividualHuWeight());
|
||||
packaging.setWeightUnit(entity.getHuDisplayedWeightUnit());
|
||||
packaging.setContentUnitCount(entity.getHuUnitCount());
|
||||
packaging.setDeprecated(null);
|
||||
|
||||
return packaging;
|
||||
}
|
||||
|
||||
public DimensionDTO toDimensionDTO(Premise entity) {
|
||||
DimensionDTO dto = new DimensionDTO();
|
||||
|
||||
dto.setId(entity.getId());
|
||||
dto.setId(null);
|
||||
dto.setType(PackagingType.HU);
|
||||
dto.setLength(entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuLength()).doubleValue());
|
||||
dto.setWidth(entity.getHuDisplayedDimensionUnit().convertFromMM(entity.getIndividualHuWidth()).doubleValue());
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public class NodeTransformer {
|
|||
dto.setDeprecated(entity.getDeprecated());
|
||||
dto.setLocation(locationTransformer.toLocationDTO(entity));
|
||||
dto.setUserNode(false);
|
||||
dto.setExternalMappingId(entity.getExternalMappingId());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ public class NodeDetailTransformer {
|
|||
|
||||
var chains = new ArrayList<Map<Integer, NodeDTO>>();
|
||||
|
||||
for (var chain : node.getNodePredecessors()) {
|
||||
var foundChains = node.getNodePredecessors();
|
||||
|
||||
for (var chain : foundChains) {
|
||||
Map<Integer, NodeDTO> predecessorChain = new HashMap<>();
|
||||
for (Integer seq : chain.keySet())
|
||||
predecessorChain.put(seq, nodeTransformer.toNodeDTO(nodeRepository.getById(chain.get(seq)).orElseThrow()));
|
||||
|
|
@ -53,6 +55,7 @@ public class NodeDetailTransformer {
|
|||
dto.setTypes(toNodeTypeArrayList(node));
|
||||
dto.setPredecessors(chains);
|
||||
dto.setOutboundCountries(node.getOutboundCountries().stream().map(id -> countryTransformer.toCountryDTO(countryRepository.getById(id).orElseThrow())).toList());
|
||||
dto.setExternalMappingId(node.getExternalMappingId());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
package de.avatic.lcc.service.transformer.nodes;
|
||||
|
||||
import de.avatic.lcc.dto.configuration.nodes.update.NodeUpdateDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Service
|
||||
public class NodeUpdateDTOTransformer {
|
||||
|
||||
|
||||
|
||||
|
||||
public Node fromNodeUpdateDTO(NodeUpdateDTO dto) {
|
||||
throw new NotImplementedException("Not yet implemented fromNodeUpdateDTO");
|
||||
|
||||
Node entity = new Node();
|
||||
|
||||
entity.setId(dto.getId());
|
||||
//entity.setSink(dto.get);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import de.avatic.lcc.dto.generic.NodeDTO;
|
|||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import de.avatic.lcc.model.premises.Premise;
|
||||
import de.avatic.lcc.model.premises.PremiseListEntry;
|
||||
import de.avatic.lcc.repositories.premise.DestinationRepository;
|
||||
import de.avatic.lcc.repositories.MaterialRepository;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||
import de.avatic.lcc.repositories.premise.DestinationRepository;
|
||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||
import de.avatic.lcc.repositories.users.UserRepository;
|
||||
import de.avatic.lcc.service.transformer.generic.CountryTransformer;
|
||||
|
|
@ -73,6 +73,7 @@ public class PremiseTransformer {
|
|||
node.setAddress(entity.getSupplierAddress());
|
||||
node.setLocation(new LocationDTO(entity.getSupplierGeoLatitude().doubleValue(), entity.getSupplierGeoLongitude().doubleValue()));
|
||||
node.setTypes(types);
|
||||
node.setDeprecated(entity.getDeprecated());
|
||||
|
||||
PremiseDTO dto = new PremiseDTO();
|
||||
|
||||
|
|
@ -94,12 +95,15 @@ public class PremiseTransformer {
|
|||
dto.setMixable(entity.getHuMixable());
|
||||
dto.setStackable(entity.getHuStackable());
|
||||
|
||||
if (entity.getIndividualHuHeight() == null || entity.getIndividualHuWidth() == null || entity.getIndividualHuLength() == null || entity.getIndividualHuWeight() == null)
|
||||
dto.setDimension(null);
|
||||
else
|
||||
dto.setDimension(dimensionTransformer.toDimensionDTO(entity));
|
||||
|
||||
if (entity.getSupplierNodeId() != null)
|
||||
dto.setSupplier(nodeRepository.getById(entity.getSupplierNodeId()).map(nodeTransformer::toNodeDTO).orElseThrow());
|
||||
|
||||
if(entity.getUserSupplierNodeId() != null) {
|
||||
if (entity.getUserSupplierNodeId() != null) {
|
||||
dto.setSupplier(userNodeRepository.getById(entity.getUserSupplierNodeId()).map(nodeTransformer::toNodeDTO).orElseThrow());
|
||||
dto.getSupplier().setUserNode(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package de.avatic.lcc.util.exception.badrequest;
|
||||
|
||||
import de.avatic.lcc.util.exception.base.BadRequestException;
|
||||
|
||||
public class InvalidArgumentException extends BadRequestException {
|
||||
|
||||
public InvalidArgumentException(String parameterName, String parameterValue) {
|
||||
super("Invalid Parameter", "Parameter " + parameterName + " set to invalid value " + parameterValue, null);
|
||||
}
|
||||
|
||||
public InvalidArgumentException(String errorText) {
|
||||
super("Invalid Parameter", errorText, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,20 +4,6 @@ import de.avatic.lcc.util.exception.base.BadRequestException;
|
|||
|
||||
public class NotFoundException extends BadRequestException {
|
||||
|
||||
public enum NotFoundType {
|
||||
PROPERTY("Property"), PROPERTY_SET("Property set"), EXPIRED_PROPERTY_SET("Expired property set"), MATERIAL("Material"), NODE("Node"), COUNTRY("Country"), COUNTRY_PROPERTY("Country property");
|
||||
|
||||
private final String identifier;
|
||||
|
||||
NotFoundType(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
public NotFoundException(NotFoundType type, String identifier, String value, Exception e) {
|
||||
super(type.getIdentifier() + " not found", type.getIdentifier() + " with " + identifier + " " + value + " not found", e);
|
||||
}
|
||||
|
|
@ -34,4 +20,26 @@ public class NotFoundException extends BadRequestException {
|
|||
super(type.getIdentifier() + " not found", type.getIdentifier() + " with " + value + " not found", e);
|
||||
}
|
||||
|
||||
public enum NotFoundType {
|
||||
PROPERTY("Property"),
|
||||
PROPERTY_SET("Property set"),
|
||||
EXPIRED_PROPERTY_SET("Expired property set"),
|
||||
MATERIAL("Material"), NODE("Node"),
|
||||
COUNTRY("Country"),
|
||||
COUNTRY_PROPERTY("Country property"),
|
||||
CONTAINER_RATE("Container rate"),
|
||||
EXPIRED_VALIDITY_PERIOD("Expired validity period"),
|
||||
USER_NODE("User node");
|
||||
|
||||
private final String identifier;
|
||||
|
||||
NotFoundType(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package de.avatic.lcc.util.exception.base;
|
||||
|
||||
public class ForbiddenException extends RuntimeException{
|
||||
|
||||
String title;
|
||||
String message;
|
||||
|
||||
public ForbiddenException(String message) {
|
||||
super(message);
|
||||
this.title = "Forbidden Error";
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ForbiddenException(String message, Exception trace) {
|
||||
super(message, trace);
|
||||
this.title = "Forbidden Error";
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ForbiddenException(String title, String message, Exception trace) {
|
||||
super(message, trace);
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,5 @@
|
|||
-- SQL INSERT-Statements für node_predecessor_chain und node_predecessor_entry
|
||||
-- Generiert aus Lastenheft_Requirements Appendix C_Vorknoten.csv
|
||||
-- 51 Predecessor-Ketten
|
||||
-- Automatisch generierte SQL-Statements für Node Predecessor Chains
|
||||
-- Generiert aus: pre_nodes.xlsx
|
||||
|
||||
-- Predecessor Chain 1: CTT
|
||||
INSERT INTO node_predecessor_chain (
|
||||
|
|
|
|||
1332
src/main/resources/master_data/08-data-containerrate.sql
Normal file
1332
src/main/resources/master_data/08-data-containerrate.sql
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -124,8 +124,8 @@ CREATE TABLE IF NOT EXISTS `sys_user_node`
|
|||
`country_id` INT NOT NULL,
|
||||
`name` VARCHAR(254) NOT NULL,
|
||||
`address` VARCHAR(500) NOT NULL,
|
||||
`geo_lat` DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
`geo_lng` DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
`geo_lat` DECIMAL(8, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
`geo_lng` DECIMAL(8, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
`is_deprecated` BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`),
|
||||
FOREIGN KEY (`country_id`) REFERENCES `country` (`id`)
|
||||
|
|
@ -144,8 +144,8 @@ CREATE TABLE IF NOT EXISTS node
|
|||
is_destination BOOLEAN NOT NULL,
|
||||
is_source BOOLEAN NOT NULL,
|
||||
is_intermediate BOOLEAN NOT NULL,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
geo_lat DECIMAL(8, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(8, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
is_deprecated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
|
|
@ -190,10 +190,10 @@ CREATE TABLE IF NOT EXISTS distance_matrix
|
|||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
from_node_id INT NOT NULL,
|
||||
to_node_id INT NOT NULL,
|
||||
from_geo_lat DECIMAL(7, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
||||
from_geo_lng DECIMAL(7, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
||||
to_geo_lat DECIMAL(7, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
||||
to_geo_lng DECIMAL(7, 4) CHECK (to_geo_lng BETWEEN -180 AND 180),
|
||||
from_geo_lat DECIMAL(8, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
||||
from_geo_lng DECIMAL(8, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
||||
to_geo_lat DECIMAL(8, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
||||
to_geo_lng DECIMAL(8, 4) CHECK (to_geo_lng BETWEEN -180 AND 180),
|
||||
distance DECIMAL(15, 2) NOT NULL COMMENT 'travel distance between the two nodes in meters',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
state CHAR(10) NOT NULL,
|
||||
|
|
@ -220,7 +220,7 @@ CREATE TABLE IF NOT EXISTS container_rate
|
|||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
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')),
|
||||
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',
|
||||
|
|
@ -332,18 +332,18 @@ CREATE TABLE IF NOT EXISTS premise
|
|||
material_id INT NOT NULL,
|
||||
supplier_node_id INT,
|
||||
user_supplier_node_id INT,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
geo_lat DECIMAL(8, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(8, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
packaging_id INT DEFAULT NULL,
|
||||
user_id INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
material_cost DECIMAL(15, 2) COMMENT 'aka MEK_A in EUR',
|
||||
material_cost DECIMAL(15, 2) DEFAULT NULL COMMENT 'aka MEK_A in EUR',
|
||||
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
||||
oversea_share DECIMAL(7, 4),
|
||||
hs_code CHAR(8),
|
||||
tariff_rate DECIMAL(7, 4),
|
||||
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
||||
hs_code CHAR(8) DEFAULT NULL,
|
||||
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
||||
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
||||
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
|
|
@ -380,13 +380,13 @@ CREATE TABLE IF NOT EXISTS premise_destination
|
|||
annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces',
|
||||
destination_node_id INT NOT NULL,
|
||||
is_d2d BOOLEAN DEFAULT FALSE,
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
|
||||
lead_time_d2d INT UNSIGNED NOT NULL,
|
||||
repacking_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
handling_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
disposal_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL CHECK (rate_d2d >= 0),
|
||||
lead_time_d2d INT UNSIGNED DEFAULT NULL,
|
||||
repacking_cost DECIMAL(15, 2) DEFAULT NULL CHECK (repacking_cost >= 0),
|
||||
handling_cost DECIMAL(15, 2) DEFAULT NULL CHECK (handling_cost >= 0),
|
||||
disposal_cost DECIMAL(15, 2) DEFAULT NULL CHECK (disposal_cost >= 0),
|
||||
geo_lat DECIMAL(8, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(8, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
FOREIGN KEY (premise_id) REFERENCES premise (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
|
|
@ -395,16 +395,6 @@ CREATE TABLE IF NOT EXISTS premise_destination
|
|||
INDEX idx_premise_id (premise_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_destination_id INT NOT NULL,
|
||||
is_fastest BOOLEAN DEFAULT FALSE,
|
||||
is_cheapest BOOLEAN DEFAULT FALSE,
|
||||
is_selected BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route_node
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
|
@ -416,8 +406,8 @@ CREATE TABLE IF NOT EXISTS premise_route_node
|
|||
is_destination BOOLEAN DEFAULT FALSE,
|
||||
is_intermediate BOOLEAN DEFAULT FALSE,
|
||||
is_source BOOLEAN DEFAULT FALSE,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
geo_lat DECIMAL(8, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(8, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
is_outdated BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
|
|
@ -427,6 +417,18 @@ CREATE TABLE IF NOT EXISTS premise_route_node
|
|||
CONSTRAINT `chk_node` CHECK (`user_node_id` IS NULL OR `node_id` IS NULL)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_destination_id INT NOT NULL,
|
||||
is_fastest BOOLEAN DEFAULT FALSE,
|
||||
is_cheapest BOOLEAN DEFAULT FALSE,
|
||||
is_selected BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route_section
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
|
@ -435,22 +437,25 @@ CREATE TABLE IF NOT EXISTS premise_route_section
|
|||
to_route_node_id INT NOT NULL,
|
||||
list_position INT NOT NULL,
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN')),
|
||||
('RAIL', 'SEA', 'ROAD', 'POST_RUN')),
|
||||
rate_type CHAR(16) CHECK (rate_type IN
|
||||
('CONTAINER', 'MATRIX')),
|
||||
is_pre_run BOOLEAN DEFAULT FALSE,
|
||||
is_main_run BOOLEAN DEFAULT FALSE,
|
||||
is_post_run BOOLEAN DEFAULT FALSE,
|
||||
is_outdated BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (premise_route_id) REFERENCES premise_route (id),
|
||||
CONSTRAINT fk_premise_route_section_premise_route_id FOREIGN KEY (premise_route_id) REFERENCES premise_route (id),
|
||||
FOREIGN KEY (from_route_node_id) REFERENCES premise_route_node (id),
|
||||
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),
|
||||
CONSTRAINT chk_main_run CHECK ((transport_type = 'RAIL' OR transport_type = 'SEA') AND is_main_run IS TRUE),
|
||||
CONSTRAINT chk_main_run CHECK ((transport_type != 'RAIL' AND transport_type != 'SEA') OR is_main_run IS TRUE),
|
||||
INDEX idx_premise_route_id (premise_route_id),
|
||||
INDEX idx_from_route_node_id (from_route_node_id),
|
||||
INDEX idx_to_route_node_id (to_route_node_id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculation_job
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
|
@ -499,11 +504,11 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination
|
|||
-- custom
|
||||
custom_value DECIMAL(15, 2) NOT NULL,-- Zollwert,
|
||||
custom_duties DECIMAL(15, 2) NOT NULL,-- Zollabgaben,
|
||||
tariff_rate DECIMAL(7, 4) NOT NULL,-- Zollsatz,
|
||||
tariff_rate DECIMAL(8, 4) NOT NULL,-- Zollsatz,
|
||||
annual_custom_cost DECIMAL(15, 2) NOT NULL,-- Zollabgaben inkl. Einmalkosten,
|
||||
-- air freight risk
|
||||
air_freight_share_max DECIMAL(7, 4) NOT NULL,
|
||||
air_freight_share DECIMAL(7, 4) NOT NULL,
|
||||
air_freight_share_max DECIMAL(8, 4) NOT NULL,
|
||||
air_freight_share DECIMAL(8, 4) NOT NULL,
|
||||
air_freight_volumetric_weight DECIMAL(15, 2) NOT NULL,
|
||||
air_freight_weight DECIMAL(15, 2) NOT NULL,
|
||||
annual_air_freight_cost DECIMAL(15, 2) NOT NULL,
|
||||
|
|
@ -517,7 +522,7 @@ CREATE TABLE IF NOT EXISTS calculation_job_destination
|
|||
layer_count INT UNSIGNED NOT NULL COMMENT 'number of layers per full container or truck',
|
||||
transport_weight_exceeded BOOLEAN DEFAULT FALSE COMMENT 'limiting factor: TRUE if weight limited or FALSE if volume limited',
|
||||
annual_transportation_cost DECIMAL(15, 2) NOT NULL COMMENT 'total annual transportation costs in EUR',
|
||||
container_utilization DECIMAL(7, 4) NOT NULL,
|
||||
container_utilization DECIMAL(8, 4) NOT NULL,
|
||||
transit_time_in_days INT UNSIGNED NOT NULL,
|
||||
safety_stock_in_days INT UNSIGNED NOT NULL,
|
||||
-- material cost
|
||||
|
|
@ -536,7 +541,7 @@ CREATE TABLE IF NOT EXISTS calculation_job_route_section
|
|||
premise_route_section_id INT NOT NULL,
|
||||
calculation_job_destination_id INT NOT NULL,
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')),
|
||||
('RAIL', 'SEA', 'ROAD', 'POST_RUN', 'MATRIX', 'D2D')),
|
||||
is_unmixed_price BOOLEAN DEFAULT FALSE,
|
||||
is_cbm_price BOOLEAN DEFAULT FALSE,
|
||||
is_weight_price BOOLEAN DEFAULT FALSE,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,318 @@
|
|||
package de.avatic.lcc.controller.calculation;
|
||||
|
||||
import de.avatic.lcc.dto.generic.RateType;
|
||||
import de.avatic.lcc.dto.generic.TransportType;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.*;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@TestConfiguration
|
||||
public class PremiseControllerTestData {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public PremiseControllerTestData(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAll() {
|
||||
jdbcTemplate.execute("DELETE FROM premise_route_section");
|
||||
jdbcTemplate.execute("DELETE FROM premise_route_node");
|
||||
jdbcTemplate.execute("DELETE FROM premise_route");
|
||||
jdbcTemplate.execute("DELETE FROM premise_destination");
|
||||
|
||||
jdbcTemplate.execute("ALTER TABLE premise_route_section AUTO_INCREMENT = 1;");
|
||||
jdbcTemplate.execute("ALTER TABLE premise_route_node AUTO_INCREMENT = 1;");
|
||||
jdbcTemplate.execute("ALTER TABLE premise_route AUTO_INCREMENT = 1;");
|
||||
jdbcTemplate.execute("ALTER TABLE premise_destination AUTO_INCREMENT = 1;");
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public void generateData() {
|
||||
|
||||
var countryIdDE = getCountryId("DE");
|
||||
var countryIdCZ = getCountryId("CZ");
|
||||
var countryIdFR = getCountryId("FR");
|
||||
|
||||
Node nodeAb = getNode("AB");
|
||||
Node nodeStr = getNode("STR");
|
||||
Node nodeCtt = getNode("CTT");
|
||||
|
||||
Node nodeLx = getNode("LX");
|
||||
Node nodeCnxmn = getNode("CNXMN");
|
||||
Node nodeDeham = getNode("DEHAM");
|
||||
Node nodeBezee = getNode("BEZEE");
|
||||
|
||||
var destinationId1 = createPremiseDestination(getPremise1Id(), 110, nodeAb.getId(), false, null, null, BigDecimal.valueOf(10), BigDecimal.valueOf(10), BigDecimal.valueOf(10), BigDecimal.valueOf(49.9506), BigDecimal.valueOf(9.1038), countryIdDE);
|
||||
var destinationId2 = createPremiseDestination(getPremise1Id(), 120, nodeStr.getId(), false, null, null, BigDecimal.valueOf(12), BigDecimal.valueOf(12), BigDecimal.valueOf(12), BigDecimal.valueOf(49.7002), BigDecimal.valueOf(13.035), countryIdCZ);
|
||||
|
||||
var routeId1 = createPremiseRoute(destinationId1, false, false, true);
|
||||
var routeId2 = createPremiseRoute(destinationId1, true, true, false);
|
||||
|
||||
var route1NodeId1 = createPremiseRouteNode(nodeLx, false, false, false, true, false);
|
||||
var route1NodeId2 = createPremiseRouteNode(nodeCnxmn, false, false, true, false, false);
|
||||
var route1NodeId3 = createPremiseRouteNode(nodeDeham, false, false, true, false, false);
|
||||
var route1NodeId4 = createPremiseRouteNode(nodeAb, false, true, false, false, false);
|
||||
|
||||
var route1section1 = createPremiseRouteSection(routeId1, route1NodeId1, route1NodeId2, 1, TransportType.ROAD, RateType.CONTAINER, true, false, false, false);
|
||||
var route1section2 = createPremiseRouteSection(routeId1, route1NodeId2, route1NodeId3, 2, TransportType.SEA, RateType.CONTAINER, false, true, false, false);
|
||||
var route1section3 = createPremiseRouteSection(routeId1, route1NodeId3, route1NodeId4, 3, TransportType.POST_RUN, RateType.CONTAINER, false, false, true, false);
|
||||
|
||||
var route2NodeId1 = createPremiseRouteNode(nodeLx, false, false, false, true, false);
|
||||
var route2NodeId2 = createPremiseRouteNode(nodeCnxmn, false, false, true, false, false);
|
||||
var route2NodeId3 = createPremiseRouteNode(nodeBezee, false, false, true, false, false);
|
||||
var route2NodeId4 = createPremiseRouteNode(nodeAb, false, true, false, false, false);
|
||||
|
||||
var route2section1 = createPremiseRouteSection(routeId2, route2NodeId1, route2NodeId2, 1, TransportType.ROAD, RateType.CONTAINER, true, false, false, false);
|
||||
var route2section2 = createPremiseRouteSection(routeId2, route2NodeId2, route2NodeId3, 2, TransportType.SEA, RateType.CONTAINER, false, true, false, false);
|
||||
var route2section3 = createPremiseRouteSection(routeId2, route2NodeId3, route2NodeId4, 3, TransportType.POST_RUN, RateType.CONTAINER, false, false, true, false);
|
||||
|
||||
var destinationId3 = createPremiseDestination(getPremise3Id(), 310, nodeCtt.getId(), true, BigDecimal.valueOf(3000.0), 30, BigDecimal.valueOf(30), BigDecimal.valueOf(30),BigDecimal.valueOf(30),BigDecimal.valueOf(46.7762),BigDecimal.valueOf(0.5351), countryIdFR);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Integer createPremiseRouteSection(Integer routeId, Integer fromNode, Integer toNode, Integer listPosition, TransportType type, RateType rateType, boolean isPreRun, boolean isMainRun, boolean isPostRun, boolean isOutdated) {
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
String query = """
|
||||
INSERT INTO premise_route_section ( premise_route_id, from_route_node_id, to_route_node_id, list_position, transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ? ,?, ?, ?);
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
ps.setInt(1, routeId);
|
||||
ps.setInt(2, fromNode);
|
||||
ps.setInt(3, toNode);
|
||||
ps.setInt(4, listPosition);
|
||||
ps.setString(5, type.name());
|
||||
ps.setString(6, rateType.name());
|
||||
ps.setBoolean(7, isPreRun);
|
||||
ps.setBoolean(8, isMainRun);
|
||||
ps.setBoolean(9, isPostRun);
|
||||
ps.setBoolean(10, isOutdated);
|
||||
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
|
||||
|
||||
}
|
||||
|
||||
private Integer createPremiseRouteNode(Node node, Boolean isUserNode, Boolean isDestination, Boolean isIntermediate, Boolean isSource, Boolean isOutdated) {
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
String query = """
|
||||
INSERT INTO premise_route_node ( node_id, user_node_id, name, address, country_id, is_destination, is_intermediate, is_source, geo_lat, geo_lng, is_outdated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ? ,?, ?, ?, ?);
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (isUserNode) {
|
||||
ps.setNull(1, Types.INTEGER);
|
||||
} else {
|
||||
ps.setInt(1, node.getId());
|
||||
}
|
||||
|
||||
if (!isUserNode) {
|
||||
ps.setNull(2, Types.INTEGER);
|
||||
} else {
|
||||
ps.setInt(2, node.getId());
|
||||
}
|
||||
|
||||
ps.setString(3, node.getName());
|
||||
ps.setString(4, node.getAddress());
|
||||
ps.setInt(5, node.getCountryId());
|
||||
ps.setBoolean(6, isDestination);
|
||||
ps.setBoolean(7, isIntermediate);
|
||||
ps.setBoolean(8, isSource);
|
||||
ps.setBigDecimal(9, node.getGeoLat());
|
||||
ps.setBigDecimal(10, node.getGeoLng());
|
||||
ps.setBoolean(11, isOutdated);
|
||||
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
|
||||
}
|
||||
|
||||
private Node getNode(String externalMappingId) {
|
||||
String query = "SELECT * FROM node WHERE external_mapping_id = ?";
|
||||
return jdbcTemplate.queryForObject(query, new NodeMapper(), externalMappingId);
|
||||
}
|
||||
|
||||
private Integer getPremise1Id() {
|
||||
String query = "SELECT id FROM premise WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129') AND hu_unit_count = 2";
|
||||
|
||||
|
||||
return jdbcTemplate.queryForObject(query, Integer.class);
|
||||
}
|
||||
|
||||
|
||||
private Integer getPremise3Id() {
|
||||
String query = "SELECT id FROM premise WHERE material_id = (SELECT id FROM material WHERE part_number = '8222640822') AND hu_unit_count = 3";
|
||||
|
||||
|
||||
|
||||
return jdbcTemplate.queryForObject(query, Integer.class);
|
||||
}
|
||||
|
||||
private Integer getCountryId(String isoCode) {
|
||||
String query = "SELECT id FROM country WHERE iso_code = ?";
|
||||
return jdbcTemplate.queryForObject(query, Integer.class, isoCode);
|
||||
}
|
||||
|
||||
private Integer getNodeId(String externalMappingId) {
|
||||
String query = "SELECT id FROM node WHERE external_mapping_id = ?";
|
||||
return jdbcTemplate.queryForObject(query, Integer.class, externalMappingId);
|
||||
}
|
||||
|
||||
private Integer createPremiseRoute(Integer premiseDestinationId, Boolean isFastest, Boolean isCheapest, Boolean isSelected) {
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
String query = """
|
||||
INSERT INTO premise_route ( premise_destination_id, is_fastest, is_cheapest, is_selected)
|
||||
VALUES (?, ?, ?, ?);
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
ps.setInt(1, premiseDestinationId);
|
||||
ps.setBoolean(2, isFastest);
|
||||
ps.setBoolean(3, isCheapest);
|
||||
ps.setBoolean(4, isSelected);
|
||||
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
|
||||
}
|
||||
|
||||
private Integer createPremiseDestination(Integer premiseId, Integer annualAmount, Integer destinationNodeId,
|
||||
Boolean isD2D, BigDecimal rateD2D, Integer leadTimeD2D, BigDecimal repackingCost, BigDecimal handlingCost,
|
||||
BigDecimal disposalCost, BigDecimal geoLat, BigDecimal geoLng, Integer countryId) {
|
||||
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
String query = """
|
||||
INSERT INTO premise_destination (premise_id,
|
||||
annual_amount,
|
||||
destination_node_id,
|
||||
is_d2d,
|
||||
rate_d2d,
|
||||
lead_time_d2d,
|
||||
repacking_cost,
|
||||
handling_cost,
|
||||
disposal_cost,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
ps.setInt(1, premiseId);
|
||||
ps.setInt(2, annualAmount);
|
||||
ps.setInt(3, destinationNodeId);
|
||||
ps.setBoolean(4, isD2D);
|
||||
ps.setBigDecimal(5, rateD2D);
|
||||
if (leadTimeD2D == null) ps.setNull(6, Types.INTEGER);
|
||||
else ps.setInt(6, leadTimeD2D);
|
||||
ps.setBigDecimal(7, repackingCost);
|
||||
ps.setBigDecimal(8, handlingCost);
|
||||
ps.setBigDecimal(9, disposalCost);
|
||||
ps.setBigDecimal(10, geoLat);
|
||||
ps.setBigDecimal(11, geoLng);
|
||||
ps.setInt(12, countryId);
|
||||
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
|
||||
}
|
||||
|
||||
private List<Map<Integer, Integer>> getPredecessorsOf(Integer id) {
|
||||
|
||||
String queryChains = """
|
||||
SELECT chain.id AS id FROM node_predecessor_chain AS chain WHERE chain.node_id = ? ORDER BY chain.id
|
||||
""";
|
||||
|
||||
return jdbcTemplate.query(queryChains, (chainRs, rowNum) -> {
|
||||
|
||||
Integer chainId = chainRs.getInt("id");
|
||||
|
||||
String query = """
|
||||
SELECT entry.node_id AS predecessor_node_id , entry.sequence_number as sequence_number
|
||||
FROM node_predecessor_entry AS entry
|
||||
WHERE entry.node_predecessor_chain_id = ? ORDER BY entry.sequence_number""";
|
||||
|
||||
return jdbcTemplate.query(query, rs -> {
|
||||
Map<Integer, Integer> predecessors = new HashMap<>();
|
||||
|
||||
while (rs.next()) {
|
||||
predecessors.put(rs.getInt("sequence_number"), rs.getInt("predecessor_node_id"));
|
||||
}
|
||||
|
||||
return predecessors;
|
||||
}, chainId);
|
||||
}, id);
|
||||
}
|
||||
|
||||
private Collection<Integer> getOutboundCountriesOf(Integer id) {
|
||||
String query = """
|
||||
SELECT outbound_country_mapping.country_id
|
||||
FROM outbound_country_mapping
|
||||
WHERE outbound_country_mapping.node_id = ?""";
|
||||
|
||||
return jdbcTemplate.queryForList(query, Integer.class, id);
|
||||
}
|
||||
|
||||
private class NodeMapper implements RowMapper<Node> {
|
||||
|
||||
@Override
|
||||
public Node mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
var data = new Node();
|
||||
|
||||
data.setId(rs.getInt("id"));
|
||||
data.setName(rs.getString("name"));
|
||||
data.setAddress(rs.getString("address"));
|
||||
data.setCountryId(rs.getInt("country_id"));
|
||||
data.setDestination(rs.getBoolean("is_destination"));
|
||||
data.setSource(rs.getBoolean("is_source"));
|
||||
data.setIntermediate(rs.getBoolean("is_intermediate"));
|
||||
data.setPredecessorRequired(rs.getBoolean("predecessor_required"));
|
||||
|
||||
data.setGeoLat(rs.getBigDecimal("geo_lat"));
|
||||
data.setGeoLng(rs.getBigDecimal("geo_lng"));
|
||||
|
||||
data.setDeprecated(rs.getBoolean("is_deprecated"));
|
||||
|
||||
data.setNodePredecessors(getPredecessorsOf(data.getId()));
|
||||
data.setOutboundCountries(getOutboundCountriesOf(data.getId()));
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
package de.avatic.lcc.controller.configuration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.avatic.lcc.dto.configuration.nodes.update.NodeUpdateDTO;
|
||||
import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO;
|
||||
import de.avatic.lcc.dto.generic.CountryDTO;
|
||||
import de.avatic.lcc.dto.generic.LocationDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class NodeControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/nodes/ - List Nodes")
|
||||
class ListNodesTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return list of nodes with default pagination")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturnListOfNodesWithDefaultPagination() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
.andExpect(header().exists("X-Total-Count"))
|
||||
.andExpect(header().exists("X-Page-Count"))
|
||||
.andExpect(header().exists("X-Current-Page"))
|
||||
.andExpect(jsonPath("$", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$", hasSize(greaterThan(0))))
|
||||
.andExpect(jsonPath("$[0].id", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].country", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].country.id", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].country.iso_code", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].country.region_code", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].address", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].location", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].location.longitude", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].location.latitude", notNullValue()))
|
||||
.andExpect(jsonPath("$[0].types", isA(java.util.List.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return filtered nodes when filter parameter is provided")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturnFilteredNodes() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("filter", "Shanghai"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$[*].name", everyItem(containsStringIgnoringCase("Shanghai"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should respect pagination parameters")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldRespectPaginationParameters() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("page", "1")
|
||||
.param("limit", "5"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print())
|
||||
.andExpect(header().string("X-Current-Page", "1"))
|
||||
.andExpect(jsonPath("$", hasSize(lessThanOrEqualTo(5))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when no nodes match filter")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturnEmptyListWhenNoNodesMatchFilter() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("filter", "NonExistentCity"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(0)))
|
||||
.andExpect(header().string("X-Total-Count", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/nodes/search - Search Nodes")
|
||||
class SearchNodesTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should search nodes without type filter")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldSearchNodesWithoutTypeFilter() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/search")
|
||||
.param("filter", "KION")
|
||||
.param("limit", "10"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$", hasSize(lessThanOrEqualTo(10))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should search nodes with specific node type")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldSearchNodesWithSpecificNodeType() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/search")
|
||||
.param("node_type", "SOURCE")
|
||||
.param("limit", "10"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$[*].types", everyItem(hasItem("source"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should include user nodes when requested")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldIncludeUserNodesWhenRequested() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/search")
|
||||
.param("include_user_node", "true")
|
||||
.param("limit", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print())
|
||||
.andExpect(jsonPath("$", isA(java.util.List.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/nodes/{id} - Get Node Details")
|
||||
class GetNodeDetailsTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return node details for existing node")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturnNodeDetailsForExistingNode() throws Exception {
|
||||
// Using node ID 16 (LX - Linde China) from test data
|
||||
mockMvc.perform(get("/api/nodes/16"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
.andExpect(jsonPath("$.id", is(16)))
|
||||
.andExpect(jsonPath("$.country", notNullValue()))
|
||||
.andExpect(jsonPath("$.country.iso_code", is("CN")))
|
||||
.andExpect(jsonPath("$.name", notNullValue()))
|
||||
.andExpect(jsonPath("$.address", notNullValue()))
|
||||
.andExpect(jsonPath("$.location", notNullValue()))
|
||||
.andExpect(jsonPath("$.location.longitude", notNullValue()))
|
||||
.andExpect(jsonPath("$.location.latitude", notNullValue()))
|
||||
.andExpect(jsonPath("$.types", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$.types", hasItem("source")))
|
||||
.andExpect(jsonPath("$.predecessors", notNullValue()))
|
||||
.andExpect(jsonPath("$.outbound_countries", isA(java.util.List.class)))
|
||||
.andExpect(jsonPath("$.is_deprecated", is(false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return bad request for non-existing node")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturn400ForNonExistingNode() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/99999")).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return node with predecessor chains")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturnNodeWithPredecessorChains() throws Exception {
|
||||
// Using node ID 24 (CTT - Châtellerault) which has predecessor chains
|
||||
mockMvc.perform(get("/api/nodes/24"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print())
|
||||
.andExpect(jsonPath("$.id", is(24)))
|
||||
.andExpect(jsonPath("$.predecessors", notNullValue()))
|
||||
.andExpect(jsonPath("$.predecessors", not(empty())));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("PUT /api/nodes/{id} - Update Node")
|
||||
class UpdateNodeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should update existing node successfully")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldUpdateExistingNodeSuccessfully() throws Exception {
|
||||
NodeUpdateDTO updateDTO = new NodeUpdateDTO();
|
||||
updateDTO.setId(16);
|
||||
updateDTO.setAddress("Updated Address for Linde China");
|
||||
// Set other required fields based on your DTO structure
|
||||
|
||||
mockMvc.perform(put("/api/nodes/16")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(updateDTO)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("16"));
|
||||
|
||||
// Verify the update
|
||||
mockMvc.perform(get("/api/nodes/16"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.address", is("Updated Address for Linde China")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return 400 when ID in path doesn't match ID in body")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"
|
||||
}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturn400WhenIdMismatch() throws Exception {
|
||||
NodeUpdateDTO updateDTO = new NodeUpdateDTO();
|
||||
updateDTO.setId(17); // Different from path parameter
|
||||
updateDTO.setAddress("Some address");
|
||||
|
||||
mockMvc.perform(put("/api/nodes/16")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(updateDTO)))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return 400 when trying to update non-existing node")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturn400WhenUpdatingNonExistingNode() throws Exception {
|
||||
NodeUpdateDTO updateDTO = new NodeUpdateDTO();
|
||||
updateDTO.setId(99999);
|
||||
updateDTO.setAddress("Some address");
|
||||
|
||||
mockMvc.perform(put("/api/nodes/99999")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(updateDTO)))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("DELETE /api/nodes/{id} - Delete Node")
|
||||
class DeleteNodeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should mark node as deprecated successfully")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldMarkNodeAsDeprecatedSuccessfully() throws Exception {
|
||||
mockMvc.perform(delete("/api/nodes/16"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("16"));
|
||||
|
||||
// Verify the node is no longer in the regular list (since deprecated nodes are filtered out)
|
||||
mockMvc.perform(get("/api/nodes/"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[?(@.id == '16')]", hasSize(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return 404 when trying to delete non-existing node")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldReturn404WhenDeletingNonExistingNode() throws Exception {
|
||||
mockMvc.perform(delete("/api/nodes/99999"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("GET /api/nodes/locate - Locate Node")
|
||||
class LocateNodeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should locate node by address")
|
||||
void shouldLocateNodeByAddress() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/locate")
|
||||
.param("address", "Berlin, Germany"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$", notNullValue()));
|
||||
// Note: This test depends on the GeoApiService implementation
|
||||
// You might want to mock the service for more predictable results
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return 400 when address parameter is missing")
|
||||
void shouldReturn400WhenAddressParameterIsMissing() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/locate"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle invalid addresses gracefully")
|
||||
void shouldHandleInvalidAddressesGracefully() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/locate")
|
||||
.param("address", "InvalidAddressThatDoesNotExist123"))
|
||||
.andExpect(status().isOk()); // Depends on GeoApiService implementation
|
||||
// The actual behavior depends on how your GeoApiService handles invalid addresses
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("PUT /api/nodes/ - Add User Node")
|
||||
class AddUserNodeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should add user node successfully")
|
||||
@Sql(scripts = {"classpath:master_data/countries_properties.sql", "classpath:master_data/nodes.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/nodes-cleanup.sql", "classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldAddUserNodeSuccessfully() throws Exception {
|
||||
|
||||
CountryDTO countryDTO = new CountryDTO();
|
||||
countryDTO.setIsoCode("DE");
|
||||
|
||||
LocationDTO locationDTO = new LocationDTO(52.5139,13.4079);
|
||||
|
||||
AddUserNodeDTO addUserNodeDTO = new AddUserNodeDTO();
|
||||
addUserNodeDTO.setName("MyUserNode");
|
||||
addUserNodeDTO.setAddress("Rathausstraße 5, 10178 Berlin");
|
||||
addUserNodeDTO.setCountry(countryDTO);
|
||||
addUserNodeDTO.setLocation(locationDTO);
|
||||
|
||||
|
||||
mockMvc.perform(put("/api/nodes/")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(addUserNodeDTO)))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return 400 for invalid user node data")
|
||||
void shouldReturn400ForInvalidUserNodeData() throws Exception {
|
||||
AddUserNodeDTO invalidDTO = new AddUserNodeDTO();
|
||||
// Leave required fields empty to trigger validation errors
|
||||
|
||||
mockMvc.perform(put("/api/nodes/")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(invalidDTO)))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Error Handling Tests")
|
||||
class ErrorHandlingTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle malformed JSON in request body")
|
||||
void shouldHandleMalformedJsonInRequestBody() throws Exception {
|
||||
String malformedJson = "{ \"id\": \"invalid\" malformed }";
|
||||
|
||||
mockMvc.perform(put("/api/nodes/16")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(malformedJson))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle non-numeric node ID in path")
|
||||
void shouldHandleNonNumericNodeIdInPath() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/invalid-id"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle negative pagination parameters")
|
||||
void shouldHandleNegativePaginationParameters() throws Exception {
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("page", "-1")
|
||||
.param("limit", "-5"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Security Tests")
|
||||
class SecurityTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject requests with XSS attempts in filter")
|
||||
void shouldRejectRequestsWithXssAttemptsInFilter() throws Exception {
|
||||
String xssAttempt = "<script>alert('xss')</script>";
|
||||
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("filter", xssAttempt))
|
||||
.andExpect(status().isOk()) // Should not fail but should sanitize
|
||||
.andExpect(jsonPath("$[*].address", not(containsString("<script>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle SQL injection attempts in filter")
|
||||
void shouldHandleSqlInjectionAttemptsInFilter() throws Exception {
|
||||
String sqlInjection = "'; DROP TABLE node; --";
|
||||
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("filter", sqlInjection))
|
||||
.andExpect(status().isOk()); // Should not cause database issues
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Performance Tests")
|
||||
class PerformanceTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle large page sizes efficiently")
|
||||
@Sql(scripts = {
|
||||
"classpath:master_data/countries_properties.sql",
|
||||
"classpath:master_data/nodes.sql"
|
||||
}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {
|
||||
"classpath:master_data/nodes-cleanup.sql",
|
||||
"classpath:master_data/countries_properties-cleanup.sql"
|
||||
}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void shouldHandleLargePageSizesEfficiently() throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
mockMvc.perform(get("/api/nodes/")
|
||||
.param("limit", "1000"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
long executionTime = endTime - startTime;
|
||||
|
||||
// Assert that the request completes within reasonable time (adjust as needed)
|
||||
assert executionTime < 5000; // 5 seconds max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
package de.avatic.lcc.controller.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* Advanced integration tests for RateController covering edge cases and complex scenarios
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class RateControllerAdvancedIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private static final String BASE_URL = "/api/rates";
|
||||
|
||||
// Parameterized Tests
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"container", "matrix"})
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testRateEndpoints_WithDifferentLimits(String endpoint) throws Exception {
|
||||
int[] limits = {1, 5, 10, 20, 50};
|
||||
|
||||
for (int limit : limits) {
|
||||
mockMvc.perform(get(BASE_URL + "/" + endpoint)
|
||||
.param("limit", String.valueOf(limit))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(lessThanOrEqualTo(limit))));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"container,RAIL",
|
||||
"container,SEA",
|
||||
"container,ROAD",
|
||||
"container,POST-RUN"
|
||||
})
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testContainerRates_FilterByTransportType(String endpoint, String transportType) throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/" + endpoint)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[?(@.type == '" + transportType + "')]").exists());
|
||||
}
|
||||
|
||||
// Complex Date Range Tests
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testContainerRates_WithFutureDateFilter_ShouldReturnOnlyFutureValidRates() throws Exception {
|
||||
LocalDateTime futureDate = LocalDateTime.now().plusMonths(6);
|
||||
String formattedDate = futureDate.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("validAt", formattedDate)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[*].validity_period.state", everyItem(equalTo("VALID"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testContainerRates_WithPastDateFilter_ShouldReturnHistoricalRates() throws Exception {
|
||||
LocalDateTime pastDate = LocalDateTime.now().minusMonths(18);
|
||||
String formattedDate = pastDate.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("validAt", formattedDate)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
// Concurrent Request Tests
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testContainerRates_MultipleFiltersSimultaneously_ShouldPrioritizeValidAt() throws Exception {
|
||||
LocalDateTime validAt = LocalDateTime.now();
|
||||
String formattedDate = validAt.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
|
||||
// When both validAt and valid parameters are provided, validAt should take precedence
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("validAt", formattedDate)
|
||||
.param("valid", "2") // This should be ignored
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
// State Transition Tests
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testValidityPeriod_StateTransitions_DraftToValid() throws Exception {
|
||||
// First check if there are draft rates
|
||||
mockMvc.perform(get(BASE_URL + "/staged_changes")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$").value(true));
|
||||
|
||||
// Approve drafts
|
||||
mockMvc.perform(put(BASE_URL + "/staged_changes")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Verify no more drafts exist
|
||||
mockMvc.perform(get(BASE_URL + "/staged_changes")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$").value(false));
|
||||
}
|
||||
|
||||
// Edge Cases for Pagination
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testPagination_RequestingPageBeyondAvailable_ShouldReturnEmptyList() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "100")
|
||||
.param("page", "999")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(0)))
|
||||
.andExpect(header().string("X-Current-Page", "999"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testPagination_WithZeroLimit_ShouldUseDefaultLimit() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "0")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(greaterThan(0))));
|
||||
}
|
||||
|
||||
// Complex Filtering Scenarios
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testMatrixRates_FilterBySpecificCountryPairs() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/matrix")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[?(@.origin.country.iso_code == 'DE' && @.destination.country.iso_code == 'FR')]").exists());
|
||||
}
|
||||
|
||||
// Business Logic Validation Tests
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testContainerRates_ValidateRateHierarchy() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "100")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
// 40ft container rate should be less than 40HC rate
|
||||
.andExpect(jsonPath("$[*]", everyItem(
|
||||
allOf(
|
||||
hasEntry(equalTo("rates"),
|
||||
allOf(
|
||||
hasKey("40"),
|
||||
hasKey("40_HC")
|
||||
)
|
||||
)
|
||||
)
|
||||
)));
|
||||
}
|
||||
|
||||
// Null and Empty Value Handling
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testValidityPeriods_WithNullEndDates_ShouldHandleGracefully() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/periods")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[?(@.end_date == null)]").exists());
|
||||
}
|
||||
|
||||
// Special Character Handling
|
||||
|
||||
@Test
|
||||
void testInvalidParameters_WithSpecialCharacters_ShouldReturn400() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "20'; DROP TABLE container_rate; --")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
// Large Dataset Performance Test
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/rates-large-dataset.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testPerformance_WithLargeDataset_ShouldMaintainResponseTime() throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// Test with pagination to ensure efficient querying
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "50")
|
||||
.param("page", "10")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
// Even with large dataset, paginated request should be fast
|
||||
assert duration < 1000 : "Request took too long with large dataset: " + duration + "ms";
|
||||
}
|
||||
|
||||
// Idempotency Tests
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testInvalidatePeriod_MultipleCallsToSameId_ShouldBeIdempotent() throws Exception {
|
||||
Integer periodId = 4; // Using invalid period from test data
|
||||
|
||||
// First call
|
||||
mockMvc.perform(delete(BASE_URL + "/periods/{id}", periodId)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Second call - should still return OK (idempotent)
|
||||
mockMvc.perform(delete(BASE_URL + "/periods/{id}", periodId)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
package de.avatic.lcc.controller.configuration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.model.rates.ValidityPeriodState;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class RateControllerIntegrationTest {
|
||||
|
||||
private static final String BASE_URL = "/api/rates";
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
// Container Rate Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/container - List container rates")
|
||||
class ListContainerRatesTest {
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithoutFilter_ShouldReturnAllRates() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(greaterThan(0))))
|
||||
.andExpect(header().exists("X-Total-Count"))
|
||||
.andExpect(header().exists("X-Page-Count"))
|
||||
.andExpect(header().string("X-Current-Page", "1"))
|
||||
.andExpect(jsonPath("$[0].id").exists())
|
||||
.andExpect(jsonPath("$[0].origin").exists())
|
||||
.andExpect(jsonPath("$[0].destination").exists())
|
||||
.andExpect(jsonPath("$[0].type").exists())
|
||||
.andExpect(jsonPath("$[0].rates").exists())
|
||||
.andExpect(jsonPath("$[0].lead_time").exists())
|
||||
.andExpect(jsonPath("$[0].validity_period").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithPagination_ShouldReturnPagedResults() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "5")
|
||||
.param("page", "1")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(equalTo(5))))
|
||||
.andExpect(header().string("X-Current-Page", "1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithValidityPeriodFilter_ShouldReturnFilteredRates() throws Exception {
|
||||
// Assuming validity period ID 1 exists in test data
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("valid", "1")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[*].validity_period.id", everyItem(equalTo(1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithValidAtFilter_ShouldReturnValidRates() throws Exception {
|
||||
LocalDateTime validAt = LocalDateTime.now();
|
||||
String formattedDate = validAt.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("validAt", formattedDate)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Container Rate Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/container/id - get container rate detail")
|
||||
class GetContainerRatesTest {
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testGetContainerRate_WithValidId_ShouldReturnRate() throws Exception {
|
||||
// First, get a valid ID from the list
|
||||
MvcResult listResult = mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "1"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
List<ContainerRateDTO> rates = objectMapper.readValue(
|
||||
listResult.getResponse().getContentAsString(),
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, ContainerRateDTO.class)
|
||||
);
|
||||
|
||||
if (!rates.isEmpty()) {
|
||||
Integer rateId = rates.getFirst().getId();
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/container/{id}", rateId)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(rateId))
|
||||
.andExpect(jsonPath("$.origin").exists())
|
||||
.andExpect(jsonPath("$.destination").exists())
|
||||
.andExpect(jsonPath("$.type").exists())
|
||||
.andExpect(jsonPath("$.rates.FEU").exists())
|
||||
.andExpect(jsonPath("$.rates.TEU").exists())
|
||||
.andExpect(jsonPath("$.rates.HQ").exists());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetContainerRate_WithInvalidId_ShouldReturn400() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container/{id}", 99999)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Matrix Rate Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/matrix/ - list matrix rates")
|
||||
class ListMatrixRatesTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListMatrixRates_WithoutFilter_ShouldReturnAllRates() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/matrix")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(0))))
|
||||
.andExpect(header().exists("X-Total-Count"))
|
||||
.andExpect(header().exists("X-Page-Count"))
|
||||
.andExpect(header().string("X-Current-Page", "1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListMatrixRates_WithValidFilter_ShouldReturnFilteredRates() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/matrix")
|
||||
.param("valid", "1")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("/api/rates/matrix/id - get matrix rate detail")
|
||||
class GetMatrixRatesTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testGetMatrixRate_WithValidId_ShouldReturnRate() throws Exception {
|
||||
// First, get a valid ID from the list
|
||||
MvcResult listResult = mockMvc.perform(get(BASE_URL + "/matrix")
|
||||
.param("limit", "1"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
List<MatrixRateDTO> rates = objectMapper.readValue(
|
||||
listResult.getResponse().getContentAsString(),
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, MatrixRateDTO.class)
|
||||
);
|
||||
|
||||
if (!rates.isEmpty()) {
|
||||
Integer rateId = rates.getFirst().getId();
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/matrix/{id}", rateId)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.id").value(rateId))
|
||||
.andExpect(jsonPath("$.origin").exists())
|
||||
.andExpect(jsonPath("$.destination").exists())
|
||||
.andExpect(jsonPath("$.rate").exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Validity Period Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/periods - List validity periods")
|
||||
class ListPeriodsTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListPeriods_ShouldReturnAllPeriods() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/periods")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(0))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
@DisplayName("/api/rates/periods/id - Invalidate validity periods")
|
||||
class GetPeriodsTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testInvalidatePeriod_WithValidId_ShouldReturnOk() throws Exception {
|
||||
// First get a period to invalidate
|
||||
MvcResult listResult = mockMvc.perform(get(BASE_URL + "/periods"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
List<ValidityPeriodDTO> periods = objectMapper.readValue(
|
||||
listResult.getResponse().getContentAsString(),
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, ValidityPeriodDTO.class)
|
||||
);
|
||||
|
||||
assertThat(periods).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty();
|
||||
Integer periodId = periods.stream().filter(p -> p.getState().equals(ValidityPeriodState.EXPIRED)).findFirst().orElseThrow(() -> new AssertionError("No expired periods found")).getId();
|
||||
|
||||
mockMvc.perform(delete(BASE_URL + "/periods/{id}", periodId)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
listResult = mockMvc.perform(get(BASE_URL + "/periods"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
periods = objectMapper.readValue(
|
||||
listResult.getResponse().getContentAsString(),
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, ValidityPeriodDTO.class)
|
||||
);
|
||||
|
||||
assertThat(periods).asInstanceOf(InstanceOfAssertFactories.LIST).isNotEmpty();
|
||||
assertThat(periods.stream().filter(p -> p.getId().equals(periodId)).findFirst().orElseThrow(() -> new AssertionError("Periods with id " + periodId + " not found"))).extracting(ValidityPeriodDTO::getState).isEqualTo(ValidityPeriodState.INVALID);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testInvalidatePeriod_WithInvalidId_ShouldReturn404() throws Exception {
|
||||
mockMvc.perform(delete(BASE_URL + "/periods/{id}", 99999)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Staged Changes Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/staged_changes - staged changes")
|
||||
class StagedChangesTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testCheckRateDrafts_ShouldReturnBoolean() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/staged_changes")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(print())
|
||||
.andExpect(jsonPath("$").isBoolean())
|
||||
.andExpect(jsonPath("$").value(true));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testApproveRateDrafts_ShouldReturnOk() throws Exception {
|
||||
mockMvc.perform(put(BASE_URL + "/staged_changes")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Edge Cases and Error Handling Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/ - Edge cases")
|
||||
class EdgeCasesTest {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithInvalidDateFormat_ShouldReturn400() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("validAt", "invalid-date")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_WithNegativeLimit_ShouldReturn400() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "-1")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListMatrixRates_WithNegativePage_ShouldReturn400() throws Exception {
|
||||
mockMvc.perform(get(BASE_URL + "/matrix")
|
||||
.param("page", "-1")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Performance Tests
|
||||
@Nested
|
||||
@DisplayName("/api/rates/ - Performance tests")
|
||||
class PerformanceTests {
|
||||
@Test
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
|
||||
void testListContainerRates_LargeLimit_ShouldCompleteInReasonableTime() throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
mockMvc.perform(get(BASE_URL + "/container")
|
||||
.param("limit", "1000")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
// Assert that the request completes within 5 seconds
|
||||
assertTrue(duration < 5000, "Request took too long: " + duration + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
76
src/test/resources/master_data/alldata-cleanup.sql
Normal file
76
src/test/resources/master_data/alldata-cleanup.sql
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
-- cleanup of full setup data in alldata.sql
|
||||
|
||||
|
||||
delete from container_rate where 1;
|
||||
ALTER TABLE container_rate AUTO_INCREMENT = 1;
|
||||
|
||||
delete from country_matrix_rate where 1;
|
||||
ALTER TABLE country_matrix_rate AUTO_INCREMENT = 1;
|
||||
|
||||
delete from validity_period where 1;
|
||||
ALTER TABLE validity_period AUTO_INCREMENT = 1;
|
||||
|
||||
delete
|
||||
from packaging_property
|
||||
where 1;
|
||||
delete
|
||||
from packaging_property_type
|
||||
where 1;
|
||||
delete
|
||||
from packaging
|
||||
where 1;
|
||||
delete
|
||||
from packaging_dimension
|
||||
where 1;
|
||||
delete
|
||||
from material
|
||||
where 1;
|
||||
|
||||
|
||||
ALTER TABLE packaging_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE packaging_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE packaging AUTO_INCREMENT = 1;
|
||||
ALTER TABLE packaging_dimension AUTO_INCREMENT = 1;
|
||||
ALTER TABLE material AUTO_INCREMENT = 1;
|
||||
|
||||
delete
|
||||
from node_predecessor_entry
|
||||
where 1;
|
||||
delete
|
||||
from node_predecessor_chain
|
||||
where 1;
|
||||
delete
|
||||
from node
|
||||
where 1;
|
||||
|
||||
|
||||
ALTER TABLE node AUTO_INCREMENT = 1;
|
||||
ALTER TABLE node_predecessor_chain AUTO_INCREMENT = 1;
|
||||
ALTER TABLE node_predecessor_entry AUTO_INCREMENT = 1;
|
||||
|
||||
|
||||
delete
|
||||
from country_property
|
||||
where 1;
|
||||
delete
|
||||
from country_property_type
|
||||
where 1;
|
||||
delete
|
||||
from country
|
||||
where 1;
|
||||
delete
|
||||
from system_property
|
||||
where 1;
|
||||
delete
|
||||
from system_property_type
|
||||
where 1;
|
||||
delete
|
||||
from property_set
|
||||
where 1;
|
||||
|
||||
ALTER TABLE country_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE country_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE country AUTO_INCREMENT = 1;
|
||||
ALTER TABLE system_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE system_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE property_set AUTO_INCREMENT = 1;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -16,3 +16,11 @@ where 1;
|
|||
delete
|
||||
from lcc_test.property_set
|
||||
where 1;
|
||||
|
||||
ALTER TABLE lcc_test.country_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.country_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.country AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.system_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.system_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.property_set AUTO_INCREMENT = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,3 +13,11 @@ where 1;
|
|||
delete
|
||||
from lcc_test.material
|
||||
where 1;
|
||||
|
||||
|
||||
ALTER TABLE lcc_test.packaging_property AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.packaging_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.packaging AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.packaging_dimension AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.material AUTO_INCREMENT = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ SET @packaging_3064540201 = LAST_INSERT_ID();
|
|||
-- Part Number: 8212640113
|
||||
INSERT INTO packaging (supplier_node_id, material_id, hu_dimension_id, shu_dimension_id, is_deprecated)
|
||||
VALUES (
|
||||
(SELECT id FROM node WHERE name = 'Linde (China) Forklift Truck (Supplier)' LIMIT 1),
|
||||
(SELECT id FROM node WHERE name = 'KION Baoli (Jiangsu) Forklift Co., Ltd.' LIMIT 1),
|
||||
(SELECT id FROM material WHERE part_number = '8212640113' LIMIT 1),
|
||||
@hu_dim_8212640113,
|
||||
@shu_dim_8212640113,
|
||||
|
|
|
|||
|
|
@ -7,3 +7,8 @@ where 1;
|
|||
delete
|
||||
from lcc_test.node
|
||||
where 1;
|
||||
|
||||
|
||||
ALTER TABLE lcc_test.node AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.node_predecessor_chain AUTO_INCREMENT = 1;
|
||||
ALTER TABLE lcc_test.node_predecessor_entry AUTO_INCREMENT = 1;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
12
src/test/resources/master_data/premises-cleanup.sql
Normal file
12
src/test/resources/master_data/premises-cleanup.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
delete from premise_route_section where 1;
|
||||
delete from premise_route_node where 1;
|
||||
delete from premise_route where 1;
|
||||
delete from premise_destination where 1;
|
||||
delete from premise where 1;
|
||||
|
||||
ALTER TABLE premise AUTO_INCREMENT = 1;
|
||||
ALTER TABLE premise_destination AUTO_INCREMENT = 1;
|
||||
ALTER TABLE premise_route AUTO_INCREMENT = 1;
|
||||
ALTER TABLE premise_route_section AUTO_INCREMENT = 1;
|
||||
ALTER TABLE premise_route_node AUTO_INCREMENT = 1;
|
||||
|
||||
458
src/test/resources/master_data/premises.sql
Normal file
458
src/test/resources/master_data/premises.sql
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
-- ============================================
|
||||
-- Premise Data Generation Script
|
||||
-- ============================================
|
||||
-- This script creates fake users and premises based on existing materials, nodes, and packaging
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- Insert Premises using existing materials and packaging
|
||||
-- ============================================
|
||||
START TRANSACTION;
|
||||
-- Premise 1: Gearbox housing blank from Linde China | john.doe@company.com | COMPLETED (0-Month-old)
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129')),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'COMPLETED',
|
||||
1200,
|
||||
400,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
2,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
|
||||
SET @premise_id_1 = LAST_INSERT_ID();
|
||||
|
||||
-- Premise 2: Gearbox housing blank from Linde China | john.doe@company.com | COMPLETED (3-Month-old)
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129')),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'COMPLETED',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
DATE_SUB(NOW(), INTERVAL 3 MONTH));
|
||||
SET @premiseId2 = LAST_INSERT_ID();
|
||||
|
||||
-- Premise 3: planet gear carrier blank 'stage 1 from Linde China | john.doe@company.com | DRAFT
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '8222640822')),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'DRAFT',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
3,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId3 = LAST_INSERT_ID();
|
||||
|
||||
|
||||
-- Premise 4: planet gear carrier blank 'stage 1 from Linde China | john.doe@company.com | COMPLETED
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '8222640822')),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'COMPLETED',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId4 = LAST_INSERT_ID();
|
||||
|
||||
-- Premise 5: planet gear carrier blank 'stage 1 from Linde China | sarah.smith@company.com | DRAFT
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '8222640822'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '8222640822')),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'DRAFT',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'KG',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId5 = LAST_INSERT_ID();
|
||||
|
||||
|
||||
-- Premise 6: wheel hub from Linde China | sarah.smith@company.com | DRAFT
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '3064540201'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '3064540201')),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'DRAFT',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'KG',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId6 = LAST_INSERT_ID();
|
||||
|
||||
|
||||
-- Premise 7: wheel hub from Linde China | sarah.smith@company.com | COMPLETED
|
||||
INSERT INTO premise (material_id,
|
||||
supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '3064540201'),
|
||||
(SELECT id FROM node WHERE external_mapping_id = 'LX'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '3064540201')),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'COMPLETED',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'KG',
|
||||
4,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId7 = LAST_INSERT_ID();
|
||||
|
||||
|
||||
-- Premise 8: Gearbox housing blank from Linde China | john.doe@company.com | DRAFT
|
||||
INSERT INTO premise (material_id,
|
||||
user_supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
||||
(SELECT id FROM sys_user_node WHERE name = 'My Supplier 1'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129')),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'DRAFT',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
8,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId8 = LAST_INSERT_ID();
|
||||
|
||||
|
||||
-- Premise 9: gearbox housing blank 'GR4H-10 from Linde China | sarah.smith@company.com | COMPLETED
|
||||
INSERT INTO premise (material_id,
|
||||
user_supplier_node_id,
|
||||
geo_lat,
|
||||
geo_lng,
|
||||
country_id,
|
||||
packaging_id,
|
||||
user_id,
|
||||
material_cost,
|
||||
is_fca_enabled,
|
||||
oversea_share,
|
||||
hs_code,
|
||||
tariff_rate,
|
||||
state,
|
||||
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,
|
||||
updated_at)
|
||||
VALUES ((SELECT id FROM material WHERE part_number = '28152640129'),
|
||||
(SELECT id FROM sys_user_node WHERE name = 'My Supplier 2'),
|
||||
24.489,
|
||||
118.1478,
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
(SELECT id FROM packaging WHERE material_id = (SELECT id FROM material WHERE part_number = '28152640129')),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'),
|
||||
2850.50,
|
||||
TRUE,
|
||||
0.75,
|
||||
'84839089',
|
||||
0.065,
|
||||
'COMPLETED',
|
||||
1200,
|
||||
790,
|
||||
700,
|
||||
677000,
|
||||
'MM',
|
||||
'G',
|
||||
1,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NOW());
|
||||
SET @premiseId9 = LAST_INSERT_ID();
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
-- Cleanup script for RateController Integration Tests
|
||||
-- (RateControllerIntegrationTest.java)
|
||||
|
||||
-- Delete in reverse order of foreign key dependencies
|
||||
|
||||
-- Delete system properties
|
||||
DELETE FROM system_property WHERE property_set_id IN (1, 2);
|
||||
|
||||
-- Delete system property types
|
||||
DELETE FROM system_property_type WHERE id IN (1, 2, 3);
|
||||
|
||||
-- Delete property sets
|
||||
DELETE FROM property_set WHERE id IN (1, 2);
|
||||
|
||||
-- Delete country matrix rates
|
||||
DELETE FROM country_matrix_rate WHERE validity_period_id IN (1, 2, 3, 4);
|
||||
|
||||
-- Delete container rates
|
||||
DELETE FROM container_rate WHERE validity_period_id IN (1, 2, 3, 4);
|
||||
|
||||
-- Delete validity periods
|
||||
DELETE FROM validity_period WHERE id IN (1, 2, 3, 4);
|
||||
|
||||
-- Delete nodes
|
||||
DELETE FROM node WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8);
|
||||
|
||||
-- Delete countries
|
||||
DELETE FROM country WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8);
|
||||
|
||||
-- Reset auto-increment counters if needed (optional)
|
||||
ALTER TABLE country AUTO_INCREMENT = 1;
|
||||
ALTER TABLE node AUTO_INCREMENT = 1;
|
||||
ALTER TABLE validity_period AUTO_INCREMENT = 1;
|
||||
ALTER TABLE container_rate AUTO_INCREMENT = 1;
|
||||
ALTER TABLE country_matrix_rate AUTO_INCREMENT = 1;
|
||||
ALTER TABLE property_set AUTO_INCREMENT = 1;
|
||||
ALTER TABLE system_property_type AUTO_INCREMENT = 1;
|
||||
ALTER TABLE system_property AUTO_INCREMENT = 1;
|
||||
95
src/test/resources/master_data/reduced_rate_setup.sql
Normal file
95
src/test/resources/master_data/reduced_rate_setup.sql
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
-- Reduced test data set for RateController Integration Tests
|
||||
-- (RateControllerIntegrationTest.java)
|
||||
|
||||
-- Insert test countries
|
||||
INSERT INTO country (id, iso_code, region_code, is_deprecated) VALUES
|
||||
(1, 'DE', 'EMEA', FALSE),
|
||||
(2, 'FR', 'EMEA', FALSE),
|
||||
(3, 'IT', 'EMEA', FALSE),
|
||||
(4, 'ES', 'EMEA', FALSE),
|
||||
(5, 'CZ', 'EMEA', FALSE),
|
||||
(6, 'PL', 'EMEA', FALSE),
|
||||
(7, 'NL', 'EMEA', FALSE),
|
||||
(8, 'BE', 'EMEA', FALSE);
|
||||
|
||||
-- Insert test nodes
|
||||
INSERT INTO node (id, country_id, name, address, external_mapping_id, predecessor_required, is_destination, is_source, is_intermediate, geo_lat, geo_lng, is_deprecated) VALUES
|
||||
(1, 1, 'Hamburg Port', 'Port of Hamburg, Germany', 'HAM001', FALSE, TRUE, TRUE, FALSE, 53.5511, 9.9937, FALSE),
|
||||
(2, 1, 'Munich Terminal', 'Munich Rail Terminal, Germany', 'MUC001', FALSE, TRUE, TRUE, TRUE, 48.1351, 11.5820, FALSE),
|
||||
(3, 1, 'Berlin Hub', 'Berlin Central Hub, Germany', 'BER001', FALSE, TRUE, TRUE, TRUE, 52.5200, 13.4050, FALSE),
|
||||
(4, 2, 'Paris Terminal', 'Paris Logistics Center, France', 'PAR001', FALSE, TRUE, TRUE, TRUE, 48.8566, 2.3522, FALSE),
|
||||
(5, 3, 'Milan Hub', 'Milan Distribution Center, Italy', 'MIL001', FALSE, TRUE, TRUE, TRUE, 45.4642, 9.1900, FALSE),
|
||||
(6, 4, 'Barcelona Port', 'Port of Barcelona, Spain', 'BCN001', FALSE, TRUE, TRUE, FALSE, 41.3851, 2.1734, FALSE),
|
||||
(7, 5, 'Prague Hub', 'Prague Distribution Center, Czech Republic', 'PRG001', FALSE, TRUE, TRUE, TRUE, 50.0755, 14.4378, FALSE),
|
||||
(8, 6, 'Warsaw Terminal', 'Warsaw Logistics Terminal, Poland', 'WAW001', FALSE, TRUE, TRUE, TRUE, 52.2297, 21.0122, FALSE);
|
||||
|
||||
-- Insert validity periods
|
||||
INSERT INTO validity_period (id, start_date, end_date, state) VALUES
|
||||
(1, DATE_SUB(NOW(), INTERVAL 1 MONTH), DATE_ADD(NOW(), INTERVAL 11 MONTH), 'VALID'),
|
||||
(2, DATE_ADD(NOW(), INTERVAL 1 DAY), NULL, 'DRAFT'),
|
||||
(3, DATE_SUB(NOW(), INTERVAL 2 YEAR), DATE_SUB(NOW(), INTERVAL 1 YEAR), 'EXPIRED'),
|
||||
(4, DATE_SUB(NOW(), INTERVAL 6 MONTH), DATE_SUB(NOW(), INTERVAL 1 DAY), 'INVALID');
|
||||
|
||||
-- Insert container rates for VALID period
|
||||
INSERT INTO container_rate (id, from_node_id, to_node_id, container_rate_type, rate_teu, rate_feu, rate_hc, lead_time, validity_period_id) VALUES
|
||||
-- Rail connections
|
||||
(1, 1, 2, 'RAIL', 1000.00, 1800.00, 2000.00, 3, 1),
|
||||
(2, 1, 3, 'RAIL', 800.00, 1500.00, 1700.00, 2, 1),
|
||||
(3, 3, 4, 'RAIL', 1200.00, 2200.00, 2400.00, 4, 1),
|
||||
(4, 3, 7, 'RAIL', 900.00, 1700.00, 1900.00, 3, 1),
|
||||
(5, 2, 5, 'RAIL', 1500.00, 2800.00, 3000.00, 5, 1),
|
||||
|
||||
-- Sea connections
|
||||
(6, 1, 6, 'SEA', 2000.00, 3800.00, 4200.00, 14, 1),
|
||||
(7, 6, 5, 'SEA', 1800.00, 3400.00, 3800.00, 10, 1),
|
||||
|
||||
-- Road connections
|
||||
(8, 2, 3, 'ROAD', 600.00, 1100.00, 1300.00, 1, 1),
|
||||
(9, 4, 5, 'ROAD', 1400.00, 2600.00, 2900.00, 2, 1),
|
||||
(10, 7, 8, 'ROAD', 700.00, 1300.00, 1500.00, 1, 1),
|
||||
|
||||
-- Post-run connections
|
||||
(11, 3, 2, 'POST_RUN', 400.00, 750.00, 850.00, 1, 1),
|
||||
(12, 5, 4, 'POST_RUN', 500.00, 950.00, 1050.00, 1, 1);
|
||||
|
||||
-- Insert container rates for DRAFT period
|
||||
INSERT INTO container_rate (from_node_id, to_node_id, container_rate_type, rate_teu, rate_feu, rate_hc, lead_time, validity_period_id) VALUES
|
||||
(1, 2, 'RAIL', 1100.00, 2000.00, 2200.00, 3, 2),
|
||||
(1, 3, 'RAIL', 900.00, 1700.00, 1900.00, 2, 2),
|
||||
(3, 4, 'RAIL', 1300.00, 2400.00, 2600.00, 4, 2);
|
||||
|
||||
-- Insert country matrix rates for VALID period
|
||||
INSERT INTO country_matrix_rate (id, from_country_id, to_country_id, rate, validity_period_id) VALUES
|
||||
(1, 1, 2, 2.50, 1), -- Germany to France
|
||||
(2, 1, 3, 3.20, 1), -- Germany to Italy
|
||||
(3, 1, 4, 3.80, 1), -- Germany to Spain
|
||||
(4, 1, 5, 2.20, 1), -- Germany to Czech Republic
|
||||
(5, 2, 3, 2.90, 1), -- France to Italy
|
||||
(6, 2, 4, 2.40, 1), -- France to Spain
|
||||
(7, 3, 4, 3.50, 1), -- Italy to Spain
|
||||
(8, 5, 6, 1.80, 1), -- Czech Republic to Poland
|
||||
(9, 1, 7, 2.00, 1), -- Germany to Netherlands
|
||||
(10, 1, 8, 2.30, 1); -- Germany to Belgium
|
||||
|
||||
-- Insert country matrix rates for DRAFT period
|
||||
INSERT INTO country_matrix_rate (from_country_id, to_country_id, rate, validity_period_id) VALUES
|
||||
(1, 2, 2.70, 2), -- Germany to France (increased rate)
|
||||
(1, 3, 3.40, 2), -- Germany to Italy (increased rate)
|
||||
(1, 4, 4.00, 2); -- Germany to Spain (increased rate)
|
||||
|
||||
-- Insert property sets for testing
|
||||
INSERT INTO property_set (id, start_date, end_date, state) VALUES
|
||||
(1, DATE_SUB(NOW(), INTERVAL 1 MONTH), DATE_ADD(NOW(), INTERVAL 11 MONTH), 'VALID'),
|
||||
(2, DATE_ADD(NOW(), INTERVAL 1 DAY), NULL, 'DRAFT');
|
||||
|
||||
-- Insert system property types
|
||||
INSERT INTO system_property_type (id, name, external_mapping_id, data_type, validation_rule) VALUES
|
||||
(1, 'Default Lead Time Buffer', 'LEAD_TIME_BUF', 'INT', 'MIN:0,MAX:30'),
|
||||
(2, 'Container Loading Factor', 'CONT_LOAD_FAC', 'PERCENTAGE', 'MIN:0,MAX:100'),
|
||||
(3, 'Enable Rate Approval', 'RATE_APPROVAL', 'BOOLEAN', NULL);
|
||||
|
||||
-- Insert system properties
|
||||
INSERT INTO system_property (property_set_id, system_property_type_id, property_value) VALUES
|
||||
(1, 1, '2'),
|
||||
(1, 2, '85'),
|
||||
(1, 3, 'true');
|
||||
23
src/test/resources/master_data/users-cleanup.sql
Normal file
23
src/test/resources/master_data/users-cleanup.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
delete
|
||||
from sys_user_group_mapping
|
||||
where 1;
|
||||
|
||||
delete
|
||||
from sys_user_node
|
||||
where 1;
|
||||
|
||||
delete
|
||||
from sys_group
|
||||
where 1;
|
||||
|
||||
delete
|
||||
from sys_user
|
||||
where 1;
|
||||
|
||||
ALTER TABLE sys_user_group_mapping AUTO_INCREMENT = 1;
|
||||
ALTER TABLE sys_user_node AUTO_INCREMENT = 1;
|
||||
ALTER TABLE sys_group AUTO_INCREMENT = 1;
|
||||
ALTER TABLE sys_user AUTO_INCREMENT = 1;
|
||||
|
||||
|
||||
|
||||
54
src/test/resources/master_data/users.sql
Normal file
54
src/test/resources/master_data/users.sql
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
-- First, create some fake users since premise table requires user_id
|
||||
INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active)
|
||||
VALUES ('USR001', 'john.doe@company.com', 'John', 'Doe', TRUE),
|
||||
('USR002', 'sarah.smith@company.com', 'Sarah', 'Smith', TRUE),
|
||||
('USR003', 'mike.johnson@company.com', 'Mike', 'Johnson', TRUE),
|
||||
('USR004', 'anna.mueller@company.com', 'Anna', 'Mueller', TRUE),
|
||||
('USR005', 'david.chen@company.com', 'David', 'Chen', TRUE)
|
||||
ON DUPLICATE KEY UPDATE email = VALUES(email);
|
||||
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('default', 'Default user: Can login and generate reports');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('LCE', 'Logistic cost expert: Can login, generate reports and do calculations');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('freight', 'Freight key user: Can login, generate reports and edit freight rates');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('packaging', 'Packaging key user: Can login, generate reports and edit packaging data');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('super',
|
||||
'Super key user: Can login, generate reports, do calculations, edit freight rates, edit packaging data');
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
(SELECT id FROM sys_user WHERE email = 'mike.johnson@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
(SELECT id FROM sys_user WHERE email = 'anna.mueller@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
(SELECT id FROM sys_user WHERE email = 'david.chen@company.com'));
|
||||
|
||||
INSERT INTO sys_user_node (user_id, country_id, name, address, geo_lat, geo_lng, is_deprecated)
|
||||
VALUES ((SELECT id FROM sys_user WHERE email = 'john.doe@company.com'),
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
'My Supplier 1', 'My Road 1, 1234 MyCity',
|
||||
24.489,
|
||||
118.1478, false);
|
||||
|
||||
INSERT INTO sys_user_node (user_id, country_id, name, address, geo_lat, geo_lng, is_deprecated)
|
||||
VALUES ((SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'),
|
||||
(SELECT id FROM country WHERE iso_code = 'CN'),
|
||||
'My Supplier 2', 'My Road 2, 1234 MyCity',
|
||||
24.489,
|
||||
118.1478, false);
|
||||
|
|
@ -1,561 +0,0 @@
|
|||
-- DROP TABLE IF EXISTS `lcc`.`calculation_job`, `lcc`.`calculation_job_destination`, `lcc`.`calculation_job_route_section`, `lcc`.`container_rate`, `lcc`.`country`, `lcc`.`country_matrix_rate`, `lcc`.`country_property`, `lcc`.`country_property_type`, `lcc`.`distance_matrix`, `lcc`.`material`, `lcc`.`node`, `lcc`.`node_predecessor_chain`, `lcc`.`node_predecessor_entry`, `lcc`.`outbound_country_mapping`, `lcc`.`packaging`, `lcc`.`packaging_dimension`, `lcc`.`packaging_property`, `lcc`.`packaging_property_type`, `lcc`.`premise`, `lcc`.`premise_destination`, `lcc`.`premise_route`, `lcc`.`premise_route_node`, `lcc`.`premise_route_section`, `lcc`.`property_set`, `lcc`.`sys_group`, `lcc`.`sys_user`, `lcc`.`sys_user_group_mapping`, `lcc`.`sys_user_node`, `lcc`.`system_property`, `lcc`.`system_property_type`, `lcc`.`validity_period`;
|
||||
|
||||
-- Property management tables
|
||||
CREATE TABLE IF NOT EXISTS `property_set`
|
||||
(
|
||||
-- Represents a collection of properties valid for a specific time period
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`start_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`end_date` TIMESTAMP NULL,
|
||||
`state` CHAR(8) NOT NULL,
|
||||
CONSTRAINT `chk_property_state_values` CHECK (`state` IN ('DRAFT', 'VALID', 'INVALID', 'EXPIRED')),
|
||||
CONSTRAINT `chk_property_date_range` CHECK (`end_date` IS NULL OR `end_date` > `start_date`),
|
||||
INDEX `idx_dates` (`start_date`, `end_date`),
|
||||
INDEX `idx_property_set_id` (id)
|
||||
) COMMENT 'Manages versioned sets of properties with temporal validity';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `system_property_type`
|
||||
(
|
||||
-- Stores system-wide configuration property types
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`external_mapping_id` VARCHAR(16),
|
||||
`data_type` VARCHAR(16) NOT NULL,
|
||||
`validation_rule` VARCHAR(64),
|
||||
UNIQUE KEY `idx_external_mapping` (`external_mapping_id`),
|
||||
CONSTRAINT `chk_system_data_type_values` CHECK (`data_type` IN
|
||||
('INT', 'PERCENTAGE', 'BOOLEAN', 'CURRENCY', 'ENUMERATION',
|
||||
'TEXT'))
|
||||
) COMMENT 'Stores system-wide configuration property types';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `system_property`
|
||||
(
|
||||
-- Stores system-wide configuration properties
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`property_set_id` INT NOT NULL,
|
||||
`system_property_type_id` INT NOT NULL,
|
||||
`property_value` VARCHAR(500),
|
||||
FOREIGN KEY (`property_set_id`) REFERENCES `property_set` (`id`),
|
||||
FOREIGN KEY (`system_property_type_id`) REFERENCES `system_property_type` (`id`),
|
||||
INDEX `idx_system_property_type_id` (system_property_type_id),
|
||||
INDEX `idx_property_set_id` (id),
|
||||
UNIQUE KEY `idx_system_property_type_id_property_set` (`system_property_type_id`, `property_set_id`)
|
||||
) COMMENT 'Stores system-wide configuration properties';
|
||||
|
||||
-- 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,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_country_iso_code` (`iso_code`),
|
||||
CONSTRAINT `chk_country_region_code`
|
||||
CHECK (`region_code` IN ('EMEA', 'LATAM', 'APAC', 'NAM'))
|
||||
) COMMENT 'Master data table for country information and regional classification';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `country_property_type`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`external_mapping_id` VARCHAR(16),
|
||||
`data_type` VARCHAR(16) NOT NULL,
|
||||
`validation_rule` VARCHAR(64),
|
||||
`is_required` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT `chk_country_data_type_values` CHECK (`data_type` IN
|
||||
('INT', 'PERCENTAGE', 'BOOLEAN', 'CURRENCY', 'ENUMERATION',
|
||||
'TEXT')),
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_property_type_data_type` (`data_type`)
|
||||
) COMMENT 'Defines available property types for country-specific configurations';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `country_property`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`country_id` INT NOT NULL,
|
||||
`country_property_type_id` INT NOT NULL,
|
||||
`property_set_id` INT NOT NULL,
|
||||
`property_value` VARCHAR(500),
|
||||
FOREIGN KEY (`country_id`) REFERENCES `country` (`id`),
|
||||
FOREIGN KEY (`country_property_type_id`) REFERENCES `country_property_type` (`id`),
|
||||
FOREIGN KEY (`property_set_id`) REFERENCES `property_set` (`id`),
|
||||
UNIQUE KEY `idx_country_property` (`country_id`, `country_property_type_id`, `property_set_id`)
|
||||
) COMMENT 'Stores country-specific property values with versioning support';
|
||||
|
||||
-- Main table for user information
|
||||
CREATE TABLE IF NOT EXISTS `sys_user`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`workday_id` CHAR(32) NOT NULL,
|
||||
`email` VARCHAR(254) NOT NULL,
|
||||
`firstname` VARCHAR(100) NOT NULL,
|
||||
`lastname` VARCHAR(100) NOT NULL,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE KEY `idx_user_email` (`email`),
|
||||
UNIQUE KEY `idx_user_workday` (`workday_id`)
|
||||
) COMMENT 'Stores basic information about system users';
|
||||
|
||||
-- Group definitions
|
||||
CREATE TABLE IF NOT EXISTS `sys_group`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`group_name` VARCHAR(64) NOT NULL,
|
||||
`group_description` VARCHAR(128) NOT NULL,
|
||||
UNIQUE KEY `idx_group_name` (`group_name`)
|
||||
) COMMENT 'Defines user groups for access management';
|
||||
|
||||
-- Junction table for user-group assignments
|
||||
CREATE TABLE IF NOT EXISTS `sys_user_group_mapping`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT NOT NULL,
|
||||
`group_id` INT NOT NULL,
|
||||
UNIQUE INDEX `idx_user_group` (`user_id`, `group_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`),
|
||||
FOREIGN KEY (`group_id`) REFERENCES `sys_group` (`id`)
|
||||
) COMMENT 'Links users with their associated groups';
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sys_user_node`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT NOT NULL,
|
||||
`country_id` INT NOT NULL,
|
||||
`name` VARCHAR(254) NOT NULL,
|
||||
`address` VARCHAR(500) NOT NULL,
|
||||
`geo_lat` DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
`geo_lng` DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
`is_deprecated` BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`),
|
||||
FOREIGN KEY (`country_id`) REFERENCES `country` (`id`)
|
||||
) COMMENT 'Contains user generated logistic nodes';
|
||||
|
||||
|
||||
-- logistic nodes
|
||||
CREATE TABLE IF NOT EXISTS node
|
||||
(
|
||||
id INT PRIMARY KEY,
|
||||
country_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address VARCHAR(500) NOT NULL,
|
||||
external_mapping_id VARCHAR(32),
|
||||
predecessor_required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_destination BOOLEAN NOT NULL,
|
||||
is_source BOOLEAN NOT NULL,
|
||||
is_intermediate BOOLEAN NOT NULL,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
is_deprecated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
INDEX idx_country_id (country_id)
|
||||
) COMMENT '';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_predecessor_chain
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
node_id INT NOT NULL,
|
||||
FOREIGN KEY (node_id) REFERENCES 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 (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)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS outbound_country_mapping
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
node_id INT NOT NULL,
|
||||
country_id INT NOT NULL,
|
||||
FOREIGN KEY (node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
UNIQUE KEY uk_node_id_country_id (node_id, country_id),
|
||||
INDEX idx_node_id (node_id),
|
||||
INDEX idx_country_id (country_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS distance_matrix
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
from_node_id INT NOT NULL,
|
||||
to_node_id INT NOT NULL,
|
||||
from_geo_lat DECIMAL(7, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
||||
from_geo_lng DECIMAL(7, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
||||
to_geo_lat DECIMAL(7, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
||||
to_geo_lng DECIMAL(7, 4) CHECK (to_geo_lng BETWEEN -180 AND 180),
|
||||
distance DECIMAL(15, 2) NOT NULL COMMENT 'travel distance between the two nodes in meters',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
state CHAR(10) NOT NULL,
|
||||
FOREIGN KEY (from_node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
||||
CONSTRAINT `chk_distance_matrix_state` CHECK (`state` IN
|
||||
('VALID', 'STALE')),
|
||||
INDEX idx_from_to_nodes (from_node_id, to_node_id)
|
||||
);
|
||||
|
||||
-- container rates
|
||||
|
||||
CREATE TABLE IF NOT EXISTS validity_period
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
start_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_date TIMESTAMP DEFAULT NULL,
|
||||
state CHAR(8) NOT NULL CHECK (state IN ('DRAFT', 'VALID', 'INVALID', 'EXPIRED')),
|
||||
CONSTRAINT `chk_validity_date_range` CHECK (`end_date` IS NULL OR `end_date` > `start_date`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS container_rate
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
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',
|
||||
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),
|
||||
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (validity_period_id) REFERENCES validity_period (id),
|
||||
INDEX idx_from_to_nodes (from_node_id, to_node_id),
|
||||
INDEX idx_validity_period_id (validity_period_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS country_matrix_rate
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
from_country_id INT NOT NULL,
|
||||
to_country_id INT NOT NULL,
|
||||
rate DECIMAL(15, 2) NOT NULL COMMENT 'rate for full truck load per kilometer in EUR',
|
||||
validity_period_id INT NOT NULL,
|
||||
FOREIGN KEY (from_country_id) REFERENCES country (id),
|
||||
FOREIGN KEY (to_country_id) REFERENCES country (id),
|
||||
FOREIGN KEY (validity_period_id) REFERENCES validity_period (id),
|
||||
INDEX idx_from_to_country (from_country_id, to_country_id),
|
||||
INDEX idx_validity_period_id (validity_period_id)
|
||||
);
|
||||
|
||||
|
||||
-- packaging and material
|
||||
CREATE TABLE IF NOT EXISTS material
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
part_number CHAR(12) NOT NULL,
|
||||
normalized_part_number CHAR(12) NOT NULL,
|
||||
hs_code CHAR(8),
|
||||
name VARCHAR(500) NOT NULL,
|
||||
is_deprecated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT `uq_normalized_part_number` UNIQUE (`normalized_part_number`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS packaging_dimension
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`type` CHAR(3) DEFAULT 'HU',
|
||||
`length` INT UNSIGNED NOT NULL COMMENT 'length stored in mm',
|
||||
`width` INT UNSIGNED NOT NULL COMMENT 'width stored in mm',
|
||||
`height` INT UNSIGNED NOT NULL COMMENT 'height stored in mm',
|
||||
`displayed_dimension_unit` CHAR(2) DEFAULT 'CM',
|
||||
`weight` INT UNSIGNED NOT NULL COMMENT 'weight stored in g',
|
||||
`displayed_weight_unit` CHAR(2) DEFAULT 'KG',
|
||||
`content_unit_count` INT UNSIGNED NOT NULL COMMENT 'how many units are contained in packaging (if there is a child packaging this references to the child packaging, otherwise this references a single unit)',
|
||||
`is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT `chk_packaging_dimension_type_values` CHECK (`type` IN
|
||||
('SHU', 'HU')),
|
||||
CONSTRAINT `chk_packaging_dimension_displayed_dimension_unit` CHECK (`displayed_dimension_unit` IN
|
||||
('MM', 'CM', 'M')),
|
||||
CONSTRAINT `chk_packaging_dimension_displayed_weight_unit` CHECK (`displayed_weight_unit` IN
|
||||
('G', 'KG'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS packaging
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`supplier_node_id` INT NOT NULL,
|
||||
`material_id` INT NOT NULL,
|
||||
`hu_dimension_id` INT NOT NULL,
|
||||
`shu_dimension_id` INT NOT NULL,
|
||||
`is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FOREIGN KEY (supplier_node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (material_id) REFERENCES material (id),
|
||||
FOREIGN KEY (hu_dimension_id) REFERENCES packaging_dimension (id),
|
||||
FOREIGN KEY (shu_dimension_id) REFERENCES packaging_dimension (id),
|
||||
INDEX idx_material_id (material_id),
|
||||
INDEX idx_hu_dimension_id (hu_dimension_id),
|
||||
INDEX idx_shu_dimension_id (shu_dimension_id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
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,
|
||||
UNIQUE KEY idx_packaging_property_type (`external_mapping_id`),
|
||||
CONSTRAINT `chk_packaging_data_type_values` CHECK (`data_type` IN
|
||||
('INT', 'PERCENTAGE', 'BOOLEAN', 'CURRENCY', 'ENUMERATION',
|
||||
'TEXT'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS packaging_property
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`packaging_property_type_id` INT NOT NULL,
|
||||
`packaging_id` INT NOT NULL,
|
||||
`property_value` VARCHAR(500),
|
||||
FOREIGN KEY (packaging_property_type_id) REFERENCES packaging_property_type (id),
|
||||
FOREIGN KEY (packaging_id) REFERENCES packaging (id),
|
||||
INDEX idx_packaging_property_type_id (packaging_property_type_id),
|
||||
INDEX idx_packaging_id (packaging_id),
|
||||
UNIQUE KEY idx_packaging_property_unique (packaging_property_type_id, packaging_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
material_id INT NOT NULL,
|
||||
supplier_node_id INT,
|
||||
user_supplier_node_id INT,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
packaging_id INT DEFAULT NULL,
|
||||
user_id INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
material_cost DECIMAL(15, 2) COMMENT 'aka MEK_A in EUR',
|
||||
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
||||
oversea_share DECIMAL(7, 4),
|
||||
hs_code CHAR(8),
|
||||
tariff_rate DECIMAL(7, 4),
|
||||
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
||||
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
individual_hu_width INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||
individual_hu_weight INT UNSIGNED COMMENT 'user entered weight in g (if system-wide packaging is used, packaging weight are copied here after creation)',
|
||||
hu_displayed_dimension_unit CHAR(2) DEFAULT 'MM',
|
||||
hu_displayed_weight_unit CHAR(2) DEFAULT 'G',
|
||||
hu_unit_count INT UNSIGNED DEFAULT NULL,
|
||||
hu_stackable BOOLEAN DEFAULT TRUE,
|
||||
hu_mixable BOOLEAN DEFAULT TRUE,
|
||||
FOREIGN KEY (material_id) REFERENCES material (id),
|
||||
FOREIGN KEY (supplier_node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (user_supplier_node_id) REFERENCES sys_user_node (id),
|
||||
FOREIGN KEY (packaging_id) REFERENCES packaging (id),
|
||||
FOREIGN KEY (user_id) REFERENCES sys_user (id),
|
||||
CONSTRAINT `chk_premise_state_values` CHECK (`state` IN
|
||||
('DRAFT', 'COMPLETED', 'ARCHIVED', 'DELETED')),
|
||||
CONSTRAINT `chk_premise_displayed_dimension_unit` CHECK (`hu_displayed_dimension_unit` IN
|
||||
('MM', 'CM', 'M')),
|
||||
CONSTRAINT `chk_premise_displayed_weight_unit` CHECK (`hu_displayed_weight_unit` IN
|
||||
('G', 'KG')),
|
||||
INDEX idx_material_id (material_id),
|
||||
INDEX idx_supplier_node_id (supplier_node_id),
|
||||
INDEX idx_packaging_id (packaging_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_user_supplier_node_id (user_supplier_node_id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_destination
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_id INT NOT NULL,
|
||||
annual_amount INT UNSIGNED NOT NULL COMMENT 'annual amount in single pieces',
|
||||
destination_node_id INT NOT NULL,
|
||||
is_d2d BOOLEAN DEFAULT FALSE,
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
|
||||
lead_time_d2d INT UNSIGNED NOT NULL,
|
||||
repacking_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
handling_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
disposal_cost DECIMAL(15, 2) DEFAULT NULL,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
country_id INT NOT NULL,
|
||||
FOREIGN KEY (premise_id) REFERENCES premise (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
FOREIGN KEY (destination_node_id) REFERENCES node (id),
|
||||
INDEX idx_destination_node_id (destination_node_id),
|
||||
INDEX idx_premise_id (premise_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_destination_id INT NOT NULL,
|
||||
is_fastest BOOLEAN DEFAULT FALSE,
|
||||
is_cheapest BOOLEAN DEFAULT FALSE,
|
||||
is_selected BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route_node
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
node_id INT DEFAULT NULL,
|
||||
user_node_id INT DEFAULT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address VARCHAR(500),
|
||||
country_id INT NOT NULL,
|
||||
is_destination BOOLEAN DEFAULT FALSE,
|
||||
is_intermediate BOOLEAN DEFAULT FALSE,
|
||||
is_source BOOLEAN DEFAULT FALSE,
|
||||
geo_lat DECIMAL(7, 4) CHECK (geo_lat BETWEEN -90 AND 90),
|
||||
geo_lng DECIMAL(7, 4) CHECK (geo_lng BETWEEN -180 AND 180),
|
||||
is_outdated BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (node_id) REFERENCES node (id),
|
||||
FOREIGN KEY (country_id) REFERENCES country (id),
|
||||
FOREIGN KEY (user_node_id) REFERENCES sys_user_node (id),
|
||||
INDEX idx_node_id (node_id),
|
||||
INDEX idx_user_node_id (user_node_id),
|
||||
CONSTRAINT `chk_node` CHECK (`user_node_id` IS NULL OR `node_id` IS NULL)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS premise_route_section
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_route_id INT NOT NULL,
|
||||
from_route_node_id INT NOT NULL,
|
||||
to_route_node_id INT NOT NULL,
|
||||
list_position INT NOT NULL,
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN')),
|
||||
rate_type CHAR(16) CHECK (rate_type IN
|
||||
('CONTAINER', 'MATRIX')),
|
||||
is_pre_run BOOLEAN DEFAULT FALSE,
|
||||
is_main_run BOOLEAN DEFAULT FALSE,
|
||||
is_post_run BOOLEAN DEFAULT FALSE,
|
||||
is_outdated BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (premise_route_id) REFERENCES premise_route (id),
|
||||
FOREIGN KEY (from_route_node_id) REFERENCES premise_route_node (id),
|
||||
FOREIGN KEY (to_route_node_id) REFERENCES premise_route_node (id),
|
||||
CONSTRAINT chk_main_run CHECK ((transport_type = 'RAIL' OR transport_type = 'SEA') AND is_main_run IS TRUE),
|
||||
INDEX idx_premise_route_id (premise_route_id),
|
||||
INDEX idx_from_route_node_id (from_route_node_id),
|
||||
INDEX idx_to_route_node_id (to_route_node_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculation_job
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_id INT NOT NULL,
|
||||
calculation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
validity_period_id INT NOT NULL,
|
||||
property_set_id INT NOT NULL,
|
||||
job_state CHAR(10) NOT NULL CHECK (job_state IN
|
||||
('CREATED', 'SCHEDULED', 'VALID', 'INVALID', 'EXCEPTION')),
|
||||
user_id INT NOT NULL,
|
||||
FOREIGN KEY (premise_id) REFERENCES premise (id),
|
||||
FOREIGN KEY (validity_period_id) REFERENCES validity_period (id),
|
||||
FOREIGN KEY (property_set_id) REFERENCES property_set (id),
|
||||
FOREIGN KEY (user_id) REFERENCES sys_user (id),
|
||||
INDEX idx_premise_id (premise_id),
|
||||
INDEX idx_validity_period_id (validity_period_id),
|
||||
INDEX idx_property_set_id (property_set_id)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculation_job_destination
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
calculation_job_id INT NOT NULL,
|
||||
premise_destination_id INT NOT NULL,
|
||||
shipping_frequency INT UNSIGNED COMMENT 'annual shipping frequency',
|
||||
total_cost DECIMAL(15, 2) COMMENT 'aka MEK_B in EUR (excl. Airfreight)',
|
||||
annual_amount DECIMAL(15, 2) COMMENT 'annual quantity for this destinations in pieces',
|
||||
-- risk
|
||||
annual_risk_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored worst case container rates (excl. Airfreight)',
|
||||
annual_chance_cost DECIMAL(15, 2) NOT NULL COMMENT 'complete calculation with globally stored best case container rates (excl. Airfreight)',
|
||||
-- handling
|
||||
is_small_unit BOOLEAN DEFAULT FALSE COMMENT 'small unit equals KLT, volume of a handling unit is smaller than 0.08 cbm ',
|
||||
annual_repacking_cost DECIMAL(15, 2) NOT NULL,
|
||||
annual_handling_cost DECIMAL(15, 2) NOT NULL,
|
||||
annual_disposal_cost DECIMAL(15, 2) NOT NULL,
|
||||
-- inventory
|
||||
operational_stock DECIMAL(15, 2) NOT NULL COMMENT 'operational stock in single pieces',
|
||||
safety_stock DECIMAL(15, 2) NOT NULL COMMENT 'safety stock in single pieces',
|
||||
stocked_inventory DECIMAL(15, 2) NOT NULL COMMENT 'sum of operational and safety stock',
|
||||
in_transport_stock DECIMAL(15, 2) NOT NULL,
|
||||
stock_before_payment DECIMAL(15, 2) NOT NULL,
|
||||
annual_capital_cost DECIMAL(15, 2) NOT NULL,
|
||||
annual_storage_cost DECIMAL(15, 2) NOT NULL, -- Flächenkosten
|
||||
-- custom
|
||||
custom_value DECIMAL(15, 2) NOT NULL,-- Zollwert,
|
||||
custom_duties DECIMAL(15, 2) NOT NULL,-- Zollabgaben,
|
||||
tariff_rate DECIMAL(7, 4) NOT NULL,-- Zollsatz,
|
||||
annual_custom_cost DECIMAL(15, 2) NOT NULL,-- Zollabgaben inkl. Einmalkosten,
|
||||
-- air freight risk
|
||||
air_freight_share_max DECIMAL(7, 4) NOT NULL,
|
||||
air_freight_share DECIMAL(7, 4) NOT NULL,
|
||||
air_freight_volumetric_weight DECIMAL(15, 2) NOT NULL,
|
||||
air_freight_weight DECIMAL(15, 2) NOT NULL,
|
||||
annual_air_freight_cost DECIMAL(15, 2) NOT NULL,
|
||||
-- transportation
|
||||
is_d2d BOOLEAN DEFAULT FALSE,
|
||||
rate_d2d DECIMAL(15, 2) DEFAULT NULL,
|
||||
container_type CHAR(8) CHECK (container_type IN
|
||||
('TEU', 'FEU', 'HC', 'TRUCK')),
|
||||
hu_count INT UNSIGNED NOT NULL COMMENT 'number of handling units int total',
|
||||
layer_structure JSON COMMENT 'json representation of a single layer',
|
||||
layer_count INT UNSIGNED NOT NULL COMMENT 'number of layers per full container or truck',
|
||||
transport_weight_exceeded BOOLEAN DEFAULT FALSE COMMENT 'limiting factor: TRUE if weight limited or FALSE if volume limited',
|
||||
annual_transportation_cost DECIMAL(15, 2) NOT NULL COMMENT 'total annual transportation costs in EUR',
|
||||
container_utilization DECIMAL(7, 4) NOT NULL,
|
||||
transit_time_in_days INT UNSIGNED NOT NULL,
|
||||
safety_stock_in_days INT UNSIGNED NOT NULL,
|
||||
-- material cost
|
||||
material_cost DECIMAL(15, 2) NOT NULL,
|
||||
fca_cost DECIMAL(15, 2) NOT NULL,
|
||||
|
||||
FOREIGN KEY (calculation_job_id) REFERENCES calculation_job (id),
|
||||
FOREIGN KEY (premise_destination_id) REFERENCES premise_destination (id),
|
||||
INDEX idx_calculation_job_id (calculation_job_id),
|
||||
INDEX idx_premise_destination_id (premise_destination_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculation_job_route_section
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
premise_route_section_id INT NOT NULL,
|
||||
calculation_job_destination_id INT NOT NULL,
|
||||
transport_type CHAR(16) CHECK (transport_type IN
|
||||
('RAIL', 'SEA', 'ROAD', 'POST-RUN', 'MATRIX', 'D2D')),
|
||||
is_unmixed_price BOOLEAN DEFAULT FALSE,
|
||||
is_cbm_price BOOLEAN DEFAULT FALSE,
|
||||
is_weight_price BOOLEAN DEFAULT FALSE,
|
||||
is_stacked BOOLEAN DEFAULT FALSE,
|
||||
is_pre_run BOOLEAN DEFAULT FALSE,
|
||||
is_main_run BOOLEAN DEFAULT FALSE,
|
||||
is_post_run BOOLEAN DEFAULT FALSE,
|
||||
rate DECIMAL(15, 2) NOT NULL COMMENT 'copy of the container rate resp. price matrix in EUR (depends on used_rule)',
|
||||
distance DECIMAL(15, 2) DEFAULT NULL COMMENT 'distance of this routeInformationObject section im meters',
|
||||
cbm_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per cubic meter',
|
||||
weight_price DECIMAL(15, 2) NOT NULL COMMENT 'calculated price per kilogram',
|
||||
annual_cost DECIMAL(15, 2) NOT NULL COMMENT 'annual costs for this routeInformationObject section, result depends on calculation method (mixed or unmixed, stacked or unstacked, per volume/per weight resp. container rate/price matrix)',
|
||||
transit_time INT UNSIGNED NOT NULL,
|
||||
FOREIGN KEY (premise_route_section_id) REFERENCES premise_route_section (id),
|
||||
FOREIGN KEY (calculation_job_destination_id) REFERENCES calculation_job_destination (id),
|
||||
INDEX idx_premise_route_section_id (premise_route_section_id),
|
||||
INDEX idx_calculation_job_destination_id (calculation_job_destination_id),
|
||||
CONSTRAINT chk_stacked CHECK (is_unmixed_price IS TRUE OR is_stacked IS TRUE), -- only unmixed transports can be unstacked
|
||||
CONSTRAINT chk_cbm_weight_price CHECK (is_unmixed_price IS FALSE OR
|
||||
(is_cbm_price IS FALSE AND is_weight_price IS FALSE))
|
||||
);
|
||||
|
||||
Loading…
Add table
Reference in a new issue