From f6f87f657a81ef9d5f00b6c761d25bab9072eb31 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 13 Jun 2025 22:44:40 -0600 Subject: [PATCH 01/15] BS successfully connected to DB --- pom.xml | 6 +- .../InventoryManagerController.java | 2 +- .../inventory_manager/domain/Product.java | 84 +------------------ .../repository/ProductRepository.java | 1 - .../service/ProductService.java | 2 +- .../service/ProductServiceImpl.java | 2 +- src/main/resources/application.properties | 6 ++ 7 files changed, 15 insertions(+), 88 deletions(-) diff --git a/pom.xml b/pom.xml index 38b1c16..170f2b7 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,11 @@ org.springframework.boot spring-boot-starter-web - + + com.oracle.database.jdbc + ojdbc8 + 21.3.0.0 + org.springframework.boot spring-boot-devtools diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 2c6311c..275aa34 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.controller; -import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.repository.Product; import com.encorazone.inventory_manager.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/encorazone/inventory_manager/domain/Product.java b/src/main/java/com/encorazone/inventory_manager/domain/Product.java index e92d424..4e4c0a8 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/Product.java +++ b/src/main/java/com/encorazone/inventory_manager/domain/Product.java @@ -1,109 +1,27 @@ package com.encorazone.inventory_manager.domain; -import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -@Entity -@Table(name = "products") + public class Product { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, length = 120) private String name; - @Column(nullable = false) private String category; - @Column(nullable = false) private BigDecimal unitPrice; private LocalDate expirationDate; - @Column(nullable = false) private Integer quantityInStock; - @Column(updatable = false) private LocalDateTime creationDate; private LocalDateTime updateDate; - @PrePersist - public void onCreate() { - creationDate = LocalDateTime.now(); - updateDate = creationDate; - } - - @PreUpdate - public void onUpdate() { - updateDate = LocalDateTime.now(); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public BigDecimal getUnitPrice() { - return unitPrice; - } - - public void setUnitPrice(BigDecimal unitPrice) { - this.unitPrice = unitPrice; - } - - public LocalDate getExpirationDate() { - return expirationDate; - } - - public void setExpirationDate(LocalDate expirationDate) { - this.expirationDate = expirationDate; - } - - public Integer getQuantityInStock() { - return quantityInStock; - } - - public void setQuantityInStock(Integer quantityInStock) { - this.quantityInStock = quantityInStock; - } - - public LocalDateTime getCreationDate() { - return creationDate; - } - - public void setCreationDate(LocalDateTime creationDate) { - this.creationDate = creationDate; - } - - public LocalDateTime getUpdateDate() { - return updateDate; - } - public void setUpdateDate(LocalDateTime updateDate) { - this.updateDate = updateDate; - } } diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index c5ef7b9..42fd6f9 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -1,6 +1,5 @@ package com.encorazone.inventory_manager.repository; -import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository { diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java index dbb58e2..aee56f7 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.service; -import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.repository.Product; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index fa70a97..8e3430a 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.service; -import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.repository.Product; import com.encorazone.inventory_manager.repository.ProductRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9c813cc..0066a40 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ spring.application.name=inventory-manager-spark + +spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 +spring.datasource.username=#INVENTORY +spring.datasource.password=admin +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect From 9722586fc1e1ead946d46cf1bd944e5b7aee060e Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 13 Jun 2025 22:46:41 -0600 Subject: [PATCH 02/15] Modified Product object requirements --- .../InventoryManagerController.java | 2 +- .../inventory_manager/domain/Product.java | 91 ++++++++++++++++++- .../repository/ProductRepository.java | 1 + .../service/ProductService.java | 2 +- .../service/ProductServiceImpl.java | 2 +- 5 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 275aa34..2c6311c 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.controller; -import com.encorazone.inventory_manager.repository.Product; +import com.encorazone.inventory_manager.domain.Product; import com.encorazone.inventory_manager.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/encorazone/inventory_manager/domain/Product.java b/src/main/java/com/encorazone/inventory_manager/domain/Product.java index 4e4c0a8..a847480 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/Product.java +++ b/src/main/java/com/encorazone/inventory_manager/domain/Product.java @@ -1,27 +1,114 @@ package com.encorazone.inventory_manager.domain; +import jakarta.persistence.*; + import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.UUID; - +@Entity +@Table(name = "PRODUCTS") public class Product { - private Long id; + @Id + @GeneratedValue(generator = "uuid2") + @Column(name = "ID", columnDefinition = "RAW(16)") + private UUID id; + @Column(nullable = false, length = 120, name = "NAME") private String name; + @Column(nullable = false, name = "CATEGORY") private String category; + @Column(nullable = false, name = "UNIT_PRICE") private BigDecimal unitPrice; + @Column(name = "EXPIRATION_DATE") private LocalDate expirationDate; + @Column(nullable = false, name = "QUANTITY_IN_STOCK") private Integer quantityInStock; + @Column(updatable = false, name = "CREATION_DATE") private LocalDateTime creationDate; + @Column(name = "UPDATE_DATE") private LocalDateTime updateDate; + @PrePersist + public void onCreate() { + creationDate = LocalDateTime.now(); + updateDate = creationDate; + } + + @PreUpdate + public void onUpdate() { + updateDate = LocalDateTime.now(); + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public BigDecimal getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(BigDecimal unitPrice) { + this.unitPrice = unitPrice; + } + + public LocalDate getExpirationDate() { + return expirationDate; + } + + public void setExpirationDate(LocalDate expirationDate) { + this.expirationDate = expirationDate; + } + + public Integer getQuantityInStock() { + return quantityInStock; + } + + public void setQuantityInStock(Integer quantityInStock) { + this.quantityInStock = quantityInStock; + } + + public LocalDateTime getCreationDate() { + return creationDate; + } + + public void setCreationDate(LocalDateTime creationDate) { + this.creationDate = creationDate; + } + + public LocalDateTime getUpdateDate() { + return updateDate; + } + public void setUpdateDate(LocalDateTime updateDate) { + this.updateDate = updateDate; + } } diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index 42fd6f9..c5ef7b9 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -1,5 +1,6 @@ package com.encorazone.inventory_manager.repository; +import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository { diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java index aee56f7..dbb58e2 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.service; -import com.encorazone.inventory_manager.repository.Product; +import com.encorazone.inventory_manager.domain.Product; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index 8e3430a..fa70a97 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -1,6 +1,6 @@ package com.encorazone.inventory_manager.service; -import com.encorazone.inventory_manager.repository.Product; +import com.encorazone.inventory_manager.domain.Product; import com.encorazone.inventory_manager.repository.ProductRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; From 131ec637470de98e7d1dc679a965c0ff2e2c2bd3 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 13 Jun 2025 23:11:23 -0600 Subject: [PATCH 03/15] Modified Product Modified Product's ID attribute to comply with database RAW Id type --- .../controller/InventoryManagerController.java | 8 +++++--- .../repository/ProductRepository.java | 4 +++- .../inventory_manager/service/ProductService.java | 11 ++++++++--- .../inventory_manager/service/ProductServiceImpl.java | 8 +++++--- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 2c6311c..0328bdd 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -5,7 +5,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; + import java.util.List; +import java.util.UUID; //@Controller @RestController @@ -30,21 +32,21 @@ public ResponseEntity create(@RequestBody Product product) { } @PutMapping("/{id}") - public ResponseEntity update(@PathVariable Long id, @RequestBody Product product) { + public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { return productService.update(id, product) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping("/{id}/outofstock") - public ResponseEntity markOutOfStock(@PathVariable Long id) { + public ResponseEntity markOutOfStock(@PathVariable UUID id) { return productService.markOutOfStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PutMapping("/{id}/instock") - public ResponseEntity restoreStock(@PathVariable Long id) { + public ResponseEntity restoreStock(@PathVariable UUID id) { return productService.restoreStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index c5ef7b9..ab4c31e 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -3,5 +3,7 @@ import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProductRepository extends JpaRepository { +import java.util.UUID; + +public interface ProductRepository extends JpaRepository { } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java index dbb58e2..9aefa53 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java @@ -4,11 +4,16 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; public interface ProductService { List getAll(String filter, String sort, int page, int size); + Product create(Product product); - Optional update(Long id, Product product); - Optional markOutOfStock(Long id); - Optional restoreStock(Long id); + + Optional update(UUID id, Product product); + + Optional markOutOfStock(UUID id); + + Optional restoreStock(UUID id); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index fa70a97..9f95fa0 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -5,8 +5,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; + import java.util.List; import java.util.Optional; +import java.util.UUID; @Service public class ProductServiceImpl implements ProductService { @@ -26,7 +28,7 @@ public Product create(Product product) { } @Override - public Optional update(Long id, Product newProduct) { + public Optional update(UUID id, Product newProduct) { return productRepository.findById(id).map(existing -> { existing.setName(newProduct.getName()); existing.setCategory(newProduct.getCategory()); @@ -38,7 +40,7 @@ public Optional update(Long id, Product newProduct) { } @Override - public Optional markOutOfStock(Long id) { + public Optional markOutOfStock(UUID id) { return productRepository.findById(id).map(product -> { product.setQuantityInStock(0); return productRepository.save(product); @@ -46,7 +48,7 @@ public Optional markOutOfStock(Long id) { } @Override - public Optional restoreStock(Long id) { + public Optional restoreStock(UUID id) { return productRepository.findById(id).map(product -> { product.setQuantityInStock(10); // Default restore value return productRepository.save(product); From d65d4062cb48aa9e3d79894df45c5f848275c672 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Sun, 15 Jun 2025 23:43:56 -0600 Subject: [PATCH 04/15] Business service updated BS is operative now. Complying to the requirements. Changing the version to 1.0.0, although is not going tio be necessary. Added javadocs comments for documentation. Nice to have in the feature: *Availability to restore the stock using web browser cache, or something like that. Probably cookies of some sort. --- pom.xml | 7 +- .../InventoryManagerController.java | 78 ++++++++++++++++--- .../inventory_manager/domain/Product.java | 12 +-- .../domain/ProductFilter.java | 42 ++++++++++ .../repository/ProductRepository.java | 21 ++++- .../service/FilteredSearch.java | 47 +++++++++++ .../service/ProductService.java | 48 +++++++++++- .../service/ProductServiceImpl.java | 28 +++++-- 8 files changed, 256 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java create mode 100644 src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java diff --git a/pom.xml b/pom.xml index 170f2b7..1137c0f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.encorazone inventory-manager - 0.0.1-SNAPSHOT + 1.0.0 inventory-manager-spark Demo project for Spring Boot @@ -72,7 +72,10 @@ springdoc-openapi-starter-webmvc-ui 2.3.0 - + + org.hibernate.validator + hibernate-validator + diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 0328bdd..909e56e 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -2,14 +2,17 @@ import com.encorazone.inventory_manager.domain.Product; import com.encorazone.inventory_manager.service.ProductService; + +import org.springdoc.core.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.UUID; -//@Controller +@CrossOrigin(origins = "http://localhost:3000") @RestController @RequestMapping("/products") final class InventoryManagerController { @@ -17,20 +20,61 @@ final class InventoryManagerController { @Autowired private ProductService productService; + /** + * Edpoin to get all the elemnts from database, no sorting nor filtering + * Just pagination for the client usability + * + * @param page represents the page for the table. Example 0. + * @param size represents the number of elements per page, default is 10. Example 20. + * @return response status amd a list containing the pagexsize elements + */ @GetMapping public ResponseEntity> getAll( - @RequestParam(required = false) String filter, - @RequestParam(required = false) String sort, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { - return ResponseEntity.ok(productService.getAll(filter, sort, page, size)); + @RequestParam(required = false) int page, + @RequestParam(required = false, defaultValue = "10") int size) { + return ResponseEntity.ok(productService.getAll(page, size)); + } + + /** + * Endpoint for filtered data retrieving, including name, category and availability + * filtering from DB objects. + * + * @param name represent the name of the product. Example, Watermelon. + * @param category represents the category the element is part of; like food. + * @param stockQuantity represents the amount of elements in the inventory. Example 10. + * @param pageable Builtin object for sorting and pagination, the API asks for the json by itself + * @return responsse status and a list containing the pagexsixe elements + * complying with the sort and filter parameters + */ + @GetMapping("/filters") + public ResponseEntity> findByFilter( + @ModelAttribute @RequestParam(required = false) String name, + @ModelAttribute @RequestParam(required = false) String category, + @ModelAttribute @RequestParam(required = false) Integer stockQuantity, + @ParameterObject Pageable pageable) { + return ResponseEntity.ok(productService.findByNameAndCategoryAndStockQuantity( + name, category, stockQuantity, pageable)); } + /** + * Endpoint to create a new product + * + * @param product object representing the product to be added to the inventory + * @return status. Example 200(OK) + */ @PostMapping public ResponseEntity create(@RequestBody Product product) { return ResponseEntity.ok(productService.create(product)); } + /** + * endpoint to update a product + * + * @param id represents the DB/BS internal id fro managing elements. + * Example 785c0229-b7e5-4ea4-853b-fa5ad4eb84f4 + * @param product Object with the changes fto be made to the product + * @return status. Example 500 (Internal server error) + */ @PutMapping("/{id}") public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { return productService.update(id, product) @@ -38,16 +82,30 @@ public ResponseEntity update(@PathVariable UUID id, @RequestBody Produc .orElse(ResponseEntity.notFound().build()); } - @PostMapping("/{id}/outofstock") + /** + * endpoint to automatically set stock to 0 + * + * @param id Represents the id of the element we want the stock to be 0 + * @return status. Example 200 + */ + @PatchMapping("/{id}/outofstock") public ResponseEntity markOutOfStock(@PathVariable UUID id) { return productService.markOutOfStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } - @PutMapping("/{id}/instock") - public ResponseEntity restoreStock(@PathVariable UUID id) { - return productService.restoreStock(id) + /** + * endpoint to automatically set stock to a given number, By default 10 + * + * @param id Represents the id of the element we want the stock to be 0 + * @param stockQuantity Represents the amount to put into stock. Example 10 + * @return status. Example 200(OK) + */ + @PatchMapping("/{id}/instock") + public ResponseEntity restoreStock(@PathVariable UUID id, + @RequestParam(defaultValue = "10") Integer stockQuantity) { + return productService.restoreStock(id, stockQuantity) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } diff --git a/src/main/java/com/encorazone/inventory_manager/domain/Product.java b/src/main/java/com/encorazone/inventory_manager/domain/Product.java index a847480..35e8fec 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/Product.java +++ b/src/main/java/com/encorazone/inventory_manager/domain/Product.java @@ -28,8 +28,8 @@ public class Product { @Column(name = "EXPIRATION_DATE") private LocalDate expirationDate; - @Column(nullable = false, name = "QUANTITY_IN_STOCK") - private Integer quantityInStock; + @Column(nullable = false, name = "STOCK_QUANTITY") + private Integer stockQuantity; @Column(updatable = false, name = "CREATION_DATE") private LocalDateTime creationDate; @@ -88,12 +88,12 @@ public void setExpirationDate(LocalDate expirationDate) { this.expirationDate = expirationDate; } - public Integer getQuantityInStock() { - return quantityInStock; + public Integer getStockQuantity() { + return stockQuantity; } - public void setQuantityInStock(Integer quantityInStock) { - this.quantityInStock = quantityInStock; + public void setStockQuantity(Integer stockQuantity) { + this.stockQuantity = stockQuantity; } public LocalDateTime getCreationDate() { diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java new file mode 100644 index 0000000..d344b60 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java @@ -0,0 +1,42 @@ +package com.encorazone.inventory_manager.domain; + +import org.springframework.data.jpa.domain.Specification; + +public class ProductFilter { + + /** + * Creates a specification for filtering products whose names contain the given substring, + * + * @param name the name substring to search for; if null or blank, no filter is applied + * @return a Specification for matching product names, or null if the input is invalid + */ + public static Specification nameContains(String name) { + return (root, query, cb) -> + name == null || name.isBlank() ? null : + cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%"); + } + + /** + * Creates a specification for filtering products where the category contain the given substring, + * + * @param category the name substring to search for; if null or blank, no filter is applied + * @return a Specification for matching product categories, or null if the input is invalid + */ + public static Specification categoryContains(String category) { + return (root, query, cb) -> + category == null || category.isBlank() ? null : + cb.like(cb.lower(root.get("category")), "%" + category.toLowerCase() + "%"); + } + + /** + * Creates a specification for filtering products that have exactly the specified stock quantity. + * + * @param stock the stock quantity to match; if null, no filter is applied + * @return a spec for matching stock quantities, or a null if the input is {@code null} + */ + public static Specification quantityEquals(Integer stock) { + return (root, query, cb) -> + stock == null ? null : + cb.equal(root.get("stockQuantity"), stock); + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index ab4c31e..c583684 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -1,9 +1,28 @@ package com.encorazone.inventory_manager.repository; import com.encorazone.inventory_manager.domain.Product; + +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; import java.util.UUID; -public interface ProductRepository extends JpaRepository { +public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { + /** + * This method was my second try to filter and sort data. It filters well, + * cannot sort, explained better on the DEMO if I don't forget, Probably will. + * Basically the method was filtering always the availability, + * setting the stockQuantity to 0, meaning if the product had a stock different + * to 0, It wouldn't appear in the resulting list. + * + * @param name name + * @param category category + * @param quantityInStock availability of the product + * @param pageable objet for sorting and pagination + * @return a list of product matchin the description + */ + List findByNameContainingIgnoreCaseAndCategoryContainingIgnoreCaseAndStockQuantity( + String name, String category, Integer quantityInStock, Pageable pageable); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java b/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java new file mode 100644 index 0000000..0eadc07 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java @@ -0,0 +1,47 @@ +package com.encorazone.inventory_manager.service; + +/** + * This class represents the first try to sorting and filtering the data. + * Should have deleted it, but le agarré cariño. Not gonna explain the method though. + * The method explain themselves + */ +public class FilteredSearch { + public Number attributesCounter(String name, String category, Number stock) { + int num = 0; + if (!name.isEmpty()) { + num++; + } + if (!category.isEmpty()) { + num++; + } + if (stock.equals(0)) { + num++; + } + return num; + } + + public static Number attributeFilter(String name, String category, Number stock) { + if (name != null) { + if (category != null) { + if (stock != null) { + return 1; + } + return 2; + } else if (stock != null) { + return 3; + } else { + return 4; + } + } else if (category != null) { + if (stock != null) { + return 5; + } else { + return 6; + } + } else if (stock != null) { + return 7; + } else { + return 0; + } + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java index 9aefa53..b6715e3 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java @@ -2,18 +2,62 @@ import com.encorazone.inventory_manager.domain.Product; +import org.springframework.data.domain.Pageable; + import java.util.List; import java.util.Optional; import java.util.UUID; public interface ProductService { - List getAll(String filter, String sort, int page, int size); + /** + * method to get all the elemnts from database, no sorting nor filtering + * Just pagination for the client usability + * @param page represents the page for the table. Example 0. + * @param size represents the number of elements per page, default is 10. Example 20. + * @return a list containing the pagexsize elements + */ + List getAll(int page, int size); + + /** + * Method to create a new product and save it + * @param product object representing the product to be added to the inventory + * @return the product creeated + */ Product create(Product product); + /** + * Updates an existing product identified by the given ID. + * @param id the UUID of the product to update + * @param product the updated product data + * @return an Optional containing the updated product if found, or empty if not found + */ Optional update(UUID id, Product product); + /** + * Method to automatically set stock to 0 + * @param id Represents the id of the element we want the stock to be 0 + * @return an optional containing the updated product if the operation succeeded, or empty if not found + */ Optional markOutOfStock(UUID id); - Optional restoreStock(UUID id); + /** + * method to automatically set stock to the given number + * @param id Represents the id of the element we want the stock to be 0 + * @param stock Represents the amount to put into stock. Example 10 + * @return n optional containing the updated product if the operation succeeded, or empty if not found + */ + Optional restoreStock(UUID id, Integer stock); + + /** + * Endpoint for filtered data retrieving, including name, category and availability + * filtering from DB objects. + * @param name represent the name of the product (or part of it). Example, Watermelon. + * @param category represents the category the element is part of; like food. + * @param stockQuantity represents the amount of elements in the inventory. Example 10. + * @param pageable Builtin object for sorting and pagination, the API asks for the json by itself + * @return a list of products matching the criteria + */ + List findByNameAndCategoryAndStockQuantity(String name, String category, + Integer stockQuantity, Pageable pageable); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index 9f95fa0..226cbcc 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -1,9 +1,14 @@ package com.encorazone.inventory_manager.service; import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.domain.ProductFilter; import com.encorazone.inventory_manager.repository.ProductRepository; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import java.util.List; @@ -17,8 +22,7 @@ public class ProductServiceImpl implements ProductService { private ProductRepository productRepository; @Override - public List getAll(String filter, String sort, int page, int size) { - // Simplified: no filter/sort logic, just pagination + public List getAll(int page, int size) { return productRepository.findAll(PageRequest.of(page, size)).getContent(); } @@ -34,7 +38,7 @@ public Optional update(UUID id, Product newProduct) { existing.setCategory(newProduct.getCategory()); existing.setUnitPrice(newProduct.getUnitPrice()); existing.setExpirationDate(newProduct.getExpirationDate()); - existing.setQuantityInStock(newProduct.getQuantityInStock()); + existing.setStockQuantity(newProduct.getStockQuantity()); return productRepository.save(existing); }); } @@ -42,16 +46,28 @@ public Optional update(UUID id, Product newProduct) { @Override public Optional markOutOfStock(UUID id) { return productRepository.findById(id).map(product -> { - product.setQuantityInStock(0); + product.setStockQuantity(0); return productRepository.save(product); }); } @Override - public Optional restoreStock(UUID id) { + public Optional restoreStock(UUID id, Integer stock) { return productRepository.findById(id).map(product -> { - product.setQuantityInStock(10); // Default restore value + product.setStockQuantity(10); return productRepository.save(product); }); } + + @Override + public List findByNameAndCategoryAndStockQuantity(String name, String category, + Integer stockQuantity, Pageable pageable) { + Specification spec = ProductFilter.nameContains(name) + .and(ProductFilter.categoryContains(category)) + .and(ProductFilter.quantityEquals(stockQuantity)); + + Page page = productRepository.findAll(spec, pageable); + return page.getContent(); + } + } From 83276bef0a5a4510d7be4999c671a691579053b3 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 12:37:55 -0600 Subject: [PATCH 05/15] Moved hardcoded versions Modified pom.xml: - Moved hardcoded versions to the properties block --- pom.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1137c0f..0306d17 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ inventory-manager 1.0.0 inventory-manager-spark - Demo project for Spring Boot + Inventory management application @@ -28,6 +28,8 @@ 17 + 21.3.0.0 + 2.3.0 @@ -49,7 +51,7 @@ com.oracle.database.jdbc ojdbc8 - 21.3.0.0 + ${ojdbc.version} org.springframework.boot @@ -70,7 +72,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.3.0 + ${springdoc.version} org.hibernate.validator From df8e90aab756b08c5c1d5c1556bc12721147d275 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 12:40:31 -0600 Subject: [PATCH 06/15] Added lombok dependency to the project Modified pom.xml: - Added lombok dependency (version:1.18.38) to manage getters & setters --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0306d17..ae7d970 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent 3.5.0 - + com.encorazone inventory-manager @@ -30,6 +30,7 @@ 17 21.3.0.0 2.3.0 + 1.18.38 @@ -78,6 +79,11 @@ org.hibernate.validator hibernate-validator + + org.projectlombok + lombok + ${lombok.version} + From 5453e4aef72ac0b2e590a8625f604b046d0d9aae Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 12:44:57 -0600 Subject: [PATCH 07/15] Deleted getters & setters Modified Product.java: - Deleted getters & setters - Enabled lombok dependency to keep the code clean - ...Next project will use ID instead of UUID --- .../inventory_manager/domain/Product.java | 64 +------------------ 1 file changed, 2 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/encorazone/inventory_manager/domain/Product.java b/src/main/java/com/encorazone/inventory_manager/domain/Product.java index 35e8fec..c056eb2 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/Product.java +++ b/src/main/java/com/encorazone/inventory_manager/domain/Product.java @@ -1,12 +1,14 @@ package com.encorazone.inventory_manager.domain; import jakarta.persistence.*; +import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +@Data @Entity @Table(name = "PRODUCTS") public class Product { @@ -48,67 +50,5 @@ public void onUpdate() { updateDate = LocalDateTime.now(); } - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public BigDecimal getUnitPrice() { - return unitPrice; - } - - public void setUnitPrice(BigDecimal unitPrice) { - this.unitPrice = unitPrice; - } - public LocalDate getExpirationDate() { - return expirationDate; - } - - public void setExpirationDate(LocalDate expirationDate) { - this.expirationDate = expirationDate; - } - - public Integer getStockQuantity() { - return stockQuantity; - } - - public void setStockQuantity(Integer stockQuantity) { - this.stockQuantity = stockQuantity; - } - - public LocalDateTime getCreationDate() { - return creationDate; - } - - public void setCreationDate(LocalDateTime creationDate) { - this.creationDate = creationDate; - } - - public LocalDateTime getUpdateDate() { - return updateDate; - } - - public void setUpdateDate(LocalDateTime updateDate) { - this.updateDate = updateDate; - } } From f31818e953a774161a36de53c9fc420fa9e5d0eb Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 12:48:59 -0600 Subject: [PATCH 08/15] Changed to .yml for the properties file * Deleted application.properties * Created application.yml --- src/main/resources/application.properties | 7 ------- src/main/resources/application.yml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 0066a40..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,7 +0,0 @@ -spring.application.name=inventory-manager-spark - -spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 -spring.datasource.username=#INVENTORY -spring.datasource.password=admin -spring.datasource.driver-class-name=oracle.jdbc.OracleDriver -spring.jpa.database-platform=org.hibernate.dialect.OracleDialect diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..687c0cc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,12 @@ +spring: + application: + name: inventory-manager-spark + + datasource: + url: jdbc:oracle:thin:@localhost:1521/XEPDB1 + username: '#INVENTORY' + password: admin + driver-class-name: oracle.jdbc.OracleDriver + + jpa: + database-platform: org.hibernate.dialect.OracleDialect From faa56da95d67729ee56f64cfeafddc064cc96b9c Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 12:50:38 -0600 Subject: [PATCH 09/15] Deleted unused class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deleted unused FilteredSearch.java. Following the YAGNI principle. Wasn´t needed --- .../service/FilteredSearch.java | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java diff --git a/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java b/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java deleted file mode 100644 index 0eadc07..0000000 --- a/src/main/java/com/encorazone/inventory_manager/service/FilteredSearch.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.encorazone.inventory_manager.service; - -/** - * This class represents the first try to sorting and filtering the data. - * Should have deleted it, but le agarré cariño. Not gonna explain the method though. - * The method explain themselves - */ -public class FilteredSearch { - public Number attributesCounter(String name, String category, Number stock) { - int num = 0; - if (!name.isEmpty()) { - num++; - } - if (!category.isEmpty()) { - num++; - } - if (stock.equals(0)) { - num++; - } - return num; - } - - public static Number attributeFilter(String name, String category, Number stock) { - if (name != null) { - if (category != null) { - if (stock != null) { - return 1; - } - return 2; - } else if (stock != null) { - return 3; - } else { - return 4; - } - } else if (category != null) { - if (stock != null) { - return 5; - } else { - return 6; - } - } else if (stock != null) { - return 7; - } else { - return 0; - } - } -} From 237672371bf576e583e543af7b0f47904ab3f230 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 17:36:38 -0600 Subject: [PATCH 10/15] New method to delete products Modified ProductServiceImpl.java - Added method/logic to delete products. * Products are DELETED as no safe-delete was needed in this project --- .../controller/InventoryManagerController.java | 12 ++++++++++++ .../service/ProductServiceImpl.java | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 909e56e..3ab9e4d 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -109,4 +109,16 @@ public ResponseEntity restoreStock(@PathVariable UUID id, .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } + + /** + * Endpoint to delete products + * + * @param id Represemts tje id of the element to be deleted + * @return status code + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteProduct(@PathVariable UUID id) { + productService.delete(id); + return ResponseEntity.noContent().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index 226cbcc..36a91a6 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -43,6 +43,15 @@ public Optional update(UUID id, Product newProduct) { }); } + @Override + public void delete(UUID id) { + if (productRepository.existsById(id)) { + productRepository.deleteById(id); + } else { + throw new NoSuchElementException("Id " + id + "not found in db"); + } + } + @Override public Optional markOutOfStock(UUID id) { return productRepository.findById(id).map(product -> { From 10c7eaae1a4053a53c113ade85555021732629a2 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 18:15:31 -0600 Subject: [PATCH 11/15] Updated the body type of the endpoints response entities with a DTO ProductServiceImpl.java: - Removed hardcoded value from updateStock method - Added condition to avoid a db call to save the same information ProductService.java & InventoryManagerController.java: - Updated method signatures to return a DTO to prevent exposing sensitive data. No multiple layers needed. ProductFilter.java: - Updated quantityEquals method to comply with client requests --- .../InventoryManagerController.java | 11 ++++--- .../domain/ProductResponse.java | 20 ++++++++++++ .../mapper/ProductMapper.java | 15 +++++++++ .../{domain => service}/ProductFilter.java | 22 +++++++++---- .../service/ProductService.java | 32 +++++++++++++------ .../service/ProductServiceImpl.java | 30 ++++++++++------- 6 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java create mode 100644 src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java rename src/main/java/com/encorazone/inventory_manager/{domain => service}/ProductFilter.java (65%) diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 3ab9e4d..ca48758 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -1,6 +1,7 @@ package com.encorazone.inventory_manager.controller; import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.domain.ProductResponse; import com.encorazone.inventory_manager.service.ProductService; import org.springdoc.core.annotations.ParameterObject; @@ -63,7 +64,7 @@ public ResponseEntity> findByFilter( * @return status. Example 200(OK) */ @PostMapping - public ResponseEntity create(@RequestBody Product product) { + public ResponseEntity create(@RequestBody Product product) { return ResponseEntity.ok(productService.create(product)); } @@ -76,7 +77,7 @@ public ResponseEntity create(@RequestBody Product product) { * @return status. Example 500 (Internal server error) */ @PutMapping("/{id}") - public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { + public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { return productService.update(id, product) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); @@ -89,7 +90,7 @@ public ResponseEntity update(@PathVariable UUID id, @RequestBody Produc * @return status. Example 200 */ @PatchMapping("/{id}/outofstock") - public ResponseEntity markOutOfStock(@PathVariable UUID id) { + public ResponseEntity markOutOfStock(@PathVariable UUID id) { return productService.markOutOfStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); @@ -103,9 +104,9 @@ public ResponseEntity markOutOfStock(@PathVariable UUID id) { * @return status. Example 200(OK) */ @PatchMapping("/{id}/instock") - public ResponseEntity restoreStock(@PathVariable UUID id, + public ResponseEntity restoreStock(@PathVariable UUID id, @RequestParam(defaultValue = "10") Integer stockQuantity) { - return productService.restoreStock(id, stockQuantity) + return productService.updateStock(id, stockQuantity) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java new file mode 100644 index 0000000..9f1208f --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java @@ -0,0 +1,20 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +public class ProductResponse { + private UUID id; + + private String name; + + private LocalDateTime creationDate; + + private LocalDateTime updateDate; + + public ProductResponse(UUID id, String name, LocalDateTime creationDate, LocalDateTime updateDate) { + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java new file mode 100644 index 0000000..1e13958 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java @@ -0,0 +1,15 @@ +package com.encorazone.inventory_manager.mapper; + +import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.domain.ProductResponse; + +public class ProductMapper { + public static ProductResponse toProductResponse(Product product) { + return new ProductResponse( + product.getId(), + product.getName(), + product.getCreationDate(), + product.getUpdateDate() + ); + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java b/src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java similarity index 65% rename from src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java rename to src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java index d344b60..735c3ca 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/ProductFilter.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java @@ -1,5 +1,6 @@ -package com.encorazone.inventory_manager.domain; +package com.encorazone.inventory_manager.service; +import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.domain.Specification; public class ProductFilter { @@ -29,14 +30,21 @@ public static Specification categoryContains(String category) { } /** - * Creates a specification for filtering products that have exactly the specified stock quantity. + * Creates a specification for filtering the availability of the products. * - * @param stock the stock quantity to match; if null, no filter is applied - * @return a spec for matching stock quantities, or a null if the input is {@code null} + * @param stock the stock quantity parameter: + * 0 -> don't filter, 1 -> only in stock, 2 -> Not in stock, 3 -> to reset status + * @return a spec for matching stock quantities, or a null if the input is null */ public static Specification quantityEquals(Integer stock) { - return (root, query, cb) -> - stock == null ? null : - cb.equal(root.get("stockQuantity"), stock); + return switch (stock) { + case 0, 3 -> (root, query, cb) -> + cb.greaterThanOrEqualTo(root.get("stockQuantity"), 0); + case 1 -> (root, query, cb) -> + cb.greaterThan(root.get("stockQuantity"), 0); + case 2 -> (root, query, cb) -> + cb.equal(root.get("stockQuantity"), 0); + default -> null; + }; } } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java index b6715e3..440c8f8 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java @@ -2,6 +2,7 @@ import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.domain.ProductResponse; import org.springframework.data.domain.Pageable; import java.util.List; @@ -13,6 +14,7 @@ public interface ProductService { /** * method to get all the elemnts from database, no sorting nor filtering * Just pagination for the client usability + * * @param page represents the page for the table. Example 0. * @param size represents the number of elements per page, default is 10. Example 20. * @return a list containing the pagexsize elements @@ -21,41 +23,53 @@ public interface ProductService { /** * Method to create a new product and save it + * * @param product object representing the product to be added to the inventory * @return the product creeated */ - Product create(Product product); + ProductResponse create(Product product); /** * Updates an existing product identified by the given ID. - * @param id the UUID of the product to update + * + * @param id the UUID of the product to update * @param product the updated product data * @return an Optional containing the updated product if found, or empty if not found */ - Optional update(UUID id, Product product); + Optional update(UUID id, Product product); + + /** + * Method to delet product + * + * @param id Reoresents the id of the element to be deleted + */ + void delete(UUID id); /** * Method to automatically set stock to 0 + * * @param id Represents the id of the element we want the stock to be 0 * @return an optional containing the updated product if the operation succeeded, or empty if not found */ - Optional markOutOfStock(UUID id); + Optional markOutOfStock(UUID id); /** * method to automatically set stock to the given number - * @param id Represents the id of the element we want the stock to be 0 + * + * @param id Represents the id of the element we want the stock to be 0 * @param stock Represents the amount to put into stock. Example 10 * @return n optional containing the updated product if the operation succeeded, or empty if not found */ - Optional restoreStock(UUID id, Integer stock); + Optional updateStock(UUID id, Integer stock); /** * Endpoint for filtered data retrieving, including name, category and availability * filtering from DB objects. - * @param name represent the name of the product (or part of it). Example, Watermelon. - * @param category represents the category the element is part of; like food. + * + * @param name represent the name of the product (or part of it). Example, Watermelon. + * @param category represents the category the element is part of; like food. * @param stockQuantity represents the amount of elements in the inventory. Example 10. - * @param pageable Builtin object for sorting and pagination, the API asks for the json by itself + * @param pageable Builtin object for sorting and pagination, the API asks for the json by itself * @return a list of products matching the criteria */ List findByNameAndCategoryAndStockQuantity(String name, String category, diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java index 36a91a6..74a48d5 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java @@ -1,8 +1,9 @@ package com.encorazone.inventory_manager.service; import com.encorazone.inventory_manager.domain.Product; -import com.encorazone.inventory_manager.domain.ProductFilter; +import com.encorazone.inventory_manager.domain.ProductResponse; import com.encorazone.inventory_manager.repository.ProductRepository; +import com.encorazone.inventory_manager.mapper.ProductMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -12,6 +13,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @@ -27,12 +29,12 @@ public List getAll(int page, int size) { } @Override - public Product create(Product product) { - return productRepository.save(product); + public ProductResponse create(Product product) { + return ProductMapper.toProductResponse(productRepository.save(product)); } @Override - public Optional update(UUID id, Product newProduct) { + public Optional update(UUID id, Product newProduct) { return productRepository.findById(id).map(existing -> { existing.setName(newProduct.getName()); existing.setCategory(newProduct.getCategory()); @@ -40,7 +42,7 @@ public Optional update(UUID id, Product newProduct) { existing.setExpirationDate(newProduct.getExpirationDate()); existing.setStockQuantity(newProduct.getStockQuantity()); return productRepository.save(existing); - }); + }).map(ProductMapper::toProductResponse); } @Override @@ -53,19 +55,23 @@ public void delete(UUID id) { } @Override - public Optional markOutOfStock(UUID id) { + public Optional markOutOfStock(UUID id) { return productRepository.findById(id).map(product -> { - product.setStockQuantity(0); - return productRepository.save(product); - }); + if (product.getStockQuantity() > 0) { + product.setStockQuantity(0); + return productRepository.save(product); + } else { + return product; + } + }).map(ProductMapper::toProductResponse); } @Override - public Optional restoreStock(UUID id, Integer stock) { + public Optional updateStock(UUID id, Integer stock) { return productRepository.findById(id).map(product -> { - product.setStockQuantity(10); + product.setStockQuantity(stock); return productRepository.save(product); - }); + }).map(ProductMapper::toProductResponse); } @Override From 755ab542ce601a281cd0bb4a8791b573bddc6555 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Fri, 20 Jun 2025 21:24:38 -0600 Subject: [PATCH 12/15] Added feature to retrieve number of pages per data fetch --- .../InventoryManagerController.java | 40 +++++++++---------- .../domain/ProductListResponse.java | 17 ++++++++ .../domain/ProductResponse.java | 28 ++++++++++++- .../domain/ProductShortResponse.java | 28 +++++++++++++ .../domain/ProductSummaryResponse.java | 20 ++++++++++ .../mapper/ProductMapper.java | 26 ++++++++++++ ...lter.java => InventoryProductsFilter.java} | 2 +- ...ductService.java => InventoryService.java} | 20 +++++----- ...iceImpl.java => InventoryServiceImpl.java} | 39 +++++++++--------- .../service/InventorySummary.java | 4 ++ 10 files changed, 173 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductListResponse.java create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductShortResponse.java create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java rename src/main/java/com/encorazone/inventory_manager/service/{ProductFilter.java => InventoryProductsFilter.java} (98%) rename src/main/java/com/encorazone/inventory_manager/service/{ProductService.java => InventoryService.java} (77%) rename src/main/java/com/encorazone/inventory_manager/service/{ProductServiceImpl.java => InventoryServiceImpl.java} (57%) create mode 100644 src/main/java/com/encorazone/inventory_manager/service/InventorySummary.java diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index ca48758..335ce93 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -1,8 +1,9 @@ package com.encorazone.inventory_manager.controller; import com.encorazone.inventory_manager.domain.Product; -import com.encorazone.inventory_manager.domain.ProductResponse; -import com.encorazone.inventory_manager.service.ProductService; +import com.encorazone.inventory_manager.domain.ProductListResponse; +import com.encorazone.inventory_manager.domain.ProductShortResponse; +import com.encorazone.inventory_manager.service.InventoryService; import org.springdoc.core.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; @@ -10,7 +11,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; import java.util.UUID; @CrossOrigin(origins = "http://localhost:3000") @@ -19,7 +19,7 @@ final class InventoryManagerController { @Autowired - private ProductService productService; + private InventoryService inventoryService; /** * Edpoin to get all the elemnts from database, no sorting nor filtering @@ -30,10 +30,10 @@ final class InventoryManagerController { * @return response status amd a list containing the pagexsize elements */ @GetMapping - public ResponseEntity> getAll( - @RequestParam(required = false) int page, + public ResponseEntity getAll( + @RequestParam(required = false, defaultValue = "0") int page, @RequestParam(required = false, defaultValue = "10") int size) { - return ResponseEntity.ok(productService.getAll(page, size)); + return ResponseEntity.ok(inventoryService.getAll(page, size)); } /** @@ -48,12 +48,12 @@ public ResponseEntity> getAll( * complying with the sort and filter parameters */ @GetMapping("/filters") - public ResponseEntity> findByFilter( + public ResponseEntity findByFilter( @ModelAttribute @RequestParam(required = false) String name, @ModelAttribute @RequestParam(required = false) String category, - @ModelAttribute @RequestParam(required = false) Integer stockQuantity, + @ModelAttribute @RequestParam(required = false, defaultValue = "0") Integer stockQuantity, @ParameterObject Pageable pageable) { - return ResponseEntity.ok(productService.findByNameAndCategoryAndStockQuantity( + return ResponseEntity.ok(inventoryService.findByNameAndCategoryAndStockQuantity( name, category, stockQuantity, pageable)); } @@ -64,8 +64,8 @@ public ResponseEntity> findByFilter( * @return status. Example 200(OK) */ @PostMapping - public ResponseEntity create(@RequestBody Product product) { - return ResponseEntity.ok(productService.create(product)); + public ResponseEntity create(@RequestBody Product product) { + return ResponseEntity.ok(inventoryService.create(product)); } /** @@ -77,8 +77,8 @@ public ResponseEntity create(@RequestBody Product product) { * @return status. Example 500 (Internal server error) */ @PutMapping("/{id}") - public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { - return productService.update(id, product) + public ResponseEntity update(@PathVariable UUID id, @RequestBody Product product) { + return inventoryService.update(id, product) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @@ -90,8 +90,8 @@ public ResponseEntity update(@PathVariable UUID id, @RequestBod * @return status. Example 200 */ @PatchMapping("/{id}/outofstock") - public ResponseEntity markOutOfStock(@PathVariable UUID id) { - return productService.markOutOfStock(id) + public ResponseEntity markOutOfStock(@PathVariable UUID id) { + return inventoryService.markOutOfStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @@ -104,9 +104,9 @@ public ResponseEntity markOutOfStock(@PathVariable UUID id) { * @return status. Example 200(OK) */ @PatchMapping("/{id}/instock") - public ResponseEntity restoreStock(@PathVariable UUID id, - @RequestParam(defaultValue = "10") Integer stockQuantity) { - return productService.updateStock(id, stockQuantity) + public ResponseEntity restoreStock(@PathVariable UUID id, + @RequestParam(defaultValue = "10") Integer stockQuantity) { + return inventoryService.updateStock(id, stockQuantity) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @@ -119,7 +119,7 @@ public ResponseEntity restoreStock(@PathVariable UUID id, */ @DeleteMapping("/{id}") public ResponseEntity deleteProduct(@PathVariable UUID id) { - productService.delete(id); + inventoryService.delete(id); return ResponseEntity.noContent().build(); } } \ No newline at end of file diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductListResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductListResponse.java new file mode 100644 index 0000000..fb4959b --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductListResponse.java @@ -0,0 +1,17 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.util.List; + +@Data +public class ProductListResponse { + private List products; + + private Integer totalPages; + + public ProductListResponse(List products, Integer totalPages) { + this.products = products; + this.totalPages = totalPages; + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java index 9f1208f..4f28f7a 100644 --- a/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java @@ -2,6 +2,8 @@ import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; @@ -11,10 +13,34 @@ public class ProductResponse { private String name; + private String category; + + private BigDecimal unitPrice; + + private LocalDate expirationDate; + + private Integer stockQuantity; + private LocalDateTime creationDate; private LocalDateTime updateDate; - public ProductResponse(UUID id, String name, LocalDateTime creationDate, LocalDateTime updateDate) { + public ProductResponse( + UUID id, + String name, + String category, + BigDecimal unitPrice, + LocalDate expirationDate, + Integer stockQuantity, + LocalDateTime creationDate, + LocalDateTime updateDate) { + this.id = id; + this.name = name; + this.category = category; + this.unitPrice = unitPrice; + this.expirationDate = expirationDate; + this.stockQuantity = stockQuantity; + this.creationDate = creationDate; + this.updateDate = updateDate; } } diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductShortResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductShortResponse.java new file mode 100644 index 0000000..2c168a7 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductShortResponse.java @@ -0,0 +1,28 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +public class ProductShortResponse { + private UUID id; + + private String name; + + private LocalDateTime creationDate; + + private LocalDateTime updateDate; + + public ProductShortResponse( + UUID id, + String name, + LocalDateTime creationDate, + LocalDateTime updateDate) { + this.id = id; + this.name = name; + this.creationDate = creationDate; + this.updateDate = updateDate; + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java new file mode 100644 index 0000000..67c34e7 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java @@ -0,0 +1,20 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ProductSummaryResponse { + private Integer productsInStock; + + private BigDecimal valueInStock; + + private BigDecimal averageValue; + + public ProductSummaryResponse(Integer productsInStock, BigDecimal valueInStock, BigDecimal averageValue) { + this.productsInStock = productsInStock; + this.valueInStock = valueInStock; + this.averageValue = averageValue; + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java index 1e13958..8678741 100644 --- a/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java +++ b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java @@ -1,15 +1,41 @@ package com.encorazone.inventory_manager.mapper; import com.encorazone.inventory_manager.domain.Product; +import com.encorazone.inventory_manager.domain.ProductListResponse; import com.encorazone.inventory_manager.domain.ProductResponse; +import com.encorazone.inventory_manager.domain.ProductShortResponse; + +import java.util.List; +import java.util.stream.Collectors; public class ProductMapper { + public static ProductShortResponse toProductShortResponse(Product product) { + return new ProductShortResponse( + product.getId(), + product.getName(), + product.getCreationDate(), + product.getUpdateDate() + ); + } + public static ProductResponse toProductResponse(Product product) { return new ProductResponse( product.getId(), product.getName(), + product.getCategory(), + product.getUnitPrice(), + product.getExpirationDate(), + product.getStockQuantity(), product.getCreationDate(), product.getUpdateDate() ); } + + public static ProductListResponse toProductListResponse(List products, Integer totalPages) { + return new ProductListResponse( + products.stream() + .map(ProductMapper::toProductResponse) + .collect(Collectors.toList()), + totalPages); + } } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryProductsFilter.java similarity index 98% rename from src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java rename to src/main/java/com/encorazone/inventory_manager/service/InventoryProductsFilter.java index 735c3ca..ce476d2 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductFilter.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryProductsFilter.java @@ -3,7 +3,7 @@ import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.domain.Specification; -public class ProductFilter { +public class InventoryProductsFilter { /** * Creates a specification for filtering products whose names contain the given substring, diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java similarity index 77% rename from src/main/java/com/encorazone/inventory_manager/service/ProductService.java rename to src/main/java/com/encorazone/inventory_manager/service/InventoryService.java index 440c8f8..16006a2 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java @@ -2,14 +2,14 @@ import com.encorazone.inventory_manager.domain.Product; -import com.encorazone.inventory_manager.domain.ProductResponse; +import com.encorazone.inventory_manager.domain.ProductListResponse; +import com.encorazone.inventory_manager.domain.ProductShortResponse; import org.springframework.data.domain.Pageable; -import java.util.List; import java.util.Optional; import java.util.UUID; -public interface ProductService { +public interface InventoryService { /** * method to get all the elemnts from database, no sorting nor filtering @@ -19,7 +19,7 @@ public interface ProductService { * @param size represents the number of elements per page, default is 10. Example 20. * @return a list containing the pagexsize elements */ - List getAll(int page, int size); + ProductListResponse getAll(int page, int size); /** * Method to create a new product and save it @@ -27,7 +27,7 @@ public interface ProductService { * @param product object representing the product to be added to the inventory * @return the product creeated */ - ProductResponse create(Product product); + ProductShortResponse create(Product product); /** * Updates an existing product identified by the given ID. @@ -36,7 +36,7 @@ public interface ProductService { * @param product the updated product data * @return an Optional containing the updated product if found, or empty if not found */ - Optional update(UUID id, Product product); + Optional update(UUID id, Product product); /** * Method to delet product @@ -51,7 +51,7 @@ public interface ProductService { * @param id Represents the id of the element we want the stock to be 0 * @return an optional containing the updated product if the operation succeeded, or empty if not found */ - Optional markOutOfStock(UUID id); + Optional markOutOfStock(UUID id); /** * method to automatically set stock to the given number @@ -60,7 +60,7 @@ public interface ProductService { * @param stock Represents the amount to put into stock. Example 10 * @return n optional containing the updated product if the operation succeeded, or empty if not found */ - Optional updateStock(UUID id, Integer stock); + Optional updateStock(UUID id, Integer stock); /** * Endpoint for filtered data retrieving, including name, category and availability @@ -72,6 +72,6 @@ public interface ProductService { * @param pageable Builtin object for sorting and pagination, the API asks for the json by itself * @return a list of products matching the criteria */ - List findByNameAndCategoryAndStockQuantity(String name, String category, - Integer stockQuantity, Pageable pageable); + ProductListResponse findByNameAndCategoryAndStockQuantity(String name, String category, + Integer stockQuantity, Pageable pageable); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java similarity index 57% rename from src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java rename to src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java index 74a48d5..6fcac90 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java @@ -1,7 +1,8 @@ package com.encorazone.inventory_manager.service; import com.encorazone.inventory_manager.domain.Product; -import com.encorazone.inventory_manager.domain.ProductResponse; +import com.encorazone.inventory_manager.domain.ProductListResponse; +import com.encorazone.inventory_manager.domain.ProductShortResponse; import com.encorazone.inventory_manager.repository.ProductRepository; import com.encorazone.inventory_manager.mapper.ProductMapper; @@ -12,29 +13,29 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; -import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @Service -public class ProductServiceImpl implements ProductService { +public class InventoryServiceImpl implements InventoryService { @Autowired private ProductRepository productRepository; @Override - public List getAll(int page, int size) { - return productRepository.findAll(PageRequest.of(page, size)).getContent(); + public ProductListResponse getAll(int page, int size) { + Page products = productRepository.findAll(PageRequest.of(page, size)); + return ProductMapper.toProductListResponse(products.getContent(),products.getTotalPages()); } @Override - public ProductResponse create(Product product) { - return ProductMapper.toProductResponse(productRepository.save(product)); + public ProductShortResponse create(Product product) { + return ProductMapper.toProductShortResponse(productRepository.save(product)); } @Override - public Optional update(UUID id, Product newProduct) { + public Optional update(UUID id, Product newProduct) { return productRepository.findById(id).map(existing -> { existing.setName(newProduct.getName()); existing.setCategory(newProduct.getCategory()); @@ -42,7 +43,7 @@ public Optional update(UUID id, Product newProduct) { existing.setExpirationDate(newProduct.getExpirationDate()); existing.setStockQuantity(newProduct.getStockQuantity()); return productRepository.save(existing); - }).map(ProductMapper::toProductResponse); + }).map(ProductMapper::toProductShortResponse); } @Override @@ -55,7 +56,7 @@ public void delete(UUID id) { } @Override - public Optional markOutOfStock(UUID id) { + public Optional markOutOfStock(UUID id) { return productRepository.findById(id).map(product -> { if (product.getStockQuantity() > 0) { product.setStockQuantity(0); @@ -63,26 +64,26 @@ public Optional markOutOfStock(UUID id) { } else { return product; } - }).map(ProductMapper::toProductResponse); + }).map(ProductMapper::toProductShortResponse); } @Override - public Optional updateStock(UUID id, Integer stock) { + public Optional updateStock(UUID id, Integer stock) { return productRepository.findById(id).map(product -> { product.setStockQuantity(stock); return productRepository.save(product); - }).map(ProductMapper::toProductResponse); + }).map(ProductMapper::toProductShortResponse); } @Override - public List findByNameAndCategoryAndStockQuantity(String name, String category, - Integer stockQuantity, Pageable pageable) { - Specification spec = ProductFilter.nameContains(name) - .and(ProductFilter.categoryContains(category)) - .and(ProductFilter.quantityEquals(stockQuantity)); + public ProductListResponse findByNameAndCategoryAndStockQuantity(String name, String category, + Integer stockQuantity, Pageable pageable) { + Specification spec = InventoryProductsFilter.nameContains(name) + .and(InventoryProductsFilter.categoryContains(category)) + .and(InventoryProductsFilter.quantityEquals(stockQuantity)); Page page = productRepository.findAll(spec, pageable); - return page.getContent(); + return ProductMapper.toProductListResponse(page.getContent(), page.getTotalPages()); } } diff --git a/src/main/java/com/encorazone/inventory_manager/service/InventorySummary.java b/src/main/java/com/encorazone/inventory_manager/service/InventorySummary.java new file mode 100644 index 0000000..b6e4465 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/service/InventorySummary.java @@ -0,0 +1,4 @@ +package com.encorazone.inventory_manager.service; + +public class InventorySummary { +} From ebc6eaeca070dfb2f7ddd6668c0f023dbc4e3e8c Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Sat, 21 Jun 2025 00:48:55 -0600 Subject: [PATCH 13/15] Added functionality so client can fetch all the different categories --- .../InventoryManagerController.java | 13 ++++++++++++ .../repository/ProductRepository.java | 20 ++++--------------- .../service/InventoryService.java | 8 ++++++++ .../service/InventoryServiceImpl.java | 6 ++++++ 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index 335ce93..c39bc61 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; import java.util.UUID; @CrossOrigin(origins = "http://localhost:3000") @@ -122,4 +123,16 @@ public ResponseEntity deleteProduct(@PathVariable UUID id) { inventoryService.delete(id); return ResponseEntity.noContent().build(); } + + /** + * Endpoint to retrieve categories + * + * @return list with the categories + */ + @GetMapping("/categories") + public ResponseEntity> fetchCategories() { + return inventoryService.fetchCategories() + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } } \ No newline at end of file diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index c583684..7c65a79 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -2,27 +2,15 @@ import com.encorazone.inventory_manager.domain.Product; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { - /** - * This method was my second try to filter and sort data. It filters well, - * cannot sort, explained better on the DEMO if I don't forget, Probably will. - * Basically the method was filtering always the availability, - * setting the stockQuantity to 0, meaning if the product had a stock different - * to 0, It wouldn't appear in the resulting list. - * - * @param name name - * @param category category - * @param quantityInStock availability of the product - * @param pageable objet for sorting and pagination - * @return a list of product matchin the description - */ - List findByNameContainingIgnoreCaseAndCategoryContainingIgnoreCaseAndStockQuantity( - String name, String category, Integer quantityInStock, Pageable pageable); + @Query("SELECT DISTINCT p.category FROM Product p") + Optional> findDistinctCategories(); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java index 16006a2..45c17d3 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java @@ -6,6 +6,7 @@ import com.encorazone.inventory_manager.domain.ProductShortResponse; import org.springframework.data.domain.Pageable; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -74,4 +75,11 @@ public interface InventoryService { */ ProductListResponse findByNameAndCategoryAndStockQuantity(String name, String category, Integer stockQuantity, Pageable pageable); + + /** + * Method to retrieve categories + * + * @return a list with the categories + */ + Optional> fetchCategories(); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java index 6fcac90..f436136 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @@ -86,4 +87,9 @@ public ProductListResponse findByNameAndCategoryAndStockQuantity(String name, St return ProductMapper.toProductListResponse(page.getContent(), page.getTotalPages()); } + @Override + public Optional> fetchCategories(){ + return productRepository.findDistinctCategories(); + } + } From 6a7c42b6079672123c6857061004c48646ac5fe1 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Sun, 22 Jun 2025 23:18:27 -0600 Subject: [PATCH 14/15] Added summary/metrics functionality logic and endpoint Modified files: - ProductRepository.java: added definition to repository to make the necessary query - ProductMapper.java: Transform the InventorySummaryInterface from the repository method to a response appropriate data type for the endpoint. - InventoryService.java: added definition for the summary/metrics new method - InventoryServiceImpl.java: added implementation of the new method from the InventoryService.java - InventoryManagerController.java: added endpoint for summary --- .../InventoryManagerController.java | 13 ++++++++++ .../domain/InventorySummaryInterface.java | 10 ++++++++ .../domain/InventorySummaryResponse.java | 24 +++++++++++++++++++ .../domain/ProductSummaryResponse.java | 20 ---------------- .../mapper/ProductMapper.java | 21 ++++++++++++---- .../repository/ProductRepository.java | 6 +++++ .../service/InventoryService.java | 8 +++++++ .../service/InventoryServiceImpl.java | 6 +++++ 8 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryInterface.java create mode 100644 src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryResponse.java delete mode 100644 src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java diff --git a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java index c39bc61..f99438c 100644 --- a/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java +++ b/src/main/java/com/encorazone/inventory_manager/controller/InventoryManagerController.java @@ -1,5 +1,6 @@ package com.encorazone.inventory_manager.controller; +import com.encorazone.inventory_manager.domain.InventorySummaryResponse; import com.encorazone.inventory_manager.domain.Product; import com.encorazone.inventory_manager.domain.ProductListResponse; import com.encorazone.inventory_manager.domain.ProductShortResponse; @@ -135,4 +136,16 @@ public ResponseEntity> fetchCategories() { .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } + + /** + * Endpoint to retrieve inventory summary + * + * @return list with the summary + */ + @GetMapping("/summary") + public ResponseEntity> fetchSummary() { + return inventoryService.fetchInventorySummary() + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } } \ No newline at end of file diff --git a/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryInterface.java b/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryInterface.java new file mode 100644 index 0000000..a9c22a4 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryInterface.java @@ -0,0 +1,10 @@ +package com.encorazone.inventory_manager.domain; + +import java.math.BigDecimal; + +public interface InventorySummaryInterface { + String getCategory(); + Long getProductsInStock(); + BigDecimal getValueInStock(); + BigDecimal getAverageValue(); +} \ No newline at end of file diff --git a/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryResponse.java new file mode 100644 index 0000000..91741b5 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/InventorySummaryResponse.java @@ -0,0 +1,24 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class InventorySummaryResponse{ + private String category; + + private Integer productsInStock; + + private BigDecimal valueInStock; + + private BigDecimal averageValue; + + public InventorySummaryResponse(String category, long productsInStock, BigDecimal valueInStock, + BigDecimal averageValue) { + this.category = category; + this.productsInStock = (int) productsInStock; + this.valueInStock = valueInStock; + this.averageValue = averageValue; + } +} diff --git a/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java b/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java deleted file mode 100644 index 67c34e7..0000000 --- a/src/main/java/com/encorazone/inventory_manager/domain/ProductSummaryResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.encorazone.inventory_manager.domain; - -import lombok.Data; - -import java.math.BigDecimal; - -@Data -public class ProductSummaryResponse { - private Integer productsInStock; - - private BigDecimal valueInStock; - - private BigDecimal averageValue; - - public ProductSummaryResponse(Integer productsInStock, BigDecimal valueInStock, BigDecimal averageValue) { - this.productsInStock = productsInStock; - this.valueInStock = valueInStock; - this.averageValue = averageValue; - } -} diff --git a/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java index 8678741..a8cde9f 100644 --- a/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java +++ b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java @@ -1,9 +1,6 @@ package com.encorazone.inventory_manager.mapper; -import com.encorazone.inventory_manager.domain.Product; -import com.encorazone.inventory_manager.domain.ProductListResponse; -import com.encorazone.inventory_manager.domain.ProductResponse; -import com.encorazone.inventory_manager.domain.ProductShortResponse; +import com.encorazone.inventory_manager.domain.*; import java.util.List; import java.util.stream.Collectors; @@ -38,4 +35,20 @@ public static ProductListResponse toProductListResponse(List products, .collect(Collectors.toList()), totalPages); } + + public static InventorySummaryResponse toInventorySummaryResponse(InventorySummaryInterface product) { + return new InventorySummaryResponse( + product.getCategory(), + product.getProductsInStock(), + product.getValueInStock(), + product.getAverageValue() + ); + } + + public static List toInventorySummaryResponseList(List lista){ + return lista + .stream() + .map(ProductMapper::toInventorySummaryResponse) + .toList(); + } } diff --git a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java index 7c65a79..551cfff 100644 --- a/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java +++ b/src/main/java/com/encorazone/inventory_manager/repository/ProductRepository.java @@ -1,5 +1,6 @@ package com.encorazone.inventory_manager.repository; +import com.encorazone.inventory_manager.domain.InventorySummaryInterface; import com.encorazone.inventory_manager.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,4 +14,9 @@ public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { @Query("SELECT DISTINCT p.category FROM Product p") Optional> findDistinctCategories(); + + @Query("SELECT p.category AS category, COUNT(p) AS productsInStock, " + + "SUM(p.unitPrice) AS valueInStock, AVG(p.unitPrice) AS averageValue " + + "FROM Product p GROUP BY p.category") + List findCategoriesSummary(); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java index 45c17d3..fb56b53 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java @@ -4,6 +4,7 @@ import com.encorazone.inventory_manager.domain.ProductListResponse; import com.encorazone.inventory_manager.domain.ProductShortResponse; +import com.encorazone.inventory_manager.domain.InventorySummaryResponse; import org.springframework.data.domain.Pageable; import java.util.List; @@ -82,4 +83,11 @@ ProductListResponse findByNameAndCategoryAndStockQuantity(String name, String ca * @return a list with the categories */ Optional> fetchCategories(); + + /** + * Method to retrieve the inventory summary + * + * @return a list with the summary + */ + Optional> fetchInventorySummary(); } diff --git a/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java index f436136..5119e9e 100644 --- a/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java @@ -1,5 +1,6 @@ package com.encorazone.inventory_manager.service; +import com.encorazone.inventory_manager.domain.InventorySummaryResponse; import com.encorazone.inventory_manager.domain.Product; import com.encorazone.inventory_manager.domain.ProductListResponse; import com.encorazone.inventory_manager.domain.ProductShortResponse; @@ -92,4 +93,9 @@ public Optional> fetchCategories(){ return productRepository.findDistinctCategories(); } + @Override + public Optional> fetchInventorySummary(){ + return Optional.ofNullable(ProductMapper.toInventorySummaryResponseList(productRepository.findCategoriesSummary())); + } + } From 378946fa48370b145e99eb54931e3fdc25f36f99 Mon Sep 17 00:00:00 2001 From: Leonardo Trevizo Date: Tue, 24 Jun 2025 10:22:37 -0600 Subject: [PATCH 15/15] testing --- pom.xml | 5 ++ .../InventoryManagerControllerTests.java | 35 ++++++++++++ .../resources/application-test.yml | 12 +++++ .../inventory_manager/resources/data.sql | 21 ++++++++ .../inventory_manager/resources/schema.sql | 11 ++++ .../service/InventoryProductsFilterTests.java | 4 ++ .../service/InventoryServiceITests.java | 53 +++++++++++++++++++ .../service/InventoryServiceImplTests.java | 4 ++ 8 files changed, 145 insertions(+) create mode 100644 src/test/java/com/encorazone/inventory_manager/controller/InventoryManagerControllerTests.java create mode 100644 src/test/java/com/encorazone/inventory_manager/resources/application-test.yml create mode 100644 src/test/java/com/encorazone/inventory_manager/resources/data.sql create mode 100644 src/test/java/com/encorazone/inventory_manager/resources/schema.sql create mode 100644 src/test/java/com/encorazone/inventory_manager/service/InventoryProductsFilterTests.java create mode 100644 src/test/java/com/encorazone/inventory_manager/service/InventoryServiceITests.java create mode 100644 src/test/java/com/encorazone/inventory_manager/service/InventoryServiceImplTests.java diff --git a/pom.xml b/pom.xml index ae7d970..c998f4d 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,11 @@ lombok ${lombok.version} + + com.h2database + h2 + test + diff --git a/src/test/java/com/encorazone/inventory_manager/controller/InventoryManagerControllerTests.java b/src/test/java/com/encorazone/inventory_manager/controller/InventoryManagerControllerTests.java new file mode 100644 index 0000000..27c10a4 --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/controller/InventoryManagerControllerTests.java @@ -0,0 +1,35 @@ +package com.encorazone.inventory_manager.controller; + + +import com.encorazone.inventory_manager.service.InventoryService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +public class InventoryManagerControllerTests { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private InventoryService inventoryService; + + @Test + void getAllProducts_shouldReturnDataFromDatabase() throws Exception { + mockMvc.perform(get("/products")) + .andExpect(status().isOk()); + } + +} diff --git a/src/test/java/com/encorazone/inventory_manager/resources/application-test.yml b/src/test/java/com/encorazone/inventory_manager/resources/application-test.yml new file mode 100644 index 0000000..ce99546 --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/resources/application-test.yml @@ -0,0 +1,12 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + username: sa + password: + driver-class-name: org.h2.Driver + jpa: + hibernate.ddl-auto: none + show-sql: true + sql: + init: + mode: always \ No newline at end of file diff --git a/src/test/java/com/encorazone/inventory_manager/resources/data.sql b/src/test/java/com/encorazone/inventory_manager/resources/data.sql new file mode 100644 index 0000000..402fde8 --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/resources/data.sql @@ -0,0 +1,21 @@ +INSERT INTO PRODUCTS (ID, NAME, CATEGORY, UNIT_PRICE, EXPIRATION_DATE, STOCK_QUANTITY, CREATION_DATE, UPDATE_DATE) VALUES ( + '00212243-4455-6677-8899-aabbccdd8eff',, + 'Fresh Milk', + 'Dairy', + 25.50, + '2025-12-31', + 100, + CURRENT_TIMESTAMP, + NULL +); + +INSERT INTO PRODUCTS (ID, NAME, CATEGORY, UNIT_PRICE, EXPIRATION_DATE, STOCK_QUANTITY, CREATION_DATE, UPDATE_DATE) VALUES ( + '00112233-4455-6677-8899-aabbccddeeff',, + 'Whole Wheat Bread', + 'Bakery', + 15.25, + '2025-06-30', + 50, + CURRENT_TIMESTAMP, + NULL +); diff --git a/src/test/java/com/encorazone/inventory_manager/resources/schema.sql b/src/test/java/com/encorazone/inventory_manager/resources/schema.sql new file mode 100644 index 0000000..89d4224 --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/resources/schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE PRODUCTS ( + ID RAW(16) NOT NULL, + NAME VARCHAR(120) NOT NULL, + CATEGORY VARCHAR(255) NOT NULL, + UNIT_PRICE DECIMAL(38,2) NOT NULL, + EXPIRATION_DATE DATE, + STOCK_QUANTITY INT NOT NULL, + CREATION_DATE TIMESTAMP, + UPDATE_DATE TIMESTAMP, + PRIMARY KEY (ID) +); \ No newline at end of file diff --git a/src/test/java/com/encorazone/inventory_manager/service/InventoryProductsFilterTests.java b/src/test/java/com/encorazone/inventory_manager/service/InventoryProductsFilterTests.java new file mode 100644 index 0000000..edf236a --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/service/InventoryProductsFilterTests.java @@ -0,0 +1,4 @@ +package com.encorazone.inventory_manager.service; + +public class InventoryProductsFilterTests { +} diff --git a/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceITests.java b/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceITests.java new file mode 100644 index 0000000..59ce884 --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceITests.java @@ -0,0 +1,53 @@ +package com.encorazone.inventory_manager.service; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class InventoryServiceITests { + @Test + void getAll() { + + } + + @Test + void create() { + + } + + @Test + void update() { + + } + + @Test + void delete() { + + } + + @Test + void markOutOfStock() { + + } + + @Test + void updateStock() { + + } + + @Test + void findByNameAndCategoryAndStockQuantity() { + + } + + @Test + void fetchCategories() { + + } + + @Test + void fetchInventorySummary() { + + } +} diff --git a/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceImplTests.java b/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceImplTests.java new file mode 100644 index 0000000..3e0c6ab --- /dev/null +++ b/src/test/java/com/encorazone/inventory_manager/service/InventoryServiceImplTests.java @@ -0,0 +1,4 @@ +package com.encorazone.inventory_manager.service; + +public class InventoryServiceImplTests { +}