From a1c8ef6b1b8f7fab69b3383088ca4167abf23b25 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 23 Mar 2025 20:36:28 +0100 Subject: [PATCH] Add models, DTOs, repository, and controller for materials Integrated Material domain entities with DTOs, repository, and controller, enabling CRUD functionality, pagination, and validation. Added utility classes like `UnitConverter`, and exception handling via global handlers. New classes include models for packaging, countries, and error handling structures. --- .../controller/GlobalExceptionHandler.java | 70 +++++++++ .../lcc/controller/MaterialController.java | 92 +++++++++++ .../avatic/lcc/dto/countries/CountryDTO.java | 53 +++++++ .../lcc/dto/countries/CountryOverviewDTO.java | 50 ++++++ .../de/avatic/lcc/dto/error/ErrorDTO.java | 68 ++++++++ .../lcc/dto/error/ErrorResponseDTO.java | 42 +++++ .../avatic/lcc/dto/material/MaterialDTO.java | 105 +++++++++++++ .../avatic/lcc/dto/material/PackagingDTO.java | 148 ++++++++++++++++++ .../java/de/avatic/lcc/dto/nodes/NodeDTO.java | 7 + .../lcc/model/country/CountryListEntry.java | 46 ++++++ .../avatic/lcc/model/materials/Material.java | 26 +-- .../model/materials/MaterialListEntry.java | 82 ++++++++++ .../de/avatic/lcc/model/nodes/Location.java | 8 + .../avatic/lcc/model/nodes/NodeListEntry.java | 64 ++++++++ .../model/packaging/PackagingListEntry.java | 119 ++++++++++++++ .../lcc/repositories/MaterialRepository.java | 137 ++++++++++++++++ .../utils/SearchQueryPagination.java | 21 +++ .../repositories/utils/SearchQueryResult.java | 49 ++++++ .../lcc/repositories/utils/UnitConverter.java | 16 ++ .../lcc/util/InvalidArgumentException.java | 14 ++ 20 files changed, 1207 insertions(+), 10 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java create mode 100644 src/main/java/de/avatic/lcc/controller/MaterialController.java create mode 100644 src/main/java/de/avatic/lcc/dto/countries/CountryDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/countries/CountryOverviewDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/error/ErrorResponseDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/material/MaterialDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/material/PackagingDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/nodes/NodeDTO.java create mode 100644 src/main/java/de/avatic/lcc/model/country/CountryListEntry.java create mode 100644 src/main/java/de/avatic/lcc/model/materials/MaterialListEntry.java create mode 100644 src/main/java/de/avatic/lcc/model/nodes/Location.java create mode 100644 src/main/java/de/avatic/lcc/model/nodes/NodeListEntry.java create mode 100644 src/main/java/de/avatic/lcc/model/packaging/PackagingListEntry.java create mode 100644 src/main/java/de/avatic/lcc/repositories/MaterialRepository.java create mode 100644 src/main/java/de/avatic/lcc/repositories/utils/SearchQueryPagination.java create mode 100644 src/main/java/de/avatic/lcc/repositories/utils/SearchQueryResult.java create mode 100644 src/main/java/de/avatic/lcc/repositories/utils/UnitConverter.java create mode 100644 src/main/java/de/avatic/lcc/util/InvalidArgumentException.java diff --git a/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java b/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..1dd8b50 --- /dev/null +++ b/src/main/java/de/avatic/lcc/controller/GlobalExceptionHandler.java @@ -0,0 +1,70 @@ +package de.avatic.lcc.controller; + +import de.avatic.lcc.dto.error.ErrorDTO; +import de.avatic.lcc.dto.error.ErrorResponseDTO; +import de.avatic.lcc.util.InvalidArgumentException; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.HashMap; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException exception) { + + ErrorDTO error = new ErrorDTO( + "BAD_REQUEST", + "Invalid Arguments", + new HashMap<>() {{ + put("errorMessage", exception.getMessage()); + }} + ); + + return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(InvalidArgumentException.class) + public ResponseEntity handleMethodArgumentNotValid(InvalidArgumentException exception) { + + ErrorDTO error = new ErrorDTO( + "BAD_REQUEST", + exception.getMessage(), + new HashMap<>() {{ + put("errorMessage", exception.getStackTrace()); + }} + ); + + return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST); + } + + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ConstraintViolationException.class) + public String handleConstraintViolation(ConstraintViolationException exception) { // + // TODO you can choose to return your custom object here, which will then get transformed to json/xml etc. + return "Sorry, that was not quite right: " + exception.getMessage(); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex) { + ErrorDTO error = new ErrorDTO( + "INTERNAL_SERVER_ERROR", + "An unexpected error occurred", + new HashMap<>() {{ + put("errorMessage", ex.getMessage()); + }} + ); + + return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/controller/MaterialController.java b/src/main/java/de/avatic/lcc/controller/MaterialController.java new file mode 100644 index 0000000..0128d21 --- /dev/null +++ b/src/main/java/de/avatic/lcc/controller/MaterialController.java @@ -0,0 +1,92 @@ +package de.avatic.lcc.controller; + +import de.avatic.lcc.model.materials.Material; +import de.avatic.lcc.model.materials.MaterialListEntry; +import de.avatic.lcc.repositories.MaterialRepository; +import de.avatic.lcc.repositories.utils.SearchQueryPagination; +import de.avatic.lcc.repositories.utils.SearchQueryResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/materials") +public class MaterialController { + + + private final MaterialRepository materialRepository; + + @Autowired + public MaterialController(MaterialRepository materialRepository) { + this.materialRepository = materialRepository; + } + + /** + * List all materials with pagination support. + * + * @param limit Maximum number of items to return (default: 20) + * @param page Page number for pagination (default: 0) + * @param filter Optional search filter (default: empty string) + * @return A list of material summaries. Pagination details are returned in response headers: + * X-Total-Count (total elements), X-Page-Count (total pages), and X-Current-Page (current page). + */ + @GetMapping("/") + public ResponseEntity> listMaterials( + @RequestParam(defaultValue = "20") int limit, + @RequestParam(defaultValue = "0") int page, + @RequestParam(required = false, defaultValue = "") String filter) { + + SearchQueryResult materials = materialRepository.listMaterials(filter, new SearchQueryPagination(page, limit)); + + return ResponseEntity.ok() + .header("X-Total-Count", String.valueOf(materials.getTotalElements())) + .header("X-Page-Count", String.valueOf(materials.getTotalPages())) + .header("X-Current-Page", String.valueOf(page)) + .body(materials.toList()); + } + + /** + * Get detailed information about a specific material. + * + * @param id Material ID + * @return Detailed information about the material + * @throws RuntimeException if the material with the given ID is not found. + */ + @GetMapping("/{id}") + public ResponseEntity getMaterialDetails(@PathVariable Integer id) { + return ResponseEntity.ok(materialRepository.getMaterialDetails(id).orElseThrow(() -> new RuntimeException("Material not found with id " + id))); + } + + /** + * Update a material with the specified ID. + * + * @param id Material ID to update + * @param material The updated material details + * @return The updated material + */ + + @PutMapping("/{id}") + public ResponseEntity updateMaterial( + @PathVariable Integer id, + @RequestBody Material material) { + + material.setId(id); + Material updatedMaterial = materialRepository.update(material); + return ResponseEntity.ok(updatedMaterial); + } + + /** + * Soft delete a material by marking it as deprecated. + * + * @param id Material ID + * @return Empty response with status 204 No Content + * @throws RuntimeException if the material with the given ID cannot be found or deleted. + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteMaterial(@PathVariable Integer id) { + materialRepository.deleteById(id); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/dto/countries/CountryDTO.java b/src/main/java/de/avatic/lcc/dto/countries/CountryDTO.java new file mode 100644 index 0000000..3a050f9 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/countries/CountryDTO.java @@ -0,0 +1,53 @@ +package de.avatic.lcc.dto.countries; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CountryDTO { + + //TODO add country properties + + @JsonProperty("id") + private String id; + + @JsonProperty("iso_code") + private String isoCode; + + @JsonProperty("region_code") + private String regionCode; + + @JsonProperty("name") + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getIsoCode() { + return isoCode; + } + + public void setIsoCode(String isoCode) { + this.isoCode = isoCode; + } + + public String getRegionCode() { + return regionCode; + } + + public void setRegionCode(String regionCode) { + this.regionCode = regionCode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/main/java/de/avatic/lcc/dto/countries/CountryOverviewDTO.java b/src/main/java/de/avatic/lcc/dto/countries/CountryOverviewDTO.java new file mode 100644 index 0000000..64d7bab --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/countries/CountryOverviewDTO.java @@ -0,0 +1,50 @@ +package de.avatic.lcc.dto.countries; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CountryOverviewDTO { + + @JsonProperty("id") + private String id; + + @JsonProperty("iso_code") + private String isoCode; + + @JsonProperty("region_code") + private String regionCode; + + @JsonProperty("name") + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getIsoCode() { + return isoCode; + } + + public void setIsoCode(String isoCode) { + this.isoCode = isoCode; + } + + public String getRegionCode() { + return regionCode; + } + + public void setRegionCode(String regionCode) { + this.regionCode = regionCode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java b/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java new file mode 100644 index 0000000..46b4789 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java @@ -0,0 +1,68 @@ +package de.avatic.lcc.dto.error; + + +import java.util.Map; +import java.util.Objects; + +public class ErrorDTO { + private String code; + private String message; + private Map details; + + public ErrorDTO() { + } + + public ErrorDTO(String code, String message, Map details) { + this.code = code; + this.message = message; + this.details = details; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getDetails() { + return details; + } + + public void setDetails(Map details) { + this.details = details; + } + + @Override + public String toString() { + return "ErrorDTO{" + + "code='" + code + '\'' + + ", message='" + message + '\'' + + ", details=" + details + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ErrorDTO errorDTO = (ErrorDTO) o; + return Objects.equals(code, errorDTO.code) && + Objects.equals(message, errorDTO.message) && + Objects.equals(details, errorDTO.details); + } + + @Override + public int hashCode() { + return Objects.hash(code, message, details); + } +} diff --git a/src/main/java/de/avatic/lcc/dto/error/ErrorResponseDTO.java b/src/main/java/de/avatic/lcc/dto/error/ErrorResponseDTO.java new file mode 100644 index 0000000..188676c --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/error/ErrorResponseDTO.java @@ -0,0 +1,42 @@ +package de.avatic.lcc.dto.error; + +import java.util.Objects; + +public class ErrorResponseDTO { + private ErrorDTO error; + + public ErrorResponseDTO() { + } + + public ErrorResponseDTO(ErrorDTO error) { + this.error = error; + } + + public ErrorDTO getError() { + return error; + } + + public void setError(ErrorDTO error) { + this.error = error; + } + + @Override + public String toString() { + return "ErrorResponseDTO{" + + "error=" + error + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ErrorResponseDTO that = (ErrorResponseDTO) o; + return Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + return Objects.hash(error); + } +} diff --git a/src/main/java/de/avatic/lcc/dto/material/MaterialDTO.java b/src/main/java/de/avatic/lcc/dto/material/MaterialDTO.java new file mode 100644 index 0000000..d2343b9 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/material/MaterialDTO.java @@ -0,0 +1,105 @@ +package de.avatic.lcc.dto.material; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Objects; + +public class MaterialDTO { + private String id; + + @JsonProperty("part_number") + private String partNumber; + + private String name; + + @JsonProperty("hs_code") + private String hsCode; + + @JsonProperty("handling_units") + private List handlingUnits; + + public MaterialDTO() { + } + + public MaterialDTO(String id, String partNumber, String name, String hs_code, List handling_units) { + this.id = id; + this.partNumber = partNumber; + this.name = name; + this.hsCode = hs_code; + this.handlingUnits = handling_units; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @JsonProperty("part_number") + public String getPartNumber() { + return partNumber; + } + + @JsonProperty("part_number") + public void setPartNumber(String part_number) { + this.partNumber = part_number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @JsonProperty("hs_code") + public String getHsCode() { + return hsCode; + } + + @JsonProperty("hs_code") + public void setHsCode(String hsCode) { + this.hsCode = hsCode; + } + + @JsonProperty("handling_units") + public List getHandlingUnits() { + return handlingUnits; + } + + public void setHandlingUnits(List handlingUnits) { + this.handlingUnits = handlingUnits; + } + + @Override + public String toString() { + return "MaterialDetailDTO{" + + "id='" + id + '\'' + + ", part_number='" + partNumber + '\'' + + ", name='" + name + '\'' + + ", hs_code='" + hsCode + '\'' + + ", handling_units=" + handlingUnits + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MaterialDTO that = (MaterialDTO) o; + return Objects.equals(id, that.id) && + Objects.equals(partNumber, that.partNumber) && + Objects.equals(name, that.name) && + Objects.equals(hsCode, that.hsCode) && + Objects.equals(handlingUnits, that.handlingUnits); + } + + @Override + public int hashCode() { + return Objects.hash(id, partNumber, name, hsCode, handlingUnits); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/dto/material/PackagingDTO.java b/src/main/java/de/avatic/lcc/dto/material/PackagingDTO.java new file mode 100644 index 0000000..12d9426 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/material/PackagingDTO.java @@ -0,0 +1,148 @@ +package de.avatic.lcc.dto.material; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + +public class PackagingDTO { + private String id; + @JsonProperty("supplier_name") + private SupplierDTO supplierName; + @JsonProperty("parent_id") + private String parentId; + private double length; + private double width; + private double height; + @JsonProperty("dimension_unit") + private String dimensionUnit; + private double weight; + @JsonProperty("weight_unit") + private String weightUnit; + @JsonProperty("content_unit_count") + private int contentUnitCount; + + public PackagingDTO() { + } + + public PackagingDTO(String id, String supplierName, String parentId, double length, double width, double height, + String dimensionUnit, double weight, String weightUnit, int contentUnitCount) { + this.id = id; + this.supplierName = supplierName; + this.parentId = parentId; + this.length = length; + this.width = width; + this.height = height; + this.dimensionUnit = dimensionUnit; + this.weight = weight; + this.weightUnit = weightUnit; + this.contentUnitCount = contentUnitCount; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @JsonProperty("supplier_name") + public String getSupplierName() { + return supplierName; + } + + @JsonProperty("supplier_name") + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + @JsonProperty("parent_id") + public String getParentId() { + return parentId; + } + + @JsonProperty("parent_id") + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public double getLength() { + return length; + } + + public void setLength(double length) { + this.length = length; + } + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + @JsonProperty("dimension_unit") + public String getDimensionUnit() { + return dimensionUnit; + } + + @JsonProperty("dimension_unit") + public void setDimensionUnit(String dimensionUnit) { + this.dimensionUnit = dimensionUnit; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + @JsonProperty("weight_unit") + public String getWeightUnit() { + return weightUnit; + } + + @JsonProperty("weight_unit") + public void setWeightUnit(String weightUnit) { + this.weightUnit = weightUnit; + } + + @JsonProperty("content_unit_count") + public int getContentUnitCount() { + return contentUnitCount; + } + + @JsonProperty("content_unit_count") + public void setContentUnitCount(int contentUnitCount) { + this.contentUnitCount = contentUnitCount; + } + + @Override + public String toString() { + // Implementation omitted for shortness + return "HandlingUnitDTO{" + /* fields */ "}"; + } + + @Override + public boolean equals(Object o) { + // Implementation omitted for shortness + return true; + } + + @Override + public int hashCode() { + // Implementation omitted for shortness + return Objects.hash(id, supplierName, parentId, length, width, height, dimensionUnit, + weight, weightUnit, contentUnitCount); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/dto/nodes/NodeDTO.java b/src/main/java/de/avatic/lcc/dto/nodes/NodeDTO.java new file mode 100644 index 0000000..5698546 --- /dev/null +++ b/src/main/java/de/avatic/lcc/dto/nodes/NodeDTO.java @@ -0,0 +1,7 @@ +package de.avatic.lcc.dto.nodes; + +public class NodeDTO { + + // TODO + +} diff --git a/src/main/java/de/avatic/lcc/model/country/CountryListEntry.java b/src/main/java/de/avatic/lcc/model/country/CountryListEntry.java new file mode 100644 index 0000000..d5cfe98 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/country/CountryListEntry.java @@ -0,0 +1,46 @@ +package de.avatic.lcc.model.country; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CountryListEntry { + private Integer id; + + @JsonProperty("iso_code") + private String isoCode; + + @JsonProperty("region_code") + private String regionCode; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getIsoCode() { + return isoCode; + } + + public void setIsoCode(String isoCode) { + this.isoCode = isoCode; + } + + public String getRegionCode() { + return regionCode; + } + + public void setRegionCode(String regionCode) { + this.regionCode = regionCode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/de/avatic/lcc/model/materials/Material.java b/src/main/java/de/avatic/lcc/model/materials/Material.java index fe270f1..20937ad 100644 --- a/src/main/java/de/avatic/lcc/model/materials/Material.java +++ b/src/main/java/de/avatic/lcc/model/materials/Material.java @@ -1,35 +1,33 @@ package de.avatic.lcc.model.materials; +import com.fasterxml.jackson.annotation.JsonProperty; +import de.avatic.lcc.model.packaging.PackagingListEntry; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table; -@Table("material") public class Material { - @Id private Integer id; - @NotNull - @Size(max = 12) + @JsonProperty("part_number") private String partNumber; - @NotNull - @Size(max = 12) + @JsonProperty("normalized_part_number") private String normalizedPartNumber; - @Size(max = 8) private String hsCode; - @NotNull - @Size(max = 500) private String name; - @NotNull + @JsonProperty("is_deprecated") private Boolean isDeprecated; + private PackagingListEntry packaging; + + public Integer getId() { return id; } @@ -77,4 +75,12 @@ public class Material { public void setDeprecated(Boolean deprecated) { isDeprecated = deprecated; } + + public PackagingListEntry getPackaging() { + return packaging; + } + + public void setPackaging(PackagingListEntry packaging) { + this.packaging = packaging; + } } diff --git a/src/main/java/de/avatic/lcc/model/materials/MaterialListEntry.java b/src/main/java/de/avatic/lcc/model/materials/MaterialListEntry.java new file mode 100644 index 0000000..2df32e0 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/materials/MaterialListEntry.java @@ -0,0 +1,82 @@ +package de.avatic.lcc.model.materials; + + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +public class MaterialListEntry { + + private Integer id; + @JsonProperty("part_number") + private String partNumber; + private String name; + private String hsCode; + + public MaterialListEntry() { + } + + public MaterialListEntry(Integer id, String partNumber, String name, String hsCode) { + this.id = id; + this.partNumber = partNumber; + this.name = name; + this.hsCode = hsCode; + } + + public String getHsCode() { + return hsCode; + } + + public void setHsCode(String hsCode) { + this.hsCode = hsCode; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getPartNumber() { + return partNumber; + } + + public void setPartNumber(String partNumber) { + this.partNumber = partNumber; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "MaterialSummaryDTO{" + + "id='" + id + '\'' + + ", partNumber='" + partNumber + '\'' + + ", name='" + name + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MaterialListEntry that = (MaterialListEntry) o; + return Objects.equals(id, that.id) && + Objects.equals(partNumber, that.partNumber) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, partNumber, name); + } + +} diff --git a/src/main/java/de/avatic/lcc/model/nodes/Location.java b/src/main/java/de/avatic/lcc/model/nodes/Location.java new file mode 100644 index 0000000..3d0b7df --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/nodes/Location.java @@ -0,0 +1,8 @@ +package de.avatic.lcc.model.nodes; + +public class Location { + private Double longitude; + private Double latitude; + + // Getters and setters +} diff --git a/src/main/java/de/avatic/lcc/model/nodes/NodeListEntry.java b/src/main/java/de/avatic/lcc/model/nodes/NodeListEntry.java new file mode 100644 index 0000000..8e6c899 --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/nodes/NodeListEntry.java @@ -0,0 +1,64 @@ +package de.avatic.lcc.model.nodes; + +import de.avatic.lcc.model.country.CountryListEntry; + +import java.util.List; + +public class NodeListEntry { + + private Integer id; + private String name; + private CountryListEntry country; + private String address; + private Location location; + private List types; + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public CountryListEntry getCountry() { + return country; + } + + public void setCountry(CountryListEntry country) { + this.country = country; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public List getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } +} diff --git a/src/main/java/de/avatic/lcc/model/packaging/PackagingListEntry.java b/src/main/java/de/avatic/lcc/model/packaging/PackagingListEntry.java new file mode 100644 index 0000000..e9ecc1e --- /dev/null +++ b/src/main/java/de/avatic/lcc/model/packaging/PackagingListEntry.java @@ -0,0 +1,119 @@ +package de.avatic.lcc.model.packaging; + +import com.fasterxml.jackson.annotation.JsonProperty; +import de.avatic.lcc.model.nodes.NodeListEntry; + +public class PackagingListEntry { + + private Integer id; + + private NodeListEntry supplier; + + @JsonProperty("parent_id") + private Integer parentId; + + private Double width; + private Double height; + private Double length; + + @JsonProperty("dimension_unit") + private String dimensionUnit; + + private Double weight; + + @JsonProperty("weight_unit") + private String weightUnit; + + @JsonProperty("content_unit_count") + private Integer contentUnitCount; + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public NodeListEntry getSupplier() { + return supplier; + } + + public void setSupplier(NodeListEntry supplier) { + this.supplier = supplier; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public Double getWidth() { + return width; + } + + public void setWidth(Double width) { + this.width = width; + } + + public Double getHeight() { + return height; + } + + public void setHeight(Double height) { + this.height = height; + } + + public Double getLength() { + return length; + } + + public void setLength(Double length) { + this.length = length; + } + + public String getDimensionUnit() { + return dimensionUnit; + } + + public void setDimensionUnit(String dimensionUnit) { + this.dimensionUnit = dimensionUnit; + } + + public Double getWeight() { + return weight; + } + + public void setWeight(Double weight) { + this.weight = weight; + } + + public String getWeightUnit() { + return weightUnit; + } + + public void setWeightUnit(String weightUnit) { + this.weightUnit = weightUnit; + } + + public Integer getContentUnitCount() { + return contentUnitCount; + } + + public void setContentUnitCount(Integer contentUnitCount) { + this.contentUnitCount = contentUnitCount; + } +} diff --git a/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java new file mode 100644 index 0000000..63f2991 --- /dev/null +++ b/src/main/java/de/avatic/lcc/repositories/MaterialRepository.java @@ -0,0 +1,137 @@ +package de.avatic.lcc.repositories; + +import de.avatic.lcc.model.country.CountryListEntry; +import de.avatic.lcc.model.materials.Material; +import de.avatic.lcc.model.materials.MaterialListEntry; +import de.avatic.lcc.model.nodes.NodeListEntry; +import de.avatic.lcc.model.packaging.PackagingListEntry; +import de.avatic.lcc.repositories.utils.SearchQueryPagination; +import de.avatic.lcc.repositories.utils.SearchQueryResult; +import de.avatic.lcc.repositories.utils.UnitConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Repository +public class MaterialRepository { + + JdbcTemplate jdbcTemplate; + + @Autowired + public MaterialRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Transactional + public SearchQueryResult listMaterials(String filter, SearchQueryPagination pagination) { + String query = "SELECT id, name, part_number FROM material WHERE name LIKE ? OR part_number LIKE ? LIMIT ? OFFSET ?"; + + var entries = jdbcTemplate.query(query, (rs, rowNum) -> { + MaterialListEntry entry = new MaterialListEntry(); + entry.setId(rs.getInt("id")); + entry.setName(rs.getString("name")); + entry.setPartNumber(rs.getString("part_number")); + return entry; + }, "%" + filter + "%", "%" + filter + "%", pagination.getLimit(), pagination.getOffset()); + + Integer totalCount = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM material WHERE name LIKE ? OR part_number LIKE ?", + Integer.class, "%" + filter + "%", "%" + filter + "%" + ); + + return new SearchQueryResult<>(entries, totalCount, pagination.getLimit(), pagination.getOffset()); + } + + @Transactional + public Optional getMaterialDetails(Integer id) { + String query = "SELECT * FROM material WHERE id = ? AND is_deprecated = FALSE"; + + Material material = jdbcTemplate.queryForObject(query, (rs, rowNum) -> { + Material foundMaterial = new Material(); + foundMaterial.setId(rs.getInt("id")); + foundMaterial.setName(rs.getString("name")); + foundMaterial.setPartNumber(rs.getString("part_number")); + foundMaterial.setNormalizedPartNumber(rs.getString("normalized_part_number")); + foundMaterial.setHsCode(rs.getString("hs_code")); + foundMaterial.setDeprecated(rs.getBoolean("is_deprecated")); + + return foundMaterial; + }, id); + + if(null == material) { + return Optional.empty(); + } + + // Query packaging data for the material + String packagingQuery = """ + SELECT packaging.id, parent_id, width, height, length, packaging.displayed_dimension_unit, weight, displayed_weight_unit, + content_unit_count, type, + node.id AS supplier_id, node.name AS supplier_name, node.address AS supplier_address, + country.id AS country_id, country.name AS country_name, country.iso_code AS country_iso_code, country.region_code AS country_region_code + FROM packaging + LEFT JOIN node ON packaging.supplier_node_id = node.id + LEFT JOIN country ON node.country_id = country.id + WHERE material_id = ? AND packaging.is_deprecated = FALSE + """; + + if (material != null) { + PackagingListEntry packaging = jdbcTemplate.queryForObject(packagingQuery, (packagingRs, packagingRowNum) -> { + PackagingListEntry packagingEntry = new PackagingListEntry(); + + String dimensionUnit = packagingRs.getString("displayed_dimension_unit"); + String weightUnit = packagingRs.getString("displayed_weight_unit"); + + packagingEntry.setId(packagingRs.getInt("id")); + packagingEntry.setParentId(packagingRs.getInt("parent_id")); + packagingEntry.setWidth(UnitConverter.convert(packagingRs.getInt("width"), dimensionUnit)); + packagingEntry.setHeight(UnitConverter.convert(packagingRs.getInt("height"), dimensionUnit)); + packagingEntry.setLength(UnitConverter.convert(packagingRs.getInt("length"), dimensionUnit)); + packagingEntry.setDimensionUnit(dimensionUnit); + packagingEntry.setWeight(UnitConverter.convert(packagingRs.getInt("weight"), weightUnit)); + packagingEntry.setWeightUnit(weightUnit); + packagingEntry.setContentUnitCount(packagingRs.getInt("content_unit_count")); + packagingEntry.setType(packagingRs.getString("type")); + + if (packagingRs.getObject("supplier_id") != null) { + NodeListEntry supplier = new NodeListEntry(); + supplier.setId(packagingRs.getInt("supplier_id")); + supplier.setName(packagingRs.getString("supplier_name")); + + + if(packagingRs.getObject("country_id") != null) { + CountryListEntry country = new CountryListEntry(); + country.setId(packagingRs.getInt("country_id")); + country.setName(packagingRs.getString("country_name")); + country.setIsoCode(packagingRs.getString("country_iso_code")); + country.setRegionCode(packagingRs.getString("country_region_code")); + supplier.setCountry(country); + } + + packagingEntry.setSupplier(supplier); + } + + return packagingEntry; + }, material.getId()); + + material.setPackaging(packaging); + } + + return Optional.of(material); + } + + @Transactional + public void deleteById(Integer id) { + String deleteQuery = "UPDATE material SET is_deprecated = TRUE WHERE id = ?"; + jdbcTemplate.update(deleteQuery, id); + } + + @Transactional + public Material update(Material material) { + String updateQuery = "UPDATE material SET name = ?, part_number = ?, normalized_part_number = ?, hs_code = ? WHERE id = ?"; + jdbcTemplate.update(updateQuery, material.getName(), material.getPartNumber(), material.getNormalizedPartNumber(), material.getHsCode(), material.getId()); + return material; + } +} diff --git a/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryPagination.java b/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryPagination.java new file mode 100644 index 0000000..2efebf0 --- /dev/null +++ b/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryPagination.java @@ -0,0 +1,21 @@ +package de.avatic.lcc.repositories.utils; + +public class SearchQueryPagination { + + private final Integer page; + private final Integer size; + + public SearchQueryPagination(Integer page, Integer size) { + this.page = page; + this.size = size; + } + + public Integer getLimit() { + return size; + } + + public Integer getOffset() { + return page * size; + } + +} diff --git a/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryResult.java b/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryResult.java new file mode 100644 index 0000000..22898f7 --- /dev/null +++ b/src/main/java/de/avatic/lcc/repositories/utils/SearchQueryResult.java @@ -0,0 +1,49 @@ +package de.avatic.lcc.repositories.utils; + +import java.util.List; + +public class SearchQueryResult { + + private List result; + + private Integer page, totalPages, totalElements; + + public SearchQueryResult(List result, Integer page, Integer totalPages, Integer totalElements) { + this.result = result; + this.page = page; + this.totalPages = totalPages; + this.totalElements = totalElements; + } + + public List toList() { + return result; + } + + public void setResult(List result) { + this.result = result; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getTotalPages() { + return totalPages; + } + + public void setTotalPages(int totalPages) { + this.totalPages = totalPages; + } + + public int getTotalElements() { + return totalElements; + } + + public void setTotalElements(int totalElements) { + this.totalElements = totalElements; + } +} diff --git a/src/main/java/de/avatic/lcc/repositories/utils/UnitConverter.java b/src/main/java/de/avatic/lcc/repositories/utils/UnitConverter.java new file mode 100644 index 0000000..ff12aaf --- /dev/null +++ b/src/main/java/de/avatic/lcc/repositories/utils/UnitConverter.java @@ -0,0 +1,16 @@ +package de.avatic.lcc.repositories.utils; + +public class UnitConverter { + + public static double convert(Integer value, String unit) { + return switch (unit) { + case "MM", "G" -> Math.round(value * 100.0) / 100.0; + case "CM" -> Math.round((value / 10.0) * 100.0) / 100.0; + case "M", "KG" -> Math.round((value / 1000.0) * 100.0) / 100.0; + case "T" -> Math.round((value / 1000000.0) * 100.0) / 100.0; + default -> throw new IllegalArgumentException("Unknown unit: " + unit); + }; + + + } +} diff --git a/src/main/java/de/avatic/lcc/util/InvalidArgumentException.java b/src/main/java/de/avatic/lcc/util/InvalidArgumentException.java new file mode 100644 index 0000000..e7a730d --- /dev/null +++ b/src/main/java/de/avatic/lcc/util/InvalidArgumentException.java @@ -0,0 +1,14 @@ +package de.avatic.lcc.util; + +public class InvalidArgumentException extends RuntimeException{ + + public InvalidArgumentException() { + super(); + } + + public InvalidArgumentException(final String parameterName) { + super("Invalid parameter: " + parameterName ); + } + + +}