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 {
+}