diff --git a/pom.xml b/pom.xml index 38b1c16..c998f4d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,13 @@ org.springframework.boot spring-boot-starter-parent 3.5.0 - + com.encorazone inventory-manager - 0.0.1-SNAPSHOT + 1.0.0 inventory-manager-spark - Demo project for Spring Boot + Inventory management application @@ -28,6 +28,9 @@ 17 + 21.3.0.0 + 2.3.0 + 1.18.38 @@ -46,7 +49,11 @@ org.springframework.boot spring-boot-starter-web - + + com.oracle.database.jdbc + ojdbc8 + ${ojdbc.version} + org.springframework.boot spring-boot-devtools @@ -66,9 +73,22 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.3.0 + ${springdoc.version} + + + org.hibernate.validator + hibernate-validator + + + org.projectlombok + lombok + ${lombok.version} + + + com.h2database + h2 + test - 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..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,51 +1,150 @@ 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.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; +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 { @Autowired - private ProductService productService; + private InventoryService inventoryService; + /** + * 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)); + public ResponseEntity getAll( + @RequestParam(required = false, defaultValue = "0") int page, + @RequestParam(required = false, defaultValue = "10") int size) { + return ResponseEntity.ok(inventoryService.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, defaultValue = "0") Integer stockQuantity, + @ParameterObject Pageable pageable) { + return ResponseEntity.ok(inventoryService.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)); + public ResponseEntity create(@RequestBody Product product) { + return ResponseEntity.ok(inventoryService.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 Long 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()); + } + + /** + * 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 inventoryService.markOutOfStock(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } - @PostMapping("/{id}/outofstock") - public ResponseEntity markOutOfStock(@PathVariable Long id) { - return productService.markOutOfStock(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 inventoryService.updateStock(id, stockQuantity) + .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) { + 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()); } - @PutMapping("/{id}/instock") - public ResponseEntity restoreStock(@PathVariable Long id) { - return productService.restoreStock(id) + /** + * 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()); } 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/Product.java b/src/main/java/com/encorazone/inventory_manager/domain/Product.java index e92d424..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,35 +1,42 @@ 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") +@Table(name = "PRODUCTS") public class Product { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @GeneratedValue(generator = "uuid2") + @Column(name = "ID", columnDefinition = "RAW(16)") + private UUID id; - @Column(nullable = false, length = 120) + @Column(nullable = false, length = 120, name = "NAME") private String name; - @Column(nullable = false) + @Column(nullable = false, name = "CATEGORY") private String category; - @Column(nullable = false) + @Column(nullable = false, name = "UNIT_PRICE") private BigDecimal unitPrice; + @Column(name = "EXPIRATION_DATE") private LocalDate expirationDate; - @Column(nullable = false) - private Integer quantityInStock; + @Column(nullable = false, name = "STOCK_QUANTITY") + private Integer stockQuantity; - @Column(updatable = false) + @Column(updatable = false, name = "CREATION_DATE") private LocalDateTime creationDate; + @Column(name = "UPDATE_DATE") private LocalDateTime updateDate; @PrePersist @@ -43,67 +50,5 @@ 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/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 new file mode 100644 index 0000000..4f28f7a --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/domain/ProductResponse.java @@ -0,0 +1,46 @@ +package com.encorazone.inventory_manager.domain; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +public class ProductResponse { + private UUID id; + + 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, + 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/mapper/ProductMapper.java b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java new file mode 100644 index 0000000..a8cde9f --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/mapper/ProductMapper.java @@ -0,0 +1,54 @@ +package com.encorazone.inventory_manager.mapper; + +import com.encorazone.inventory_manager.domain.*; + +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); + } + + 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 c5ef7b9..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,7 +1,22 @@ 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; +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 { + @Query("SELECT DISTINCT p.category FROM Product p") + Optional> findDistinctCategories(); -public interface ProductRepository extends JpaRepository { + @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/InventoryProductsFilter.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryProductsFilter.java new file mode 100644 index 0000000..ce476d2 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryProductsFilter.java @@ -0,0 +1,50 @@ +package com.encorazone.inventory_manager.service; + +import com.encorazone.inventory_manager.domain.Product; +import org.springframework.data.jpa.domain.Specification; + +public class InventoryProductsFilter { + + /** + * 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 the availability of the products. + * + * @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 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/InventoryService.java b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java new file mode 100644 index 0000000..fb56b53 --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryService.java @@ -0,0 +1,93 @@ +package com.encorazone.inventory_manager.service; + +import com.encorazone.inventory_manager.domain.Product; + +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; +import java.util.Optional; +import java.util.UUID; + +public interface InventoryService { + + /** + * 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 + */ + ProductListResponse 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 + */ + ProductShortResponse 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 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); + + /** + * 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 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 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 + */ + ProductListResponse findByNameAndCategoryAndStockQuantity(String name, String category, + Integer stockQuantity, Pageable pageable); + + /** + * Method to retrieve categories + * + * @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 new file mode 100644 index 0000000..5119e9e --- /dev/null +++ b/src/main/java/com/encorazone/inventory_manager/service/InventoryServiceImpl.java @@ -0,0 +1,101 @@ +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; +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; +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; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +public class InventoryServiceImpl implements InventoryService { + + @Autowired + private ProductRepository productRepository; + + @Override + public ProductListResponse getAll(int page, int size) { + Page products = productRepository.findAll(PageRequest.of(page, size)); + return ProductMapper.toProductListResponse(products.getContent(),products.getTotalPages()); + } + + @Override + public ProductShortResponse create(Product product) { + return ProductMapper.toProductShortResponse(productRepository.save(product)); + } + + @Override + public Optional update(UUID id, Product newProduct) { + return productRepository.findById(id).map(existing -> { + existing.setName(newProduct.getName()); + existing.setCategory(newProduct.getCategory()); + existing.setUnitPrice(newProduct.getUnitPrice()); + existing.setExpirationDate(newProduct.getExpirationDate()); + existing.setStockQuantity(newProduct.getStockQuantity()); + return productRepository.save(existing); + }).map(ProductMapper::toProductShortResponse); + } + + @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 -> { + if (product.getStockQuantity() > 0) { + product.setStockQuantity(0); + return productRepository.save(product); + } else { + return product; + } + }).map(ProductMapper::toProductShortResponse); + } + + @Override + public Optional updateStock(UUID id, Integer stock) { + return productRepository.findById(id).map(product -> { + product.setStockQuantity(stock); + return productRepository.save(product); + }).map(ProductMapper::toProductShortResponse); + } + + @Override + 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 ProductMapper.toProductListResponse(page.getContent(), page.getTotalPages()); + } + + @Override + public Optional> fetchCategories(){ + return productRepository.findDistinctCategories(); + } + + @Override + public Optional> fetchInventorySummary(){ + return Optional.ofNullable(ProductMapper.toInventorySummaryResponseList(productRepository.findCategoriesSummary())); + } + +} 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 { +} diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java b/src/main/java/com/encorazone/inventory_manager/service/ProductService.java deleted file mode 100644 index dbb58e2..0000000 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.encorazone.inventory_manager.service; - -import com.encorazone.inventory_manager.domain.Product; - -import java.util.List; -import java.util.Optional; - -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); -} diff --git a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java b/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java deleted file mode 100644 index fa70a97..0000000 --- a/src/main/java/com/encorazone/inventory_manager/service/ProductServiceImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.encorazone.inventory_manager.service; - -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; -import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; - -@Service -public class ProductServiceImpl implements ProductService { - - @Autowired - private ProductRepository productRepository; - - @Override - public List getAll(String filter, String sort, int page, int size) { - // Simplified: no filter/sort logic, just pagination - return productRepository.findAll(PageRequest.of(page, size)).getContent(); - } - - @Override - public Product create(Product product) { - return productRepository.save(product); - } - - @Override - public Optional update(Long id, Product newProduct) { - return productRepository.findById(id).map(existing -> { - existing.setName(newProduct.getName()); - existing.setCategory(newProduct.getCategory()); - existing.setUnitPrice(newProduct.getUnitPrice()); - existing.setExpirationDate(newProduct.getExpirationDate()); - existing.setQuantityInStock(newProduct.getQuantityInStock()); - return productRepository.save(existing); - }); - } - - @Override - public Optional markOutOfStock(Long id) { - return productRepository.findById(id).map(product -> { - product.setQuantityInStock(0); - return productRepository.save(product); - }); - } - - @Override - public Optional restoreStock(Long id) { - return productRepository.findById(id).map(product -> { - product.setQuantityInStock(10); // Default restore value - return productRepository.save(product); - }); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 9c813cc..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=inventory-manager-spark 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 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 { +}