Ajout d'un endpoint pour déduire les quantités des produits d'un mélange selon un ratio donné.

This commit is contained in:
FyloZ 2021-03-20 00:12:30 -04:00
parent 3afd5f16f9
commit afc18a4a67
6 changed files with 123 additions and 28 deletions

View File

@ -310,12 +310,12 @@ private enum class ControllerAuthorizations(
val permissions: Map<HttpMethod, EmployeePermission>
) {
INVENTORY_ADD(
"/api/material/inventory/add", mapOf(
"/api/inventory/add/**", mapOf(
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL
)
),
INVENTORY_DEDUCT(
"/api/material/inventory/deduct", mapOf(
"/api/inventory/deduct/**", mapOf(
HttpMethod.PUT to EmployeePermission.VIEW_MATERIAL
)
),

View File

@ -4,15 +4,18 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
const val IDENTIFIER_MIX_TYPE_NAME = "mixType"
private const val MIX_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val MIX_NAME_NULL_MESSAGE = "Un nom est requis"
private const val MIX_RECIPE_NULL_MESSAGE = "Un recette est requise"
private const val MIX_MATERIAL_TYPE_NULL_MESSAGE = "Un type de prodsuit est requis"
private const val MIX_MATERIAL_TYPE_NULL_MESSAGE = "Un type de produit est requis"
private const val MIX_DEDUCT_MIX_ID_NULL_MESSAGE = "Un identifiant de mélange est requis"
private const val MIX_DEDUCT_RATIO_NULL_MESSAGE = "Un ratio est requis"
private const val MIX_DEDUCT_RATION_NEGATIVE_MESSAGE = "Le ratio doit être égal ou supérieur à 0"
@Entity
@Table(name = "mix")
@ -65,6 +68,15 @@ open class MixUpdateDto(
override fun toEntity(): Mix = throw UnsupportedOperationException()
}
data class MixDeductDto(
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
val id: Long,
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
val ratio: Float
)
// ==== DSL ====
fun mix(
id: Long? = null,
@ -90,3 +102,9 @@ fun mixUpdateDto(
mixMaterials: Map<Long, Float>? = mapOf(),
op: MixUpdateDto.() -> Unit = {}
) = MixUpdateDto(id, name, materialTypeId, mixMaterials).apply(op)
fun mixRatio(
id: Long = 0L,
ratio: Float = 1f,
op: MixDeductDto.() -> Unit = {}
) = MixDeductDto(id, ratio).apply(op)

View File

@ -0,0 +1,35 @@
package dev.fyloz.trial.colorrecipesexplorer.rest
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.model.MixDeductDto
import dev.fyloz.trial.colorrecipesexplorer.service.InventoryService
import org.springframework.context.annotation.Profile
import org.springframework.http.ResponseEntity
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.RestController
private const val INVENTORY_CONTROLLER_PATH = "api/inventory"
@RestController
@RequestMapping(INVENTORY_CONTROLLER_PATH)
@Profile("rest")
class InventoryController(
private val inventoryService: InventoryService
) {
@PutMapping("add")
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.add(quantities))
}
@PutMapping("deduct")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.deduct(quantities))
}
@PutMapping("deduct/mix")
fun deduct(@RequestBody mixRatio: MixDeductDto): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.deductMix(mixRatio))
}
}

View File

@ -13,7 +13,6 @@ import org.springframework.web.multipart.MultipartFile
import javax.validation.Valid
private const val MATERIAL_CONTROLLER_PATH = "api/material"
private const val INVENTORY_CONTROLLER_PATH = "api/material/inventory"
@RestController
@RequestMapping(MATERIAL_CONTROLLER_PATH)
@ -90,20 +89,3 @@ class MaterialController(materialService: MaterialService) :
ResponseEntity.notFound().build()
}
@RestController
@RequestMapping(INVENTORY_CONTROLLER_PATH)
@Profile("rest")
class InventoryController(
private val inventoryService: InventoryService
) {
@PutMapping("add")
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.add(quantities))
}
@PutMapping("deduct")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.deduct(quantities))
}
}

View File

@ -3,6 +3,8 @@ package dev.fyloz.trial.colorrecipesexplorer.service
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.model.MixDeductDto
import dev.fyloz.trial.colorrecipesexplorer.model.MixMaterial
import dev.fyloz.trial.colorrecipesexplorer.model.materialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.service.utils.mapMayThrow
import org.springframework.stereotype.Service
@ -15,6 +17,9 @@ interface InventoryService {
/** Adds a given quantity to the given [Material]'s inventory quantity according to the given [materialQuantity] and returns the updated quantity. */
fun add(materialQuantity: MaterialQuantityDto): Float
/** Deducts the inventory quantity of each [Material]s in the mix according to the ratio defined in the given [mixRatio] and returns the updated quantities. */
fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto>
/** Deducts the inventory quantity of each given [MaterialQuantityDto] and returns the updated quantities. */
fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto>
@ -24,7 +29,8 @@ interface InventoryService {
@Service
class InventoryServiceImpl(
private val materialService: MaterialService
private val materialService: MaterialService,
private val mixService: MixService
) : InventoryService {
@Transactional
override fun add(materialQuantities: Collection<MaterialQuantityDto>) =
@ -38,6 +44,26 @@ class InventoryServiceImpl(
materialQuantity.quantity
)
@Transactional
override fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto> {
val mix = mixService.getById(mixRatio.id)
val firstMixMaterial = mix.mixMaterials.first()
val adjustedFirstMaterialQuantity = firstMixMaterial.quantity * mixRatio.ratio
fun adjustQuantity(mixMaterial: MixMaterial): Float =
if (!mixMaterial.material.materialType!!.usePercentages)
mixMaterial.quantity * mixRatio.ratio
else
(mixMaterial.quantity * adjustedFirstMaterialQuantity) / 100f
return deduct(mix.mixMaterials.map {
materialQuantityDto(
materialId = it.material.id!!,
quantity = adjustQuantity(it)
)
})
}
@Transactional
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
val thrown = mutableListOf<MaterialQuantityDto>()

View File

@ -3,9 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.model.material
import dev.fyloz.trial.colorrecipesexplorer.model.materialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.model.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
@ -14,7 +12,8 @@ import kotlin.test.assertTrue
class InventoryServiceTest {
private val materialService: MaterialService = mock()
private val service = spy(InventoryServiceImpl(materialService))
private val mixService: MixService = mock()
private val service = spy(InventoryServiceImpl(materialService, mixService))
@AfterEach
fun afterEach() {
@ -60,6 +59,41 @@ class InventoryServiceTest {
}
}
// deductMix()
@Test
fun `deductMix() calls deduct() with a collection of MaterialQuantityDto with adjusted quantities`() {
val material = material(id = 0L, materialType = materialType(usePercentages = false))
val materialPercents = material(id = 1L, materialType = materialType(usePercentages = true))
val mixRatio = mixRatio(ratio = 1.5f)
val mix = mix(
id = mixRatio.id,
mixMaterials = mutableListOf(
mixMaterial(id = 0L, material = material, quantity = 1000f),
mixMaterial(id = 1L, material = materialPercents, quantity = 50f)
)
)
val expectedQuantities = mapOf(
0L to 1500f,
1L to 750f
)
whenever(mixService.getById(mix.id!!)).doReturn(mix)
doAnswer {
(it.arguments[0] as Collection<MaterialQuantityDto>).map { materialQuantity ->
materialQuantityDto(materialId = materialQuantity.material, quantity = 0f)
}
}.whenever(service).deduct(any<Collection<MaterialQuantityDto>>())
val found = service.deductMix(mixRatio)
verify(service).deduct(argThat<Collection<MaterialQuantityDto>> {
this.all { it.quantity == expectedQuantities[it.material] }
})
assertEquals(expectedQuantities.size, found.size)
}
// deduct()
@Test