diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbb02ef..3848fb2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag, and push docker image to Amazon ECR - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + #if: github.ref == 'refs/heads/main' && github.event_name == 'push' env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: ${{ vars.AWS_ECR_REPO_NAME }} diff --git a/src/main/kotlin/com/fiap/stock/application/adapter/controller/ProductController.kt b/src/main/kotlin/com/fiap/stock/application/adapter/controller/ProductController.kt index 4e137be..6a76050 100644 --- a/src/main/kotlin/com/fiap/stock/application/adapter/controller/ProductController.kt +++ b/src/main/kotlin/com/fiap/stock/application/adapter/controller/ProductController.kt @@ -5,7 +5,9 @@ import com.fiap.stock.application.domain.valueobjects.ProductCategory import com.fiap.stock.application.driver.web.ProductAPI import com.fiap.stock.application.driver.web.request.ProductComposeRequest import com.fiap.stock.application.driver.web.request.ProductRequest +import com.fiap.stock.application.driver.web.request.ProductStockBatchChangeRequest import com.fiap.stock.application.driver.web.response.ProductResponse +import com.fiap.stock.application.usecases.AdjustStockUseCase import com.fiap.stock.application.usecases.AssembleProductsUseCase import com.fiap.stock.application.usecases.LoadProductUseCase import com.fiap.stock.application.usecases.SearchProductUseCase @@ -17,29 +19,37 @@ class ProductController( private val assembleProductsUseCase: AssembleProductsUseCase, private val loadProductUseCase: LoadProductUseCase, private val searchProductUseCase: SearchProductUseCase, + private val adjustStockUseCase: AdjustStockUseCase, ) : ProductAPI { override fun getByProductNumber(productNumber: Long): ResponseEntity { return loadProductUseCase.getByProductNumber(productNumber).let(::createResponse) } - override fun findAll(): ResponseEntity> { - return loadProductUseCase.findAll().let(::respond) - } + override fun findAll(): ResponseEntity> = + loadProductUseCase.findAll().let(::respond) - override fun findByCategory(category: String): ResponseEntity> { - return loadProductUseCase.findByCategory(ProductCategory.fromString(category)).let(::respond) - } + override fun findAllByProductNumber(productNumbers: List): ResponseEntity> = + loadProductUseCase.findAllByProductNumber(productNumbers).let(::respond) - override fun searchByName(name: String): ResponseEntity> { - return searchProductUseCase.searchByName(name).let(::respond) + override fun incrementStockOfProducts(productStockBatchChangeRequest: ProductStockBatchChangeRequest): ResponseEntity { + adjustStockUseCase.incrementStockOfProducts(productStockBatchChangeRequest.productNumberQuantityMap) + return ResponseEntity.ok().build() } - override fun create(productRequest: ProductRequest): ResponseEntity { - val result = - assembleProductsUseCase.create(productRequest.toDomain(), productRequest.components).let(::createResponse) - return result + override fun decrementStockOfProducts(productStockBatchChangeRequest: ProductStockBatchChangeRequest): ResponseEntity { + adjustStockUseCase.decrementStockOfProducts(productStockBatchChangeRequest.productNumberQuantityMap) + return ResponseEntity.ok().build() } + override fun findByCategory(category: String): ResponseEntity> = + loadProductUseCase.findByCategory(ProductCategory.fromString(category)).let(::respond) + + override fun searchByName(name: String): ResponseEntity> = + searchProductUseCase.searchByName(name).let(::respond) + + override fun create(productRequest: ProductRequest): ResponseEntity = + assembleProductsUseCase.create(productRequest.toDomain(), productRequest.components).let(::createResponse) + override fun update( productNumber: Long, productRequest: ProductRequest, @@ -48,22 +58,18 @@ class ProductController( return assembleProductsUseCase.update(product, productRequest.components).let(::createResponse) } - override fun delete(productNumber: Long): ResponseEntity { - return assembleProductsUseCase.delete(productNumber).let(::createResponse) - } + override fun delete(productNumber: Long): ResponseEntity = + assembleProductsUseCase.delete(productNumber).let(::createResponse) - override fun compose(productComposeRequest: ProductComposeRequest): ResponseEntity { - return assembleProductsUseCase.compose( + override fun compose(productComposeRequest: ProductComposeRequest): ResponseEntity = + assembleProductsUseCase.compose( productComposeRequest.productNumber, productComposeRequest.subItemsNumbers, ).let(::createResponse) - } - private fun createResponse(product: Product?): ResponseEntity { - return ResponseEntity.ok(product?.let { ProductResponse.fromDomain(product) }) - } + private fun createResponse(product: Product?): ResponseEntity = + ResponseEntity.ok(product?.let { ProductResponse.fromDomain(product) }) - private fun respond(products: List): ResponseEntity> { - return ResponseEntity.ok(products.map { ProductResponse.fromDomain(it) }) - } + private fun respond(products: List): ResponseEntity> = + ResponseEntity.ok(products.map { ProductResponse.fromDomain(it) }) } diff --git a/src/main/kotlin/com/fiap/stock/application/adapter/controller/config/ServiceConfig.kt b/src/main/kotlin/com/fiap/stock/application/adapter/controller/config/ServiceConfig.kt index c133954..a7621c1 100644 --- a/src/main/kotlin/com/fiap/stock/application/adapter/controller/config/ServiceConfig.kt +++ b/src/main/kotlin/com/fiap/stock/application/adapter/controller/config/ServiceConfig.kt @@ -4,6 +4,7 @@ import com.fiap.stock.application.StockApiApp import com.fiap.stock.application.adapter.gateway.ComponentGateway import com.fiap.stock.application.adapter.gateway.ProductGateway import com.fiap.stock.application.adapter.gateway.StockGateway +import com.fiap.stock.application.adapter.gateway.TransactionalGateway import com.fiap.stock.application.usecases.LoadComponentUseCase import com.fiap.stock.application.services.ComponentService import org.springframework.context.annotation.Bean @@ -11,6 +12,7 @@ import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import com.fiap.stock.application.services.ProductService import com.fiap.stock.application.services.StockService +import com.fiap.stock.application.usecases.LoadProductUseCase @Configuration @ComponentScan(basePackageClasses = [StockApiApp::class]) @@ -42,9 +44,15 @@ class ServiceConfig { } @Bean - fun createStockService(stockRepository: StockGateway): StockService { - return StockService(stockRepository) + fun createStockService( + stockRepository: StockGateway, + loadProductUseCase: LoadProductUseCase, + transactionalGateway: TransactionalGateway, + ): StockService { + return StockService( + stockRepository, + loadProductUseCase, + transactionalGateway, + ) } - - } diff --git a/src/main/kotlin/com/fiap/stock/application/adapter/gateway/ProductGateway.kt b/src/main/kotlin/com/fiap/stock/application/adapter/gateway/ProductGateway.kt index c47c58a..e4601c2 100644 --- a/src/main/kotlin/com/fiap/stock/application/adapter/gateway/ProductGateway.kt +++ b/src/main/kotlin/com/fiap/stock/application/adapter/gateway/ProductGateway.kt @@ -5,6 +5,8 @@ import com.fiap.stock.application.domain.valueobjects.ProductCategory interface ProductGateway { fun findAll(): List + + fun findAllByProductNumber(productNumbers: List): List fun findByProductNumber(productNumber: Long): Product? diff --git a/src/main/kotlin/com/fiap/stock/application/adapter/gateway/impl/ProductGatewayImpl.kt b/src/main/kotlin/com/fiap/stock/application/adapter/gateway/impl/ProductGatewayImpl.kt index b803f4f..de80f04 100644 --- a/src/main/kotlin/com/fiap/stock/application/adapter/gateway/impl/ProductGatewayImpl.kt +++ b/src/main/kotlin/com/fiap/stock/application/adapter/gateway/impl/ProductGatewayImpl.kt @@ -14,30 +14,23 @@ class ProductGatewayImpl( ) : ProductGateway { private val mapper: ProductMapper = Mappers.getMapper(ProductMapper::class.java) - override fun findAll(): List { - return productJpaRepository.findAll() - .map(mapper::toDomain) - } + override fun findAll(): List = + productJpaRepository.findAll().map(mapper::toDomain) - override fun findByProductNumber(productNumber: Long): Product? { - return productJpaRepository.findById(productNumber) - .map { mapper.toDomain(it) } - .orElse(null) - } + override fun findAllByProductNumber(productNumbers: List): List = + productJpaRepository.findAllById(productNumbers).map(mapper::toDomain) - override fun findByCategory(category: ProductCategory): List { - return productJpaRepository.findByCategoryIgnoreCase(category.toString()) - .map { mapper.toDomain(it) } - } + override fun findByProductNumber(productNumber: Long): Product? = + productJpaRepository.findById(productNumber).map { mapper.toDomain(it) }.orElse(null) - override fun searchByName(name: String): List { - return productJpaRepository.findByNameContainingIgnoreCase(name) - .map(mapper::toDomain) - } + override fun findByCategory(category: ProductCategory): List = + productJpaRepository.findByCategoryIgnoreCase(category.toString()).map { mapper.toDomain(it) } - override fun create(product: Product): Product { - return persist(product.copy(number = null)) - } + override fun searchByName(name: String): List = + productJpaRepository.findByNameContainingIgnoreCase(name).map(mapper::toDomain) + + override fun create(product: Product): Product = + persist(product.copy(number = null)) override fun update(product: Product): Product { val number = diff --git a/src/main/kotlin/com/fiap/stock/application/driver/web/ProductAPI.kt b/src/main/kotlin/com/fiap/stock/application/driver/web/ProductAPI.kt index 1fa2712..ba63f50 100644 --- a/src/main/kotlin/com/fiap/stock/application/driver/web/ProductAPI.kt +++ b/src/main/kotlin/com/fiap/stock/application/driver/web/ProductAPI.kt @@ -2,6 +2,7 @@ package com.fiap.stock.application.driver.web import com.fiap.stock.application.driver.web.request.ProductComposeRequest import com.fiap.stock.application.driver.web.request.ProductRequest +import com.fiap.stock.application.driver.web.request.ProductStockBatchChangeRequest import com.fiap.stock.application.driver.web.response.ProductResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam @Tag(name = "produto", description = "Produtos") @RequestMapping("/admin/products") @@ -43,6 +45,71 @@ interface ProductAPI { @GetMapping fun findAll(): ResponseEntity> + @Operation( + summary = "Retorna todos os produtos identificados por número", + parameters = [ + Parameter( + name = "x-admin-token", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(type = "string", defaultValue = "token"), + ), + ], + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), + ], + ) + @GetMapping("/batch") + fun findAllByProductNumber( + @Parameter(description = "IDs de produtos") @RequestParam("numbers") productNumbers: List, + ): ResponseEntity> + + @Operation( + summary = "Incrementa estoque disponível para os produtos identificados", + parameters = [ + Parameter( + name = "x-admin-token", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(type = "string", defaultValue = "token"), + ), + ], + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), + ], + ) + @PostMapping("/batch/increment") + fun incrementStockOfProducts( + @Parameter(description = "Relações de produto e quantidade a incrementar") + @RequestBody productStockBatchChangeRequest: ProductStockBatchChangeRequest, + ): ResponseEntity + + @Operation( + summary = "Decrementa estoque disponível para os produtos identificados", + parameters = [ + Parameter( + name = "x-admin-token", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(type = "string", defaultValue = "token"), + ), + ], + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), + ], + ) + @PostMapping("/batch/decrement") + fun decrementStockOfProducts( + @Parameter(description = "Relações de produto e quantidade a decrementar") + @RequestBody productStockBatchChangeRequest: ProductStockBatchChangeRequest, + ): ResponseEntity + @Operation( summary = "Retorna produtos por categoria", parameters = [ @@ -127,7 +194,7 @@ interface ProductAPI { ApiResponse(responseCode = "500", description = "Erro não esperado"), ], ) - @PostMapping() + @PostMapping fun create( @Parameter(description = "Cadastro do produto") @RequestBody productRequest: ProductRequest, ): ResponseEntity diff --git a/src/main/kotlin/com/fiap/stock/application/driver/web/request/ProductStockChangeRequest.kt b/src/main/kotlin/com/fiap/stock/application/driver/web/request/ProductStockChangeRequest.kt new file mode 100644 index 0000000..157a3cc --- /dev/null +++ b/src/main/kotlin/com/fiap/stock/application/driver/web/request/ProductStockChangeRequest.kt @@ -0,0 +1,5 @@ +package com.fiap.stock.application.driver.web.request + +data class ProductStockBatchChangeRequest( + val productNumberQuantityMap: Map +) diff --git a/src/main/kotlin/com/fiap/stock/application/services/ProductService.kt b/src/main/kotlin/com/fiap/stock/application/services/ProductService.kt index 6ae0135..26755b5 100644 --- a/src/main/kotlin/com/fiap/stock/application/services/ProductService.kt +++ b/src/main/kotlin/com/fiap/stock/application/services/ProductService.kt @@ -5,7 +5,11 @@ import com.fiap.stock.application.domain.entities.Product import com.fiap.stock.application.domain.errors.ErrorType import com.fiap.stock.application.domain.errors.SelfOrderManagementException import com.fiap.stock.application.domain.valueobjects.ProductCategory -import com.fiap.stock.application.usecases.* +import com.fiap.stock.application.usecases.AssembleProductsUseCase +import com.fiap.stock.application.usecases.LoadComponentUseCase +import com.fiap.stock.application.usecases.LoadProductUseCase +import com.fiap.stock.application.usecases.RemoveProductUseCase +import com.fiap.stock.application.usecases.SearchProductUseCase import org.slf4j.LoggerFactory class ProductService( @@ -18,25 +22,24 @@ class ProductService( RemoveProductUseCase { private val log = LoggerFactory.getLogger(javaClass) - override fun getByProductNumber(productNumber: Long): Product { - return productRepository.findByProductNumber(productNumber) + override fun getByProductNumber(productNumber: Long): Product = + productRepository.findByProductNumber(productNumber) ?: throw SelfOrderManagementException( errorType = ErrorType.PRODUCT_NOT_FOUND, message = "Product [$productNumber] not found", ) - } - override fun findAll(): List { - return productRepository.findAll() - } + override fun findAll(): List = + productRepository.findAll() - override fun findByCategory(category: ProductCategory): List { - return productRepository.findByCategory(category) - } + override fun findAllByProductNumber(productNumbers: List): List = + productRepository.findAllByProductNumber(productNumbers) - override fun searchByName(productName: String): List { - return productRepository.searchByName(productName.trim()) - } + override fun findByCategory(category: ProductCategory): List = + productRepository.findByCategory(category) + + override fun searchByName(productName: String): List = + productRepository.searchByName(productName.trim()) override fun create( product: Product, diff --git a/src/main/kotlin/com/fiap/stock/application/services/StockService.kt b/src/main/kotlin/com/fiap/stock/application/services/StockService.kt index b9992c1..5620587 100644 --- a/src/main/kotlin/com/fiap/stock/application/services/StockService.kt +++ b/src/main/kotlin/com/fiap/stock/application/services/StockService.kt @@ -1,15 +1,19 @@ package com.fiap.stock.application.services import com.fiap.stock.application.adapter.gateway.StockGateway +import com.fiap.stock.application.adapter.gateway.TransactionalGateway import com.fiap.stock.application.domain.entities.Stock import com.fiap.stock.application.domain.errors.ErrorType import com.fiap.stock.application.domain.errors.SelfOrderManagementException import com.fiap.stock.application.usecases.AdjustStockUseCase +import com.fiap.stock.application.usecases.LoadProductUseCase import com.fiap.stock.application.usecases.LoadStockUseCase import org.slf4j.LoggerFactory class StockService( private val stockRepository: StockGateway, + private val loadProductUseCase: LoadProductUseCase, + private val transactionalGateway: TransactionalGateway, ) : LoadStockUseCase, AdjustStockUseCase { private val log = LoggerFactory.getLogger(javaClass) @@ -45,4 +49,24 @@ class StockService( } return stockRepository.update(stock.copy(quantity = stock.quantity - quantity)) } + + override fun incrementStockOfProducts(productNumberQuantityMap: Map) { + transactionalGateway.transaction { + productNumberQuantityMap.forEach{ (productId, quantity) -> + loadProductUseCase.getByProductNumber(productId).components.forEach { component -> + increment(component.number!!, quantity) + } + } + } + } + + override fun decrementStockOfProducts(productNumberQuantityMap: Map) { + transactionalGateway.transaction { + productNumberQuantityMap.forEach{ (productId, quantity) -> + loadProductUseCase.getByProductNumber(productId).components.forEach { component -> + decrement(component.number!!, quantity) + } + } + } + } } diff --git a/src/main/kotlin/com/fiap/stock/application/usecases/AdjustStockUseCase.kt b/src/main/kotlin/com/fiap/stock/application/usecases/AdjustStockUseCase.kt index b6bd651..d818fa8 100644 --- a/src/main/kotlin/com/fiap/stock/application/usecases/AdjustStockUseCase.kt +++ b/src/main/kotlin/com/fiap/stock/application/usecases/AdjustStockUseCase.kt @@ -6,4 +6,8 @@ interface AdjustStockUseCase { fun increment(componentNumber: Long, quantity: Long): Stock fun decrement(componentNumber: Long, quantity: Long): Stock + + fun incrementStockOfProducts(productNumberQuantityMap: Map) + + fun decrementStockOfProducts(productNumberQuantityMap: Map) } diff --git a/src/main/kotlin/com/fiap/stock/application/usecases/LoadProductUseCase.kt b/src/main/kotlin/com/fiap/stock/application/usecases/LoadProductUseCase.kt index 0707896..a6ac093 100644 --- a/src/main/kotlin/com/fiap/stock/application/usecases/LoadProductUseCase.kt +++ b/src/main/kotlin/com/fiap/stock/application/usecases/LoadProductUseCase.kt @@ -8,5 +8,7 @@ interface LoadProductUseCase { fun findAll(): List + fun findAllByProductNumber(productNumbers: List): List + fun findByCategory(category: ProductCategory): List } diff --git a/src/test/kotlin/com/fiap/stock/application/adapter/controller/ProductControllerTest.kt b/src/test/kotlin/com/fiap/stock/application/adapter/controller/ProductControllerTest.kt index 39abd03..d3ef8a4 100644 --- a/src/test/kotlin/com/fiap/stock/application/adapter/controller/ProductControllerTest.kt +++ b/src/test/kotlin/com/fiap/stock/application/adapter/controller/ProductControllerTest.kt @@ -3,6 +3,7 @@ package com.fiap.stock.application.adapter.controller import com.fiap.stock.application.domain.valueobjects.ProductCategory import com.fiap.stock.application.driver.web.request.ProductComposeRequest import com.fiap.stock.application.driver.web.response.ProductResponse +import com.fiap.stock.application.usecases.AdjustStockUseCase import com.fiap.stock.application.usecases.AssembleProductsUseCase import com.fiap.stock.application.usecases.LoadProductUseCase import com.fiap.stock.application.usecases.SearchProductUseCase @@ -16,18 +17,19 @@ import org.junit.jupiter.api.Test class ProductControllerTest { - private val assembleProductsUseCase: AssembleProductsUseCase = mockk() - private val loadProductUseCase: LoadProductUseCase = mockk() - private val searchProductUseCase: SearchProductUseCase = mockk() + private val assembleProductsUseCase = mockk() + private val loadProductUseCase = mockk() + private val searchProductUseCase = mockk() + private val adjustStockUseCase = mockk() private val controller = ProductController( assembleProductsUseCase = assembleProductsUseCase, loadProductUseCase = loadProductUseCase, - searchProductUseCase = searchProductUseCase + searchProductUseCase = searchProductUseCase, + adjustStockUseCase = adjustStockUseCase, ) - @Nested inner class CrudProduct { @@ -96,9 +98,7 @@ class ProductControllerTest { assertThat(response.statusCode.value()) .isEqualTo(200) - } - } @Nested @@ -106,7 +106,6 @@ class ProductControllerTest { @Test fun `getByProductNumber should return product by number`() { - val product = createProductRequest().toDomain() every { loadProductUseCase.getByProductNumber(1) } returns product.copy(number = 1) @@ -120,7 +119,6 @@ class ProductControllerTest { @Test fun `findAll should return all products`() { - val product = createProductRequest().toDomain() every { loadProductUseCase.findAll() } returns arrayListOf(product.copy(number = 1)) @@ -137,7 +135,6 @@ class ProductControllerTest { @Test fun `findByCategory should return all from a category`() { - val product = createProductRequest().toDomain() every { loadProductUseCase.findByCategory(ProductCategory.DRINK) } returns arrayListOf(product.copy(number = 1)) @@ -166,7 +163,4 @@ class ProductControllerTest { .isNotNull() } } - - - -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/fiap/stock/application/services/StockServiceTest.kt b/src/test/kotlin/com/fiap/stock/application/services/StockServiceTest.kt index 7c397e0..d96aac4 100644 --- a/src/test/kotlin/com/fiap/stock/application/services/StockServiceTest.kt +++ b/src/test/kotlin/com/fiap/stock/application/services/StockServiceTest.kt @@ -1,8 +1,10 @@ package com.fiap.stock.application.services import com.fiap.stock.application.adapter.gateway.StockGateway +import com.fiap.stock.application.adapter.gateway.TransactionalGateway import com.fiap.stock.application.domain.errors.ErrorType import com.fiap.stock.application.domain.errors.SelfOrderManagementException +import com.fiap.stock.application.usecases.LoadProductUseCase import createStock import io.mockk.every import io.mockk.mockk @@ -15,10 +17,14 @@ import org.junit.jupiter.api.Test class StockServiceTest { private val stockRepository = mockk() + private val loadProductUseCase = mockk() + private val transactionalGateway = mockk() private val stockService = StockService( stockRepository, + loadProductUseCase, + transactionalGateway, ) @AfterEach