#25 Migrate mixes to new logic
This commit is contained in:
parent
956db504f5
commit
efac09a76b
|
@ -5,6 +5,7 @@ object Constants {
|
||||||
const val FILE = "/api/file"
|
const val FILE = "/api/file"
|
||||||
const val MATERIAL = "/api/material"
|
const val MATERIAL = "/api/material"
|
||||||
const val MATERIAL_TYPE = "/api/materialtype"
|
const val MATERIAL_TYPE = "/api/materialtype"
|
||||||
|
const val MIX = "/api/recipe/mix"
|
||||||
}
|
}
|
||||||
|
|
||||||
object FilePaths {
|
object FilePaths {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.config.initializers
|
package dev.fyloz.colorrecipesexplorer.config.initializers
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.MixLogic
|
import dev.fyloz.colorrecipesexplorer.logic.MixLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.Mix
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.merge
|
import dev.fyloz.colorrecipesexplorer.utils.merge
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
@ -31,12 +31,12 @@ class MixInitializer(
|
||||||
logger.debug("Mix materials positions are valid!")
|
logger.debug("Mix materials positions are valid!")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixMixPositions(mix: Mix) {
|
private fun fixMixPositions(mix: MixDto) {
|
||||||
val maxPosition = mix.mixMaterials.maxOf { it.position }
|
val maxPosition = mix.mixMaterials.maxOf { it.position }
|
||||||
|
|
||||||
logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:")
|
logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:")
|
||||||
|
|
||||||
val invalidMixMaterials: Collection<MixMaterial> = with(mix.mixMaterials.filter { it.position == 0 }) {
|
val invalidMixMaterials: Collection<MixMaterialDto> = with(mix.mixMaterials.filter { it.position == 0 }) {
|
||||||
if (maxPosition == 0 && this.size > 1) {
|
if (maxPosition == 0 && this.size > 1) {
|
||||||
orderMixMaterials(this)
|
orderMixMaterials(this)
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,16 +52,16 @@ class MixInitializer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun increaseMixMaterialsPosition(mixMaterials: Iterable<MixMaterial>, firstPosition: Int) =
|
private fun increaseMixMaterialsPosition(mixMaterials: Iterable<MixMaterialDto>, firstPosition: Int) =
|
||||||
mixMaterials
|
mixMaterials
|
||||||
.mapIndexed { index, mixMaterial -> mixMaterial.copy(position = firstPosition + index) }
|
.mapIndexed { index, mixMaterial -> mixMaterial.copy(position = firstPosition + index) }
|
||||||
.onEach {
|
.onEach {
|
||||||
logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}")
|
logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun orderMixMaterials(mixMaterials: Collection<MixMaterial>) =
|
private fun orderMixMaterials(mixMaterials: Collection<MixMaterialDto>) =
|
||||||
LinkedList(mixMaterials).apply {
|
LinkedList(mixMaterials).apply {
|
||||||
while (this.peek().material.materialType?.usePercentages == true) {
|
while (this.peek().material.materialType.usePercentages) {
|
||||||
// The first mix material can't use percents, so move it to the end of the queue
|
// The first mix material can't use percents, so move it to the end of the queue
|
||||||
val pop = this.pop()
|
val pop = this.pop()
|
||||||
this.add(pop)
|
this.add(pop)
|
||||||
|
|
|
@ -36,7 +36,7 @@ class RecipeInitializer(
|
||||||
.filter { groupInfo -> groupInfo.steps!!.any { it.position == 0 } }
|
.filter { groupInfo -> groupInfo.steps!!.any { it.position == 0 } }
|
||||||
.map { fixGroupInformationPositions(recipe, it) }
|
.map { fixGroupInformationPositions(recipe, it) }
|
||||||
|
|
||||||
val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation)
|
val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation) { it.id }
|
||||||
|
|
||||||
with(recipe.copy(groupsInformation = updatedGroupInformation.toMutableSet())) {
|
with(recipe.copy(groupsInformation = updatedGroupInformation.toMutableSet())) {
|
||||||
recipeLogic.update(this)
|
recipeLogic.update(this)
|
||||||
|
@ -54,7 +54,7 @@ class RecipeInitializer(
|
||||||
|
|
||||||
val invalidRecipeSteps = steps.filter { it.position == 0 }
|
val invalidRecipeSteps = steps.filter { it.position == 0 }
|
||||||
val fixedRecipeSteps = increaseRecipeStepsPosition(groupInformation, invalidRecipeSteps, maxPosition + 1)
|
val fixedRecipeSteps = increaseRecipeStepsPosition(groupInformation, invalidRecipeSteps, maxPosition + 1)
|
||||||
val updatedRecipeSteps = steps.merge(fixedRecipeSteps)
|
val updatedRecipeSteps = steps.merge(fixedRecipeSteps) { it.id }
|
||||||
|
|
||||||
return groupInformation.copy(steps = updatedRecipeSteps.toMutableSet())
|
return groupInformation.copy(steps = updatedRecipeSteps.toMutableSet())
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,3 +32,10 @@ data class MaterialSaveDto(
|
||||||
|
|
||||||
val simdutFile: MultipartFile?
|
val simdutFile: MultipartFile?
|
||||||
) : EntityDto
|
) : EntityDto
|
||||||
|
|
||||||
|
data class MaterialQuantityDto(
|
||||||
|
val materialId: Long,
|
||||||
|
|
||||||
|
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||||
|
val quantity: Float
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.dtos
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Recipe
|
||||||
|
import javax.validation.constraints.Min
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
|
||||||
|
data class MixDto(
|
||||||
|
override val id: Long = 0L,
|
||||||
|
|
||||||
|
val location: String? = null,
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
val recipe: Recipe, // TODO change to dto
|
||||||
|
|
||||||
|
val mixType: MixTypeDto,
|
||||||
|
|
||||||
|
val mixMaterials: Set<MixMaterialDto>
|
||||||
|
) : EntityDto
|
||||||
|
|
||||||
|
data class MixSaveDto(
|
||||||
|
val id: Long = 0L,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val recipeId: Long = 0L,
|
||||||
|
|
||||||
|
val materialTypeId: Long,
|
||||||
|
|
||||||
|
val mixMaterials: Set<MixMaterialSaveDto>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MixDeductDto(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||||
|
val ratio: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MixLocationDto(
|
||||||
|
val mixId: Long,
|
||||||
|
|
||||||
|
val location: String?
|
||||||
|
)
|
|
@ -2,8 +2,11 @@ package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
|
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
@ -34,83 +37,87 @@ class DefaultInventoryLogic(
|
||||||
) : InventoryLogic {
|
) : InventoryLogic {
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun add(materialQuantities: Collection<MaterialQuantityDto>) =
|
override fun add(materialQuantities: Collection<MaterialQuantityDto>) =
|
||||||
materialQuantities.map {
|
materialQuantities.map { MaterialQuantityDto(it.materialId, add(it)) }
|
||||||
materialQuantityDto(materialId = it.material, quantity = add(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun add(materialQuantity: MaterialQuantityDto) =
|
override fun add(materialQuantity: MaterialQuantityDto) =
|
||||||
materialLogic.updateQuantity(
|
materialLogic.updateQuantity(
|
||||||
materialLogic.getById(materialQuantity.material),
|
materialLogic.getById(materialQuantity.materialId),
|
||||||
materialQuantity.quantity
|
materialQuantity.quantity
|
||||||
)
|
)
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto> {
|
override fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto> {
|
||||||
val mix = mixLogic.getById(mixRatio.id)
|
val mix = mixLogic.getById(mixRatio.id)
|
||||||
val firstMixMaterial = mix.mixMaterials.first()
|
|
||||||
val adjustedFirstMaterialQuantity = firstMixMaterial.quantity * mixRatio.ratio
|
|
||||||
|
|
||||||
fun adjustQuantity(mixMaterial: MixMaterial): Float =
|
return deduct(getMaterialsWithAdjustedQuantities(mix.mixMaterials, mixRatio))
|
||||||
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
|
@Transactional
|
||||||
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
|
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
|
||||||
val thrown = mutableListOf<NotEnoughInventoryException>()
|
val thrown = mutableListOf<NotEnoughInventoryException>()
|
||||||
|
|
||||||
val updatedQuantities =
|
val updatedQuantities =
|
||||||
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
|
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
|
||||||
{ thrown.add(it) }
|
{ thrown.add(it) }
|
||||||
) {
|
) {
|
||||||
materialQuantityDto(materialId = it.material, quantity = deduct(it))
|
MaterialQuantityDto(it.materialId, deduct(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thrown.isNotEmpty()) {
|
if (thrown.isNotEmpty()) {
|
||||||
throw MultiplesNotEnoughInventoryException(thrown)
|
throw MultiplesNotEnoughInventoryException(thrown)
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedQuantities
|
return updatedQuantities
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deduct(materialQuantity: MaterialQuantityDto): Float =
|
override fun deduct(materialQuantity: MaterialQuantityDto): Float =
|
||||||
with(materialLogic.getById(materialQuantity.material)) {
|
with(materialLogic.getById(materialQuantity.materialId)) {
|
||||||
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
||||||
materialLogic.updateQuantity(this, -materialQuantity.quantity)
|
materialLogic.updateQuantity(this, -materialQuantity.quantity)
|
||||||
} else {
|
} else {
|
||||||
throw NotEnoughInventoryException(materialQuantity.quantity, this)
|
throw NotEnoughInventoryException(materialQuantity.quantity, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getMaterialsWithAdjustedQuantities(
|
||||||
|
mixMaterials: Collection<MixMaterialDto>,
|
||||||
|
mixRatio: MixDeductDto
|
||||||
|
): Collection<MaterialQuantityDto> {
|
||||||
|
val adjustedFirstMaterialQuantity = mixMaterials.first().quantity * mixRatio.ratio
|
||||||
|
|
||||||
|
fun getAdjustedQuantity(material: MaterialDto, quantity: Float) =
|
||||||
|
if (!material.materialType.usePercentages)
|
||||||
|
quantity * mixRatio.ratio // Simply multiply the quantity by the ratio
|
||||||
|
else
|
||||||
|
(quantity * adjustedFirstMaterialQuantity) / 100f // Percents quantities are a ratio of the first material
|
||||||
|
|
||||||
|
return mixMaterials.associate { it.material to it.quantity }
|
||||||
|
.mapValues { getAdjustedQuantity(it.key, it.value) }
|
||||||
|
.map { MaterialQuantityDto(it.key.id, it.value) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotEnoughInventoryException(quantity: Float, material: MaterialDto) :
|
class NotEnoughInventoryException(quantity: Float, material: MaterialDto) :
|
||||||
RestException(
|
RestException(
|
||||||
"notenoughinventory",
|
"notenoughinventory",
|
||||||
"Not enough inventory",
|
"Not enough inventory",
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
|
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
|
||||||
mapOf(
|
mapOf(
|
||||||
"material" to material.name,
|
"material" to material.name,
|
||||||
"materialId" to material.id.toString(),
|
"materialId" to material.id.toString(),
|
||||||
"requestQuantity" to quantity,
|
"requestQuantity" to quantity,
|
||||||
"availableQuantity" to material.inventoryQuantity
|
"availableQuantity" to material.inventoryQuantity
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
class MultiplesNotEnoughInventoryException(exceptions: List<NotEnoughInventoryException>) :
|
class MultiplesNotEnoughInventoryException(exceptions: List<NotEnoughInventoryException>) :
|
||||||
RestException(
|
RestException(
|
||||||
"notenoughinventory-multiple",
|
"notenoughinventory-multiple",
|
||||||
"Not enough inventory",
|
"Not enough inventory",
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
"Cannot deduct requested quantities because there is no enough of them in inventory",
|
"Cannot deduct requested quantities because there is no enough of them in inventory",
|
||||||
mapOf(
|
mapOf(
|
||||||
"lowQuantities" to exceptions.map { it.extensions }
|
"lowQuantities" to exceptions.map { it.extensions }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
|
@ -1,102 +1,66 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
import dev.fyloz.colorrecipesexplorer.dtos.MixLocationDto
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.setAll
|
import dev.fyloz.colorrecipesexplorer.dtos.MixSaveDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Mix
|
||||||
|
import dev.fyloz.colorrecipesexplorer.service.MixService
|
||||||
import org.springframework.context.annotation.Lazy
|
import org.springframework.context.annotation.Lazy
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import javax.transaction.Transactional
|
|
||||||
|
|
||||||
interface MixLogic : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository> {
|
interface MixLogic : Logic<MixDto, MixService> {
|
||||||
/** Gets all mixes with the given [mixType]. */
|
/** Saves the given [dto]. */
|
||||||
fun getAllByMixType(mixType: MixType): Collection<Mix>
|
fun save(dto: MixSaveDto): MixDto
|
||||||
|
|
||||||
/** Checks if a [MixType] is shared by several [Mix]es or not. */
|
/** Updates the given [dto]. */
|
||||||
fun mixTypeIsShared(mixType: MixType): Boolean
|
fun update(dto: MixSaveDto): MixDto
|
||||||
|
|
||||||
/** Updates the location of each [Mix] in the given [MixLocationDto]s. */
|
/** Updates the location of each mix in the given [updatedLocations]. */
|
||||||
fun updateLocations(updatedLocations: Collection<MixLocationDto>)
|
fun updateLocations(updatedLocations: Collection<MixLocationDto>)
|
||||||
|
|
||||||
/** Updates the location of a given [Mix] to the given [MixLocationDto]. */
|
|
||||||
fun updateLocation(updatedLocation: MixLocationDto)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@LogicComponent
|
||||||
@RequireDatabase
|
|
||||||
class DefaultMixLogic(
|
class DefaultMixLogic(
|
||||||
mixRepository: MixRepository,
|
service: MixService,
|
||||||
@Lazy val recipeLogic: RecipeLogic,
|
@Lazy private val recipeLogic: RecipeLogic,
|
||||||
@Lazy val materialTypeLogic: MaterialTypeLogic,
|
@Lazy private val materialTypeLogic: MaterialTypeLogic,
|
||||||
val mixMaterialLogic: MixMaterialLogic,
|
private val mixTypeLogic: MixTypeLogic,
|
||||||
val mixTypeLogic: MixTypeLogic
|
private val mixMaterialLogic: MixMaterialLogic
|
||||||
) : AbstractExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository>(mixRepository),
|
) : BaseLogic<MixDto, MixService>(service, Mix::class.simpleName!!), MixLogic {
|
||||||
MixLogic {
|
|
||||||
override fun idNotFoundException(id: Long) = mixIdNotFoundException(id)
|
|
||||||
override fun idAlreadyExistsException(id: Long) = mixIdAlreadyExistsException(id)
|
|
||||||
|
|
||||||
override fun getAllByMixType(mixType: MixType): Collection<Mix> = repository.findAllByMixType(mixType)
|
|
||||||
override fun mixTypeIsShared(mixType: MixType): Boolean = getAllByMixType(mixType).count() > 1
|
|
||||||
|
|
||||||
override fun Mix.toOutput() = MixOutputDto(
|
|
||||||
this.id!!,
|
|
||||||
this.location,
|
|
||||||
this.mixType,
|
|
||||||
this.mixMaterials.map { mixMaterialDto(it) }.toSet()
|
|
||||||
)
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun save(entity: MixSaveDto): Mix {
|
override fun save(dto: MixSaveDto): MixDto {
|
||||||
val recipe = recipeLogic.getById(entity.recipeId)
|
val recipe = recipeLogic.getById(dto.recipeId)
|
||||||
val materialType = materialTypeLogic.getById(entity.materialTypeId)
|
val materialType = materialTypeLogic.getById(dto.materialTypeId)
|
||||||
val mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(entity.name, materialType)
|
|
||||||
|
|
||||||
val mixMaterials =
|
val mix = MixDto(
|
||||||
if (entity.mixMaterials != null) mixMaterialLogic.saveAll(entity.mixMaterials).toSet() else setOf()
|
recipe = recipe,
|
||||||
mixMaterialLogic.validateMixMaterials(mixMaterials)
|
mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(dto.name, materialType),
|
||||||
|
mixMaterials = mixMaterialLogic.validateAndSaveAll(dto.mixMaterials).toSet()
|
||||||
|
)
|
||||||
|
|
||||||
var mix = mix(recipe = recipe, mixType = mixType(mixType), mixMaterials = mixMaterials.map(::mixMaterial).toMutableSet())
|
return save(mix)
|
||||||
mix = save(mix)
|
|
||||||
|
|
||||||
recipeLogic.addMix(recipe, mix)
|
|
||||||
|
|
||||||
return mix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun update(entity: MixUpdateDto): Mix {
|
override fun update(dto: MixSaveDto): MixDto {
|
||||||
val mix = getById(entity.id)
|
val materialType = materialTypeLogic.getById(dto.materialTypeId)
|
||||||
if (entity.name != null || entity.materialTypeId != null) {
|
val mix = getById(dto.id)
|
||||||
val name = entity.name ?: mix.mixType.name
|
|
||||||
val materialType = if (entity.materialTypeId != null)
|
|
||||||
materialType(materialTypeLogic.getById(entity.materialTypeId))
|
|
||||||
else
|
|
||||||
mix.mixType.material.materialType!!
|
|
||||||
|
|
||||||
mix.mixType = if (mixTypeIsShared(mix.mixType)) {
|
return update(
|
||||||
mixType(mixTypeLogic.saveForNameAndMaterialType(name, materialTypeDto(materialType)))
|
MixDto(
|
||||||
} else {
|
id = dto.id,
|
||||||
mixType(mixTypeLogic.updateForNameAndMaterialType(mixTypeDto(mix.mixType), name, materialTypeDto(materialType)))
|
recipe = recipeLogic.getById(dto.recipeId),
|
||||||
}
|
mixType = mixTypeLogic.updateOrCreateForNameAndMaterialType(mix.mixType, dto.name, materialType),
|
||||||
}
|
mixMaterials = mixMaterialLogic.validateAndSaveAll(dto.mixMaterials).toSet()
|
||||||
if (entity.mixMaterials != null) {
|
)
|
||||||
mix.mixMaterials.setAll(mixMaterialLogic.saveAll(entity.mixMaterials!!).map(::mixMaterial).toMutableSet())
|
)
|
||||||
}
|
|
||||||
return update(mix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateLocations(updatedLocations: Collection<MixLocationDto>) {
|
override fun updateLocations(updatedLocations: Collection<MixLocationDto>) =
|
||||||
updatedLocations.forEach(::updateLocation)
|
updatedLocations.forEach(::updateLocation)
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateLocation(updatedLocation: MixLocationDto) {
|
private fun updateLocation(updatedLocation: MixLocationDto) {
|
||||||
repository.updateLocationById(updatedLocation.mixId, updatedLocation.location)
|
service.updateLocationById(updatedLocation.mixId, updatedLocation.location)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Transactional
|
|
||||||
override fun delete(entity: Mix) {
|
|
||||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixException(entity)
|
|
||||||
recipeLogic.removeMix(entity)
|
|
||||||
super.delete(entity)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,12 +2,14 @@ package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialSaveDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||||
import dev.fyloz.colorrecipesexplorer.service.MixMaterialService
|
import dev.fyloz.colorrecipesexplorer.service.MixMaterialService
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||||
|
import org.springframework.context.annotation.Lazy
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
|
|
||||||
interface MixMaterialLogic : Logic<MixMaterialDto, MixMaterialService> {
|
interface MixMaterialLogic : Logic<MixMaterialDto, MixMaterialService> {
|
||||||
|
@ -17,10 +19,13 @@ interface MixMaterialLogic : Logic<MixMaterialDto, MixMaterialService> {
|
||||||
* If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown.
|
* If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown.
|
||||||
*/
|
*/
|
||||||
fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>)
|
fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>)
|
||||||
|
|
||||||
|
/** Validates the given mix materials [dtos] and save them. */
|
||||||
|
fun validateAndSaveAll(dtos: Collection<MixMaterialSaveDto>): Collection<MixMaterialDto>
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogicComponent
|
@LogicComponent
|
||||||
class DefaultMixMaterialLogic(service: MixMaterialService) :
|
class DefaultMixMaterialLogic(service: MixMaterialService, @Lazy private val materialLogic: MaterialLogic) :
|
||||||
BaseLogic<MixMaterialDto, MixMaterialService>(service, MixMaterial::class.simpleName!!), MixMaterialLogic {
|
BaseLogic<MixMaterialDto, MixMaterialService>(service, MixMaterial::class.simpleName!!), MixMaterialLogic {
|
||||||
override fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>) {
|
override fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>) {
|
||||||
if (mixMaterials.isEmpty()) return
|
if (mixMaterials.isEmpty()) return
|
||||||
|
@ -37,6 +42,21 @@ class DefaultMixMaterialLogic(service: MixMaterialService) :
|
||||||
throw InvalidFirstMixMaterialException(sortedMixMaterials[0])
|
throw InvalidFirstMixMaterialException(sortedMixMaterials[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun validateAndSaveAll(dtos: Collection<MixMaterialSaveDto>): Collection<MixMaterialDto> {
|
||||||
|
val dtosWithMaterials = dtos.map {
|
||||||
|
MixMaterialDto(
|
||||||
|
id = it.id,
|
||||||
|
material = materialLogic.getById(it.materialId),
|
||||||
|
quantity = it.quantity,
|
||||||
|
position = it.position
|
||||||
|
)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
validateMixMaterials(dtosWithMaterials)
|
||||||
|
|
||||||
|
return dtosWithMaterials.map(::save)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check if required
|
// TODO check if required
|
||||||
|
|
|
@ -6,27 +6,50 @@ import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixType
|
import dev.fyloz.colorrecipesexplorer.model.MixType
|
||||||
import dev.fyloz.colorrecipesexplorer.service.MixTypeService
|
import dev.fyloz.colorrecipesexplorer.service.MixTypeService
|
||||||
|
import org.springframework.context.annotation.Lazy
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
interface MixTypeLogic : Logic<MixTypeDto, MixTypeService> {
|
interface MixTypeLogic : Logic<MixTypeDto, MixTypeService> {
|
||||||
/** Returns a [MixType] for the given [name] and [materialType]. If this mix type does not already exist, it will be created. */
|
/** Returns a mix type for the given [name] and [materialType]. If this mix type does not already exist, it will be created. */
|
||||||
fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto
|
fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto
|
||||||
|
|
||||||
/** Returns a new and persisted [MixType] with the given [name] and [materialType]. */
|
/** Updates the [mixType] with the given [name] and [materialType], or create a new one if it is shared with other mixes. */
|
||||||
fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto
|
fun updateOrCreateForNameAndMaterialType(
|
||||||
|
mixType: MixTypeDto,
|
||||||
/** Returns the given [mixType] updated with the given [name] and [materialType]. */
|
name: String,
|
||||||
fun updateForNameAndMaterialType(mixType: MixTypeDto, name: String, materialType: MaterialTypeDto): MixTypeDto
|
materialType: MaterialTypeDto
|
||||||
|
): MixTypeDto
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogicComponent
|
@LogicComponent
|
||||||
class DefaultMixTypeLogic(service: MixTypeService, private val materialLogic: MaterialLogic) :
|
class DefaultMixTypeLogic(
|
||||||
|
service: MixTypeService,
|
||||||
|
@Lazy private val materialLogic: MaterialLogic
|
||||||
|
) :
|
||||||
BaseLogic<MixTypeDto, MixTypeService>(service, MixType::class.simpleName!!), MixTypeLogic {
|
BaseLogic<MixTypeDto, MixTypeService>(service, MixType::class.simpleName!!), MixTypeLogic {
|
||||||
|
@Transactional
|
||||||
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto) =
|
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto) =
|
||||||
service.getByNameAndMaterialType(name, materialType.id) ?: saveForNameAndMaterialType(name, materialType)
|
service.getByNameAndMaterialType(name, materialType.id) ?: saveForNameAndMaterialType(name, materialType)
|
||||||
|
|
||||||
@Transactional
|
override fun updateOrCreateForNameAndMaterialType(
|
||||||
override fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto {
|
mixType: MixTypeDto,
|
||||||
|
name: String,
|
||||||
|
materialType: MaterialTypeDto
|
||||||
|
) = if (service.isShared(mixType.id)) {
|
||||||
|
saveForNameAndMaterialType(name, materialType)
|
||||||
|
} else {
|
||||||
|
updateForNameAndMaterialType(mixType, name, materialType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long) {
|
||||||
|
if (service.isUsedByMixes(id)) {
|
||||||
|
throw cannotDeleteException("Cannot delete the mix type with the id '$id' because one or more mixes depends on it")
|
||||||
|
}
|
||||||
|
|
||||||
|
super.deleteById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto {
|
||||||
val material = materialLogic.save(
|
val material = materialLogic.save(
|
||||||
MaterialDto(
|
MaterialDto(
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -39,7 +62,7 @@ class DefaultMixTypeLogic(service: MixTypeService, private val materialLogic: Ma
|
||||||
return save(MixTypeDto(name = name, material = material))
|
return save(MixTypeDto(name = name, material = material))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateForNameAndMaterialType(
|
private fun updateForNameAndMaterialType(
|
||||||
mixType: MixTypeDto,
|
mixType: MixTypeDto,
|
||||||
name: String,
|
name: String,
|
||||||
materialType: MaterialTypeDto
|
materialType: MaterialTypeDto
|
||||||
|
@ -47,12 +70,4 @@ class DefaultMixTypeLogic(service: MixTypeService, private val materialLogic: Ma
|
||||||
val material = materialLogic.update(mixType.material.copy(name = name, materialType = materialType))
|
val material = materialLogic.update(mixType.material.copy(name = name, materialType = materialType))
|
||||||
return update(mixType.copy(name = name, material = material))
|
return update(mixType.copy(name = name, material = material))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteById(id: Long) {
|
|
||||||
if (service.isUsedByMixes(id)) {
|
|
||||||
throw cannotDeleteException("Cannot delete the mix type with the id '$id' because one or more mixes depends on it")
|
|
||||||
}
|
|
||||||
|
|
||||||
super.deleteById(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -72,11 +72,7 @@ class DefaultRecipeLogic(
|
||||||
isApprobationExpired(this),
|
isApprobationExpired(this),
|
||||||
this.remark,
|
this.remark,
|
||||||
this.company,
|
this.company,
|
||||||
this.mixes.map {
|
this.mixes.map { mix(it) }.toSet(),
|
||||||
with(mixLogic) {
|
|
||||||
it.toOutput()
|
|
||||||
}
|
|
||||||
}.toSet(),
|
|
||||||
this.groupsInformation,
|
this.groupsInformation,
|
||||||
recipeImageLogic.getAllImages(this)
|
recipeImageLogic.getAllImages(this)
|
||||||
.map { this.imageUrl(configService.getContent(ConfigurationType.INSTANCE_URL), it) }
|
.map { this.imageUrl(configService.getContent(ConfigurationType.INSTANCE_URL), it) }
|
||||||
|
|
|
@ -2,13 +2,7 @@ package dev.fyloz.colorrecipesexplorer.model
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.Constants
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
|
||||||
|
|
||||||
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "material")
|
@Table(name = "material")
|
||||||
|
@ -36,13 +30,6 @@ data class Material(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MaterialQuantityDto(
|
|
||||||
val material: Long,
|
|
||||||
|
|
||||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
|
||||||
val quantity: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
// === DSL ===
|
// === DSL ===
|
||||||
|
|
||||||
fun material(
|
fun material(
|
||||||
|
@ -71,10 +58,4 @@ fun material(
|
||||||
@Deprecated("Temporary DSL for transition")
|
@Deprecated("Temporary DSL for transition")
|
||||||
fun materialDto(
|
fun materialDto(
|
||||||
entity: Material
|
entity: Material
|
||||||
) = MaterialDto(entity.id!!, entity.name, entity.inventoryQuantity, entity.isMixType, materialTypeDto(entity.materialType!!))
|
) = MaterialDto(entity.id!!, entity.name, entity.inventoryQuantity, entity.isMixType, materialTypeDto(entity.materialType!!))
|
||||||
|
|
||||||
fun materialQuantityDto(
|
|
||||||
materialId: Long,
|
|
||||||
quantity: Float,
|
|
||||||
op: MaterialQuantityDto.() -> Unit = {}
|
|
||||||
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
|
|
@ -1,15 +1,11 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model
|
package dev.fyloz.colorrecipesexplorer.model
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
import dev.fyloz.colorrecipesexplorer.Constants
|
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
|
||||||
import javax.validation.constraints.NotBlank
|
|
||||||
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "mix")
|
@Table(name = "mix")
|
||||||
|
@ -20,7 +16,6 @@ data class Mix(
|
||||||
|
|
||||||
var location: String?,
|
var location: String?,
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "recipe_id")
|
@JoinColumn(name = "recipe_id")
|
||||||
val recipe: Recipe,
|
val recipe: Recipe,
|
||||||
|
@ -31,53 +26,9 @@ data class Mix(
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||||
@JoinColumn(name = "mix_id")
|
@JoinColumn(name = "mix_id")
|
||||||
var mixMaterials: MutableSet<MixMaterial>,
|
var mixMaterials: Set<MixMaterial>,
|
||||||
) : ModelEntity
|
) : ModelEntity
|
||||||
|
|
||||||
open class MixSaveDto(
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
val recipeId: Long,
|
|
||||||
|
|
||||||
val materialTypeId: Long,
|
|
||||||
|
|
||||||
val mixMaterials: Set<MixMaterialDto>?
|
|
||||||
) : EntityDto<Mix>
|
|
||||||
|
|
||||||
open class MixUpdateDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String?,
|
|
||||||
|
|
||||||
val materialTypeId: Long?,
|
|
||||||
|
|
||||||
var mixMaterials: Set<MixMaterialDto>?
|
|
||||||
) : EntityDto<Mix>
|
|
||||||
|
|
||||||
data class MixOutputDto(
|
|
||||||
val id: Long,
|
|
||||||
val location: String?,
|
|
||||||
val mixType: MixType,
|
|
||||||
val mixMaterials: Set<MixMaterialDto>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class MixDeductDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
|
||||||
val ratio: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
data class MixLocationDto(
|
|
||||||
val mixId: Long,
|
|
||||||
|
|
||||||
val location: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
//fun Mix.toOutput() =
|
|
||||||
|
|
||||||
// ==== DSL ====
|
// ==== DSL ====
|
||||||
fun mix(
|
fun mix(
|
||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
|
@ -88,59 +39,12 @@ fun mix(
|
||||||
op: Mix.() -> Unit = {}
|
op: Mix.() -> Unit = {}
|
||||||
) = Mix(id, location, recipe, mixType, mixMaterials).apply(op)
|
) = Mix(id, location, recipe, mixType, mixMaterials).apply(op)
|
||||||
|
|
||||||
fun mixSaveDto(
|
@Deprecated("Temporary DSL for transition")
|
||||||
name: String = "name",
|
fun mix(
|
||||||
recipeId: Long = 0L,
|
dto: MixDto
|
||||||
materialTypeId: Long = 0L,
|
) = Mix(dto.id, dto.location, dto.recipe, mixType(dto.mixType), dto.mixMaterials.map(::mixMaterial).toSet())
|
||||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
|
||||||
op: MixSaveDto.() -> Unit = {}
|
|
||||||
) = MixSaveDto(name, recipeId, materialTypeId, mixMaterials).apply(op)
|
|
||||||
|
|
||||||
fun mixUpdateDto(
|
@Deprecated("Temporary DSL for transition")
|
||||||
id: Long = 0L,
|
fun mix(
|
||||||
name: String? = "name",
|
entity: Mix
|
||||||
materialTypeId: Long? = 0L,
|
) = MixDto(entity.id!!, entity.location, entity.recipe, mixTypeDto(entity.mixType), entity.mixMaterials.map(::mixMaterialDto).toSet())
|
||||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
|
||||||
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)
|
|
||||||
|
|
||||||
fun mixLocationDto(
|
|
||||||
mixId: Long = 0L,
|
|
||||||
location: String? = "location",
|
|
||||||
op: MixLocationDto.() -> Unit = {}
|
|
||||||
) = MixLocationDto(mixId, location).apply(op)
|
|
||||||
|
|
||||||
// ==== Exceptions ====
|
|
||||||
private const val MIX_NOT_FOUND_EXCEPTION_TITLE = "Mix not found"
|
|
||||||
private const val MIX_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix already exists"
|
|
||||||
private const val MIX_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix"
|
|
||||||
private const val MIX_EXCEPTION_ERROR_CODE = "mix"
|
|
||||||
|
|
||||||
fun mixIdNotFoundException(id: Long) =
|
|
||||||
NotFoundException(
|
|
||||||
MIX_EXCEPTION_ERROR_CODE,
|
|
||||||
MIX_NOT_FOUND_EXCEPTION_TITLE,
|
|
||||||
"A mix with the id $id could not be found",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun mixIdAlreadyExistsException(id: Long) =
|
|
||||||
AlreadyExistsException(
|
|
||||||
MIX_EXCEPTION_ERROR_CODE,
|
|
||||||
MIX_ALREADY_EXISTS_EXCEPTION_TITLE,
|
|
||||||
"A mix with the id $id already exists",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
|
|
||||||
fun cannotDeleteMixException(mix: Mix) =
|
|
||||||
CannotDeleteException(
|
|
||||||
MIX_EXCEPTION_ERROR_CODE,
|
|
||||||
MIX_CANNOT_DELETE_EXCEPTION_TITLE,
|
|
||||||
"Cannot delete the mix ${mix.mixType.name} because one or more mixes depends on it"
|
|
||||||
)
|
|
|
@ -2,6 +2,8 @@ package dev.fyloz.colorrecipesexplorer.model
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import dev.fyloz.colorrecipesexplorer.Constants
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixLocationDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||||
|
@ -150,7 +152,7 @@ data class RecipeOutputDto(
|
||||||
val approbationExpired: Boolean?,
|
val approbationExpired: Boolean?,
|
||||||
val remark: String?,
|
val remark: String?,
|
||||||
val company: Company,
|
val company: Company,
|
||||||
val mixes: Set<MixOutputDto>,
|
val mixes: Set<MixDto>,
|
||||||
val groupsInformation: Set<RecipeGroupInformation>,
|
val groupsInformation: Set<RecipeGroupInformation>,
|
||||||
var imagesUrls: Set<String>
|
var imagesUrls: Set<String>
|
||||||
) : ModelEntity
|
) : ModelEntity
|
||||||
|
|
|
@ -7,21 +7,11 @@ import org.springframework.data.jpa.repository.Modifying
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
|
|
||||||
interface MixRepository : JpaRepository<Mix, Long> {
|
interface MixRepository : JpaRepository<Mix, Long> {
|
||||||
/** Finds all mixes with the given [mixType]. */
|
/** Finds all mixes with the mix type with the given [mixTypeId]. */
|
||||||
fun findAllByMixType(mixType: MixType): Collection<Mix>
|
fun findAllByMixTypeId(mixTypeId: Long): Collection<Mix>
|
||||||
|
|
||||||
/** Updates the [location] of the [Mix] with the given [id]. */
|
/** Updates the [location] of the [Mix] with the given [id]. */
|
||||||
@Modifying
|
@Modifying
|
||||||
@Query("UPDATE Mix m SET m.location = :location WHERE m.id = :id")
|
@Query("update Mix m set m.location = :location where m.id = :id")
|
||||||
fun updateLocationById(id: Long, location: String?)
|
fun updateLocationById(id: Long, location: String?)
|
||||||
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
select case when(count(mm.id) > 0) then false else true end
|
|
||||||
from Mix m
|
|
||||||
left join MixMaterial mm on m.mixType.material.id = mm.material.id
|
|
||||||
where m.id = :id
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
fun canBeDeleted(id: Long): Boolean
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.repository
|
package dev.fyloz.colorrecipesexplorer.repository
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MaterialType
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixType
|
import dev.fyloz.colorrecipesexplorer.model.MixType
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
|
@ -24,4 +23,13 @@ interface MixTypeRepository : JpaRepository<MixType, Long> {
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
fun isUsedByMixes(id: Long): Boolean
|
fun isUsedByMixes(id: Long): Boolean
|
||||||
|
|
||||||
|
/** Checks if the mix type with the given [id] is used by more than one mix. */
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
select case when(count(m.id) > 1) then false else true end
|
||||||
|
from Mix m where m.mixType.id = :id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun isShared(id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.InventoryLogic
|
import dev.fyloz.colorrecipesexplorer.logic.InventoryLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MixDeductDto
|
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.context.annotation.Profile
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.PutMapping
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewRecipes
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixSaveDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.MixLogic
|
||||||
|
import org.springframework.context.annotation.Profile
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import javax.validation.Valid
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Constants.ControllerPaths.MIX)
|
||||||
|
@Profile("!emergency")
|
||||||
|
@PreAuthorizeViewRecipes
|
||||||
|
class MixController(private val mixLogic: MixLogic) {
|
||||||
|
@GetMapping("{id}")
|
||||||
|
fun getById(@PathVariable id: Long) =
|
||||||
|
ok(mixLogic.getById(id))
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorizeEditRecipes
|
||||||
|
fun save(@Valid @RequestBody mix: MixSaveDto) =
|
||||||
|
created<MixDto>(Constants.ControllerPaths.MIX) {
|
||||||
|
mixLogic.save(mix)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@PreAuthorizeEditRecipes
|
||||||
|
fun update(@Valid @RequestBody mix: MixSaveDto) =
|
||||||
|
noContent {
|
||||||
|
mixLogic.update(mix)
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
@PreAuthorizeEditRecipes
|
||||||
|
fun deleteById(@PathVariable id: Long) =
|
||||||
|
noContent {
|
||||||
|
mixLogic.deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ import javax.validation.Valid
|
||||||
|
|
||||||
|
|
||||||
private const val RECIPE_CONTROLLER_PATH = "api/recipe"
|
private const val RECIPE_CONTROLLER_PATH = "api/recipe"
|
||||||
private const val MIX_CONTROLLER_PATH = "api/recipe/mix"
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(RECIPE_CONTROLLER_PATH)
|
@RequestMapping(RECIPE_CONTROLLER_PATH)
|
||||||
|
@ -83,34 +82,3 @@ class RecipeController(
|
||||||
recipeImageLogic.delete(recipeLogic.getById(recipeId), name)
|
recipeImageLogic.delete(recipeLogic.getById(recipeId), name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping(MIX_CONTROLLER_PATH)
|
|
||||||
@Profile("!emergency")
|
|
||||||
@PreAuthorizeViewRecipes
|
|
||||||
class MixController(private val mixLogic: MixLogic) {
|
|
||||||
@GetMapping("{id}")
|
|
||||||
fun getById(@PathVariable id: Long) =
|
|
||||||
ok(mixLogic.getByIdForOutput(id))
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
@PreAuthorizeEditRecipes
|
|
||||||
fun save(@Valid @RequestBody mix: MixSaveDto) =
|
|
||||||
created<Mix>(MIX_CONTROLLER_PATH) {
|
|
||||||
mixLogic.save(mix)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping
|
|
||||||
@PreAuthorizeEditRecipes
|
|
||||||
fun update(@Valid @RequestBody mix: MixUpdateDto) =
|
|
||||||
noContent {
|
|
||||||
mixLogic.update(mix)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
|
||||||
@PreAuthorizeEditRecipes
|
|
||||||
fun deleteById(@PathVariable id: Long) =
|
|
||||||
noContent {
|
|
||||||
mixLogic.deleteById(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Mix
|
||||||
|
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
||||||
|
|
||||||
|
interface MixService : Service<MixDto, Mix, MixRepository> {
|
||||||
|
/** Gets all mixes with the mix type with the given [mixTypeId]. */
|
||||||
|
fun getAllByMixTypeId(mixTypeId: Long): Collection<MixDto>
|
||||||
|
|
||||||
|
/** Updates the [location] of the mix with the given [id]. */
|
||||||
|
fun updateLocationById(id: Long, location: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceComponent
|
||||||
|
class DefaultMixService(
|
||||||
|
repository: MixRepository,
|
||||||
|
private val mixTypeService: MixTypeService,
|
||||||
|
private val mixMaterialService: MixMaterialService
|
||||||
|
) : BaseService<MixDto, Mix, MixRepository>(repository), MixService {
|
||||||
|
override fun getAllByMixTypeId(mixTypeId: Long) = repository.findAllByMixTypeId(mixTypeId).map(::toDto)
|
||||||
|
override fun updateLocationById(id: Long, location: String?) = repository.updateLocationById(id, location)
|
||||||
|
|
||||||
|
override fun toDto(entity: Mix) =
|
||||||
|
MixDto(
|
||||||
|
entity.id!!,
|
||||||
|
entity.location,
|
||||||
|
entity.recipe,
|
||||||
|
mixTypeService.toDto(entity.mixType),
|
||||||
|
entity.mixMaterials.map(mixMaterialService::toDto).toSet()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toEntity(dto: MixDto) =
|
||||||
|
Mix(
|
||||||
|
dto.id,
|
||||||
|
dto.location,
|
||||||
|
dto.recipe,
|
||||||
|
mixTypeService.toEntity(dto.mixType),
|
||||||
|
dto.mixMaterials.map(mixMaterialService::toEntity).toSet()
|
||||||
|
)
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ interface MixTypeService : Service<MixTypeDto, MixType, MixTypeRepository> {
|
||||||
|
|
||||||
/** Checks if a mix depends on the mix type with the given [id]. */
|
/** Checks if a mix depends on the mix type with the given [id]. */
|
||||||
fun isUsedByMixes(id: Long): Boolean
|
fun isUsedByMixes(id: Long): Boolean
|
||||||
|
|
||||||
|
/** Checks if the mix type with the given [id] is used by more than one mix. */
|
||||||
|
fun isShared(id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ServiceComponent
|
@ServiceComponent
|
||||||
|
@ -25,8 +28,8 @@ class DefaultMixTypeService(repository: MixTypeRepository, val materialService:
|
||||||
override fun getByNameAndMaterialType(name: String, materialTypeId: Long) =
|
override fun getByNameAndMaterialType(name: String, materialTypeId: Long) =
|
||||||
repository.findByNameAndMaterialType(name, materialTypeId)?.let(::toDto)
|
repository.findByNameAndMaterialType(name, materialTypeId)?.let(::toDto)
|
||||||
|
|
||||||
override fun isUsedByMixes(id: Long) =
|
override fun isUsedByMixes(id: Long) = repository.isUsedByMixes(id)
|
||||||
repository.isUsedByMixes(id)
|
override fun isShared(id: Long) = repository.isShared(id)
|
||||||
|
|
||||||
override fun toDto(entity: MixType) =
|
override fun toDto(entity: MixType) =
|
||||||
MixTypeDto(entity.id!!, entity.name, materialService.toDto(entity.material))
|
MixTypeDto(entity.id!!, entity.name, materialService.toDto(entity.material))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.utils
|
package dev.fyloz.colorrecipesexplorer.utils
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
import dev.fyloz.colorrecipesexplorer.dtos.EntityDto
|
||||||
|
|
||||||
/** Returns a list containing the result of the given [transform] applied to each item of the [Iterable]. If the given [transform] throws, the [Throwable] will be passed to the given [throwableConsumer]. */
|
/** Returns a list containing the result of the given [transform] applied to each item of the [Iterable]. If the given [transform] throws, the [Throwable] will be passed to the given [throwableConsumer]. */
|
||||||
inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
||||||
|
@ -19,26 +19,12 @@ inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find duplicated keys in the given [Iterable], using keys obtained from the given [keySelector]. */
|
|
||||||
inline fun <T, K> Iterable<T>.findDuplicated(keySelector: (T) -> K) =
|
|
||||||
this.groupBy(keySelector)
|
|
||||||
.filter { it.value.count() > 1 }
|
|
||||||
.map { it.key }
|
|
||||||
|
|
||||||
/** Find duplicated elements in the given [Iterable]. */
|
/** Find duplicated elements in the given [Iterable]. */
|
||||||
fun <T> Iterable<T>.findDuplicated() =
|
fun <T> Iterable<T>.findDuplicated() =
|
||||||
this.groupBy { it }
|
this.groupBy { it }
|
||||||
.filter { it.value.count() > 1 }
|
.filter { it.value.count() > 1 }
|
||||||
.map { it.key }
|
.map { it.key }
|
||||||
|
|
||||||
/** Check if the given [Iterable] has gaps between each element, using keys obtained from the given [keySelector]. */
|
|
||||||
inline fun <T> Iterable<T>.hasGaps(keySelector: (T) -> Int) =
|
|
||||||
this.map(keySelector)
|
|
||||||
.toIntArray()
|
|
||||||
.sorted()
|
|
||||||
.filterIndexed { index, it -> it != index + 1 }
|
|
||||||
.isNotEmpty()
|
|
||||||
|
|
||||||
/** Check if the given [Int] [Iterable] has gaps between each element. */
|
/** Check if the given [Int] [Iterable] has gaps between each element. */
|
||||||
fun Iterable<Int>.hasGaps() =
|
fun Iterable<Int>.hasGaps() =
|
||||||
this.sorted()
|
this.sorted()
|
||||||
|
@ -58,8 +44,20 @@ inline fun <T> MutableCollection<T>.excludeAll(predicate: (T) -> Boolean): Itera
|
||||||
return matching
|
return matching
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merge to [ModelEntity] [Iterable]s and prevent id duplication. */
|
/**
|
||||||
fun <T : ModelEntity> Iterable<T>.merge(other: Iterable<T>) =
|
* Merge two [EntityDto] [Iterable]s and prevent duplication of their ids.
|
||||||
this
|
* In case of collision, the items from the [other] iterable will be taken.
|
||||||
.filter { model -> other.all { it.id != model.id } }
|
*/
|
||||||
|
@JvmName("mergeDto")
|
||||||
|
fun <T : EntityDto> Iterable<T>.merge(other: Iterable<T>) =
|
||||||
|
this.merge(other) { it.id }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two [Iterable]s and prevent duplication of the keys determined by the given [keyMapper].
|
||||||
|
* In case of collision, the items from the [other] iterable will be taken.
|
||||||
|
*/
|
||||||
|
fun <T, K> Iterable<T>.merge(other: Iterable<T>, keyMapper: (T) -> K) =
|
||||||
|
this.associateBy { keyMapper(it) }
|
||||||
|
.filter { pair -> other.all { keyMapper(it) != pair.key } }
|
||||||
|
.map { it.value }
|
||||||
.plus(other)
|
.plus(other)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
spring.jpa.show-sql=true
|
|
@ -0,0 +1,247 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Recipe
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.company
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class DefaultInventoryLogicTest {
|
||||||
|
private val materialLogicMock = mockk<MaterialLogic>()
|
||||||
|
private val mixLogicMock = mockk<MixLogic>()
|
||||||
|
|
||||||
|
private val inventoryLogic = spyk(DefaultInventoryLogic(materialLogicMock, mixLogicMock))
|
||||||
|
|
||||||
|
private val defaultInventoryQuantity = 5000f
|
||||||
|
private val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", false)
|
||||||
|
private val material = MaterialDto(1L, "Unit test material", defaultInventoryQuantity, false, materialType)
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_collection_normalBehavior_callsAddSingleForEachQuantity() {
|
||||||
|
// Arrange
|
||||||
|
val quantities = setOf(
|
||||||
|
MaterialQuantityDto(1L, 1000f),
|
||||||
|
MaterialQuantityDto(2L, 2000f)
|
||||||
|
)
|
||||||
|
val expectedQuantities = quantities.map { it.copy(quantity = it.quantity + defaultInventoryQuantity) }
|
||||||
|
|
||||||
|
every { inventoryLogic.add(any<MaterialQuantityDto>()) } answers { expectedQuantities[this.nArgs].quantity }
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inventoryLogic.add(quantities)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
quantities.forEach {
|
||||||
|
inventoryLogic.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_collection_normalBehavior_returnsFromAddSingle() {
|
||||||
|
// Arrange
|
||||||
|
val quantities = setOf(
|
||||||
|
MaterialQuantityDto(1L, 1000f),
|
||||||
|
MaterialQuantityDto(2L, 2000f)
|
||||||
|
)
|
||||||
|
val expectedQuantities = quantities.map { it.copy(quantity = it.quantity + defaultInventoryQuantity) }
|
||||||
|
|
||||||
|
every { inventoryLogic.add(any<MaterialQuantityDto>()) } returnsMany (expectedQuantities.map { it.quantity })
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actualQuantities = inventoryLogic.add(quantities)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(expectedQuantities, actualQuantities)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_single_normalBehavior_callsUpdateQuantityInMaterialLogic() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogicMock.getById(any()) } returns material
|
||||||
|
every { materialLogicMock.updateQuantity(any(), any()) } answers {
|
||||||
|
this.secondArg<Float>() + defaultInventoryQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
val quantity = MaterialQuantityDto(1L, 1000f)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inventoryLogic.add(quantity)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
materialLogicMock.updateQuantity(material, quantity.quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deductMix_normalBehavior_callsDeductWithMixMaterials() {
|
||||||
|
// Arrange
|
||||||
|
val company = CompanyDto(1L, "Unit test company")
|
||||||
|
val recipe = Recipe(
|
||||||
|
1L,
|
||||||
|
"Unit test recipe",
|
||||||
|
"Unit test recipe",
|
||||||
|
"FFFFFF",
|
||||||
|
0xf,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"Remark",
|
||||||
|
company(company),
|
||||||
|
mutableListOf(),
|
||||||
|
setOf()
|
||||||
|
)
|
||||||
|
val mixType = MixTypeDto(1L, "Unit test mix type", material)
|
||||||
|
val mixMaterial = MixMaterialDto(1L, material, 1000f, 1)
|
||||||
|
val mix = MixDto(1L, null, recipe, mixType, setOf(mixMaterial))
|
||||||
|
|
||||||
|
val dto = MixDeductDto(mix.id, 2f)
|
||||||
|
val expectedQuantities = listOf(MaterialQuantityDto(material.id, mixMaterial.quantity * dto.ratio))
|
||||||
|
|
||||||
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
every { inventoryLogic.deduct(any<Collection<MaterialQuantityDto>>()) } returns listOf()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inventoryLogic.deductMix(dto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
inventoryLogic.deduct(expectedQuantities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deductMix_normalBehavior_returnsFromDeduct() {
|
||||||
|
// Arrange
|
||||||
|
val company = CompanyDto(1L, "Unit test company")
|
||||||
|
val recipe = Recipe(
|
||||||
|
1L,
|
||||||
|
"Unit test recipe",
|
||||||
|
"Unit test recipe",
|
||||||
|
"FFFFFF",
|
||||||
|
0xf,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"Remark",
|
||||||
|
company(company),
|
||||||
|
mutableListOf(),
|
||||||
|
setOf()
|
||||||
|
)
|
||||||
|
val mixType = MixTypeDto(1L, "Unit test mix type", material)
|
||||||
|
val mixMaterial = MixMaterialDto(1L, material, 1000f, 1)
|
||||||
|
val mix = MixDto(1L, null, recipe, mixType, setOf(mixMaterial))
|
||||||
|
|
||||||
|
val dto = MixDeductDto(mix.id, 2f)
|
||||||
|
val expectedQuantities = listOf(MaterialQuantityDto(material.id, mixMaterial.quantity * dto.ratio))
|
||||||
|
|
||||||
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
every { inventoryLogic.deduct(any<Collection<MaterialQuantityDto>>()) } returns expectedQuantities
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actualQuantities = inventoryLogic.deductMix(dto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(expectedQuantities, actualQuantities)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deduct_collection_normalBehavior_callsDeductForEachQuantity() {
|
||||||
|
// Arrange
|
||||||
|
val quantities = setOf(
|
||||||
|
MaterialQuantityDto(1L, 1000f),
|
||||||
|
MaterialQuantityDto(2L, 2000f)
|
||||||
|
)
|
||||||
|
|
||||||
|
every { inventoryLogic.deduct(any<MaterialQuantityDto>()) } answers {
|
||||||
|
defaultInventoryQuantity - firstArg<MaterialQuantityDto>().quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inventoryLogic.deduct(quantities)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
quantities.forEach {
|
||||||
|
inventoryLogic.deduct(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deduct_collection_normalBehavior_returnsFromDeduct() {
|
||||||
|
// Arrange
|
||||||
|
val quantities = setOf(
|
||||||
|
MaterialQuantityDto(1L, 1000f),
|
||||||
|
MaterialQuantityDto(2L, 2000f)
|
||||||
|
)
|
||||||
|
|
||||||
|
every { inventoryLogic.deduct(any<MaterialQuantityDto>()) } answers {
|
||||||
|
defaultInventoryQuantity - firstArg<MaterialQuantityDto>().quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
val expectedQuantities =
|
||||||
|
quantities.map { MaterialQuantityDto(it.materialId, defaultInventoryQuantity - it.quantity) }
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actualQuantities = inventoryLogic.deduct(quantities)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(expectedQuantities, actualQuantities)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deduct_collection_notEnoughInventory_throwsMultiplesNotEnoughInventoryException() {
|
||||||
|
// Arrange
|
||||||
|
val quantities = setOf(
|
||||||
|
MaterialQuantityDto(1L, 1000f),
|
||||||
|
MaterialQuantityDto(2L, 2000f)
|
||||||
|
)
|
||||||
|
|
||||||
|
every { inventoryLogic.deduct(any<MaterialQuantityDto>()) } throws NotEnoughInventoryException(1000f, material)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<MultiplesNotEnoughInventoryException> { inventoryLogic.deduct(quantities) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deduct_single_normalBehavior_callsUpdateQuantityInMaterialLogic() {
|
||||||
|
// Arrange
|
||||||
|
val quantity = MaterialQuantityDto(material.id, 1000f)
|
||||||
|
|
||||||
|
every { materialLogicMock.getById(any()) } returns material
|
||||||
|
every { materialLogicMock.updateQuantity(any(), any()) } returns defaultInventoryQuantity - quantity.quantity
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inventoryLogic.deduct(quantity)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
materialLogicMock.getById(quantity.materialId)
|
||||||
|
materialLogicMock.updateQuantity(material, -quantity.quantity)
|
||||||
|
}
|
||||||
|
confirmVerified(materialLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deduct_single_notEnoughInventory_throwsNotEnoughInventoryException() {
|
||||||
|
// Arrange
|
||||||
|
val quantity = MaterialQuantityDto(material.id, material.inventoryQuantity + 1000f)
|
||||||
|
|
||||||
|
every { materialLogicMock.getById(any()) } returns material
|
||||||
|
every { materialLogicMock.updateQuantity(any(), any()) } returns defaultInventoryQuantity - quantity.quantity
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<NotEnoughInventoryException> { inventoryLogic.deduct(quantity) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
import dev.fyloz.colorrecipesexplorer.dtos.*
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.Company
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Recipe
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.mix
|
||||||
import dev.fyloz.colorrecipesexplorer.service.MaterialService
|
import dev.fyloz.colorrecipesexplorer.service.MaterialService
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
@ -50,10 +51,10 @@ class DefaultMaterialLogicTest {
|
||||||
mutableListOf(),
|
mutableListOf(),
|
||||||
setOf()
|
setOf()
|
||||||
)
|
)
|
||||||
private val mix = Mix(
|
private val mix = MixDto(
|
||||||
1L, "location", recipe, mixType = MixType(1L, "Unit test mix type", material(materialMixType)), mutableSetOf()
|
1L, "location", recipe, mixType = MixTypeDto(1L, "Unit test mix type", materialMixType), mutableSetOf()
|
||||||
)
|
)
|
||||||
private val mix2 = mix.copy(id = 2L, mixType = mix.mixType.copy(id = 2L, material = material(materialMixType2)))
|
private val mix2 = mix.copy(id = 2L, mixType = mix.mixType.copy(id = 2L, material = materialMixType2))
|
||||||
|
|
||||||
private val simdutFileMock = MockMultipartFile(
|
private val simdutFileMock = MockMultipartFile(
|
||||||
"Unit test SIMDUT",
|
"Unit test SIMDUT",
|
||||||
|
@ -62,7 +63,7 @@ class DefaultMaterialLogicTest {
|
||||||
private val materialSaveDto = MaterialSaveDto(1L, "Unit test material", 1000f, materialType.id, simdutFileMock)
|
private val materialSaveDto = MaterialSaveDto(1L, "Unit test material", 1000f, materialType.id, simdutFileMock)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
recipe.mixes.addAll(listOf(mix, mix2))
|
recipe.mixes.addAll(listOf(mix(mix), mix(mix2)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
@ -139,7 +140,7 @@ class DefaultMaterialLogicTest {
|
||||||
every { mixLogicMock.getById(any()) } returns mix
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
val materials = materialLogic.getAllForMixUpdate(mix.id)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertContains(materials, material)
|
assertContains(materials, material)
|
||||||
|
@ -152,7 +153,7 @@ class DefaultMaterialLogicTest {
|
||||||
every { mixLogicMock.getById(any()) } returns mix
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
val materials = materialLogic.getAllForMixUpdate(mix.id)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertContains(materials, materialMixType2)
|
assertContains(materials, materialMixType2)
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Company
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Recipe
|
||||||
|
import dev.fyloz.colorrecipesexplorer.service.MixService
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class DefaultMixLogicTest {
|
||||||
|
private val mixServiceMock = mockk<MixService>()
|
||||||
|
private val recipeLogicMock = mockk<RecipeLogic>()
|
||||||
|
private val materialTypeLogicMock = mockk<MaterialTypeLogic>()
|
||||||
|
private val mixTypeLogicMock = mockk<MixTypeLogic>()
|
||||||
|
private val mixMaterialLogicMock = mockk<MixMaterialLogic>()
|
||||||
|
|
||||||
|
private val mixLogic = spyk(
|
||||||
|
DefaultMixLogic(
|
||||||
|
mixServiceMock,
|
||||||
|
recipeLogicMock,
|
||||||
|
materialTypeLogicMock,
|
||||||
|
mixTypeLogicMock,
|
||||||
|
mixMaterialLogicMock
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val company = Company(1L, "Unit test company")
|
||||||
|
private val recipe = Recipe(
|
||||||
|
1L,
|
||||||
|
"Unit test recipe",
|
||||||
|
"Unit test recipe",
|
||||||
|
"FFFFFF",
|
||||||
|
0xf,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"A remark",
|
||||||
|
company,
|
||||||
|
mutableListOf(),
|
||||||
|
setOf()
|
||||||
|
)
|
||||||
|
private val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", false)
|
||||||
|
private val mixType =
|
||||||
|
MixTypeDto(1L, "Unit test mix type", MaterialDto(1L, "Unit test mix type material", 1000f, true, materialType))
|
||||||
|
private val mixMaterial =
|
||||||
|
MixMaterialDto(1L, MaterialDto(2L, "Unit test material", 1000f, false, materialType), 50f, 1)
|
||||||
|
private val mix = MixDto(recipe = recipe, mixType = mixType, mixMaterials = setOf(mixMaterial))
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setup_save_normalBehavior() {
|
||||||
|
every { recipeLogicMock.getById(any()) } returns recipe
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { mixTypeLogicMock.getOrCreateForNameAndMaterialType(any(), any()) } returns mixType
|
||||||
|
every { mixMaterialLogicMock.validateAndSaveAll(any()) } returns listOf(mixMaterial)
|
||||||
|
every { recipeLogicMock.addMix(any(), any()) } returns recipe
|
||||||
|
every { mixLogic.save(any<MixDto>()) } returnsArgument 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setup_update_normalBehavior() {
|
||||||
|
every { recipeLogicMock.getById(any()) } returns recipe
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { mixTypeLogicMock.updateOrCreateForNameAndMaterialType(any(), any(), any()) } returns mixType
|
||||||
|
every { mixMaterialLogicMock.validateAndSaveAll(any()) } returns listOf(mixMaterial)
|
||||||
|
every { mixLogic.getById(any()) } returns mix
|
||||||
|
every { mixLogic.update(any<MixDto>()) } returnsArgument 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_dto_normalBehavior_callsSave() {
|
||||||
|
// Arrange
|
||||||
|
setup_save_normalBehavior()
|
||||||
|
|
||||||
|
val mixMaterialDto =
|
||||||
|
MixMaterialSaveDto(mixMaterial.id, mixMaterial.material.id, mixMaterial.quantity, mixMaterial.position)
|
||||||
|
val saveDto = MixSaveDto(0L, mixType.name, recipe.id!!, materialType.id, setOf(mixMaterialDto))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixLogic.save(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
mixLogic.save(mix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_dto_normalBehavior_callsValidateAndSaveAllInMixMaterialLogic() {
|
||||||
|
// Arrange
|
||||||
|
setup_save_normalBehavior()
|
||||||
|
|
||||||
|
val mixMaterialDtos =
|
||||||
|
setOf(
|
||||||
|
MixMaterialSaveDto(
|
||||||
|
mixMaterial.id,
|
||||||
|
mixMaterial.material.id,
|
||||||
|
mixMaterial.quantity,
|
||||||
|
mixMaterial.position
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val saveDto = MixSaveDto(0L, mixType.name, recipe.id!!, materialType.id, mixMaterialDtos)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixLogic.save(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
mixMaterialLogicMock.validateAndSaveAll(mixMaterialDtos)
|
||||||
|
}
|
||||||
|
confirmVerified(mixMaterialLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_dto_normalBehavior_callsUpdate() {
|
||||||
|
// Arrange
|
||||||
|
setup_update_normalBehavior()
|
||||||
|
|
||||||
|
val mixMaterialDto =
|
||||||
|
MixMaterialSaveDto(mixMaterial.id, mixMaterial.material.id, mixMaterial.quantity, mixMaterial.position)
|
||||||
|
val saveDto = MixSaveDto(mix.id, mixType.name, recipe.id!!, materialType.id, setOf(mixMaterialDto))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixLogic.update(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
mixLogic.update(mix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_dto_normalBehavior_callsValidateAndSaveAllInMixMaterialLogic() {
|
||||||
|
// Arrange
|
||||||
|
setup_update_normalBehavior()
|
||||||
|
|
||||||
|
val mixMaterialDtos = setOf(
|
||||||
|
MixMaterialSaveDto(
|
||||||
|
mixMaterial.id,
|
||||||
|
mixMaterial.material.id,
|
||||||
|
mixMaterial.quantity,
|
||||||
|
mixMaterial.position
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val saveDto = MixSaveDto(mix.id, mixType.name, recipe.id!!, materialType.id, mixMaterialDtos)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixLogic.update(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
mixMaterialLogicMock.validateAndSaveAll(mixMaterialDtos)
|
||||||
|
}
|
||||||
|
confirmVerified(mixMaterialLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateLocations_normalBehavior_callsUpdateLocationByIdInServiceForEachUpdatedLocation() {
|
||||||
|
// Arrange
|
||||||
|
every { mixServiceMock.updateLocationById(any(), any()) } just runs
|
||||||
|
|
||||||
|
val updatedLocations = listOf(
|
||||||
|
MixLocationDto(1L, "A location"),
|
||||||
|
MixLocationDto(2L, "Another location")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixLogic.updateLocations(updatedLocations)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
updatedLocations.forEach {
|
||||||
|
verify {
|
||||||
|
mixServiceMock.updateLocationById(it.mixId, it.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
confirmVerified(mixServiceMock)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,9 @@ import org.junit.jupiter.api.assertThrows
|
||||||
|
|
||||||
class DefaultMixMaterialLogicTest {
|
class DefaultMixMaterialLogicTest {
|
||||||
private val mixMaterialServiceMock = mockk<MixMaterialService>()
|
private val mixMaterialServiceMock = mockk<MixMaterialService>()
|
||||||
|
private val materialLogicMock = mockk<MaterialLogic>()
|
||||||
|
|
||||||
private val mixMaterialLogic = DefaultMixMaterialLogic(mixMaterialServiceMock)
|
private val mixMaterialLogic = DefaultMixMaterialLogic(mixMaterialServiceMock, materialLogicMock)
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
internal fun afterEach() {
|
internal fun afterEach() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import kotlin.math.exp
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class DefaultMixTypeLogicTest {
|
class DefaultMixTypeLogicTest {
|
||||||
|
@ -39,84 +40,105 @@ class DefaultMixTypeLogicTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getOrCreateForNameAndMaterialType_notFound_returnsFromSaveForNameAndMaterialType() {
|
fun getOrCreateForNameAndMaterialType_notFound_callsSave() {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { mixTypeServiceMock.getByNameAndMaterialType(any(), any()) } returns null
|
every { mixTypeServiceMock.getByNameAndMaterialType(any(), any()) } returns null
|
||||||
every { mixTypeLogic.saveForNameAndMaterialType(any(), any()) } returns mixType
|
every { materialLogicMock.save(any<MaterialDto>()) } returns material
|
||||||
|
every { mixTypeLogic.save(any()) } returnsArgument 0
|
||||||
|
|
||||||
|
val expectedMixType = mixType.copy(id = 0L)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
mixTypeLogic.save(expectedMixType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getOrCreateForNameAndMaterialType_notFound_returnsFromSave() {
|
||||||
|
// Arrange
|
||||||
|
every { mixTypeServiceMock.getByNameAndMaterialType(any(), any()) } returns null
|
||||||
|
every { materialLogicMock.save(any<MaterialDto>()) } returns material
|
||||||
|
every { mixTypeLogic.save(any()) } returnsArgument 0
|
||||||
|
|
||||||
|
val expectedMixType = mixType.copy(id = 0L)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actualMixType = mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType)
|
val actualMixType = mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals(mixType, actualMixType)
|
assertEquals(expectedMixType, actualMixType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun saveForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() {
|
fun updateOrCreateForNameAndMaterialType_mixTypeShared_callsSave() {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { materialLogicMock.save(any<MaterialDto>()) } returnsArgument 0
|
every { mixTypeServiceMock.isShared(any()) } returns true
|
||||||
|
every { materialLogicMock.save(any<MaterialDto>()) } returns material
|
||||||
every { mixTypeLogic.save(any()) } returnsArgument 0
|
every { mixTypeLogic.save(any()) } returnsArgument 0
|
||||||
|
|
||||||
|
val expectedMixType = mixType.copy(id = 0L, name = "${mixType.name} updated")
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
mixTypeLogic.saveForNameAndMaterialType(mixType.name, materialType)
|
mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
verify {
|
verify {
|
||||||
materialLogicMock.save(match<MaterialDto> { it.name == mixType.name && it.materialType == materialType })
|
mixTypeLogic.save(expectedMixType)
|
||||||
}
|
}
|
||||||
confirmVerified(materialLogicMock)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun saveForNameAndMaterialType_normalBehavior_callsSave() {
|
fun updateOrCreateForNameAndMaterialType_mixTypeShared_returnsFromSave() {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { materialLogicMock.save(any<MaterialDto>()) } returnsArgument 0
|
every { mixTypeServiceMock.isShared(any()) } returns true
|
||||||
|
every { materialLogicMock.save(any<MaterialDto>()) } returns material
|
||||||
every { mixTypeLogic.save(any()) } returnsArgument 0
|
every { mixTypeLogic.save(any()) } returnsArgument 0
|
||||||
|
|
||||||
|
val expectedMixType = mixType.copy(id = 0L, name = "${mixType.name} updated")
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
mixTypeLogic.saveForNameAndMaterialType(mixType.name, materialType)
|
val actualMixType = mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(expectedMixType, actualMixType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateOrCreateForNameAndMaterialType_mixTypeNotShared_callsUpdate() {
|
||||||
|
// Arrange
|
||||||
|
every { mixTypeServiceMock.isShared(any()) } returns false
|
||||||
|
every { materialLogicMock.update(any<MaterialDto>()) } returns material
|
||||||
|
every { mixTypeLogic.update(any()) } returnsArgument 0
|
||||||
|
|
||||||
|
val expectedMixType = mixType.copy(name = "${mixType.name} updated")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
verify {
|
verify {
|
||||||
mixTypeLogic.save(match { it.name == mixType.name && it.material.name == mixType.name && it.material.materialType == materialType })
|
mixTypeLogic.update(expectedMixType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun updateForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() {
|
fun updateOrCreateForNameAndMaterialType_mixTypeNotShared_returnsFromUpdate() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val updatedName = mixType.name + " updated"
|
every { mixTypeServiceMock.isShared(any()) } returns false
|
||||||
val updatedMaterialType = materialType.copy(id = 2L)
|
every { materialLogicMock.update(any<MaterialDto>()) } returns material
|
||||||
|
|
||||||
every { materialLogicMock.update(any<MaterialDto>()) } returnsArgument 0
|
|
||||||
every { mixTypeLogic.update(any()) } returnsArgument 0
|
every { mixTypeLogic.update(any()) } returnsArgument 0
|
||||||
|
|
||||||
// Act
|
val expectedMixType = mixType.copy(name = "${mixType.name} updated")
|
||||||
mixTypeLogic.updateForNameAndMaterialType(mixType, updatedName, updatedMaterialType)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
verify {
|
|
||||||
materialLogicMock.update(match<MaterialDto> { it.name == updatedName && it.materialType == updatedMaterialType })
|
|
||||||
}
|
|
||||||
confirmVerified(materialLogicMock)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun updateForNameAndMaterialType_normalBehavior_callsSave() {
|
|
||||||
// Arrange
|
|
||||||
val updatedName = mixType.name + " updated"
|
|
||||||
val updatedMaterialType = materialType.copy(id = 2L)
|
|
||||||
|
|
||||||
every { materialLogicMock.update(any<MaterialDto>()) } returnsArgument 0
|
|
||||||
every { mixTypeLogic.update(any()) } returnsArgument 0
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
mixTypeLogic.updateForNameAndMaterialType(mixType, updatedName, updatedMaterialType)
|
val actualMixType = mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
verify {
|
assertEquals(expectedMixType, actualMixType)
|
||||||
mixTypeLogic.update(match { it.name == updatedName && it.material.name == updatedName && it.material.materialType == updatedMaterialType })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class InventoryLogicTest {
|
|
||||||
private val materialLogic: MaterialLogic = mock()
|
|
||||||
private val mixLogic: MixLogic = mock()
|
|
||||||
private val logic = spy(DefaultInventoryLogic(materialLogic, mixLogic))
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
fun afterEach() {
|
|
||||||
reset(materialLogic, logic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `add(materialQuantities) calls add() for each MaterialQuantityDto`() {
|
|
||||||
val materialQuantities = listOf(
|
|
||||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
|
||||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
|
||||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
|
||||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
|
||||||
)
|
|
||||||
val storedQuantity = 2000f
|
|
||||||
|
|
||||||
doAnswer { storedQuantity + (it.arguments[0] as MaterialQuantityDto).quantity }.whenever(logic)
|
|
||||||
.add(any<MaterialQuantityDto>())
|
|
||||||
|
|
||||||
val found = logic.add(materialQuantities)
|
|
||||||
|
|
||||||
materialQuantities.forEach {
|
|
||||||
verify(logic).add(it)
|
|
||||||
assertTrue { found.any { updated -> updated.material == it.material && updated.quantity == storedQuantity + it.quantity } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `add(materialQuantity) updates material's quantity`() {
|
|
||||||
withGivenQuantities(0f, 1000f) {
|
|
||||||
val updatedQuantity = it + this.quantity
|
|
||||||
whenever(materialLogic.updateQuantity(any(), eq(this.quantity))).doReturn(updatedQuantity)
|
|
||||||
|
|
||||||
val found = logic.add(this)
|
|
||||||
|
|
||||||
verify(materialLogic).updateQuantity(
|
|
||||||
argThat { this.id == this@withGivenQuantities.material },
|
|
||||||
eq(this.quantity)
|
|
||||||
)
|
|
||||||
assertEquals(updatedQuantity, found)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = mutableSetOf(
|
|
||||||
mixMaterial(id = 0L, material = material, quantity = 1000f, position = 0),
|
|
||||||
mixMaterial(id = 1L, material = materialPercents, quantity = 50f, position = 1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val expectedQuantities = mapOf(
|
|
||||||
0L to 1500f,
|
|
||||||
1L to 750f
|
|
||||||
)
|
|
||||||
|
|
||||||
whenever(mixLogic.getById(mix.id!!)).doReturn(mix)
|
|
||||||
doAnswer {
|
|
||||||
(it.arguments[0] as Collection<MaterialQuantityDto>).map { materialQuantity ->
|
|
||||||
materialQuantityDto(materialId = materialQuantity.material, quantity = 0f)
|
|
||||||
}
|
|
||||||
}.whenever(logic).deduct(any<Collection<MaterialQuantityDto>>())
|
|
||||||
|
|
||||||
val found = logic.deductMix(mixRatio)
|
|
||||||
|
|
||||||
verify(logic).deduct(argThat<Collection<MaterialQuantityDto>> {
|
|
||||||
this.all { it.quantity == expectedQuantities[it.material] }
|
|
||||||
})
|
|
||||||
|
|
||||||
assertEquals(expectedQuantities.size, found.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deduct()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `deduct(materialQuantities) calls deduct() for each MaterialQuantityDto`() {
|
|
||||||
val materialQuantities = listOf(
|
|
||||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
|
||||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
|
||||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
|
||||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
|
||||||
)
|
|
||||||
val storedQuantity = 5000f
|
|
||||||
|
|
||||||
doAnswer { storedQuantity - (it.arguments[0] as MaterialQuantityDto).quantity }.whenever(logic)
|
|
||||||
.deduct(any<MaterialQuantityDto>())
|
|
||||||
|
|
||||||
val found = logic.deduct(materialQuantities)
|
|
||||||
|
|
||||||
materialQuantities.forEach {
|
|
||||||
verify(logic).deduct(it)
|
|
||||||
assertTrue { found.any { updated -> updated.material == it.material && updated.quantity == storedQuantity - it.quantity } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `deduct(materialQuantities) throws MultiplesNotEnoughInventoryException when there is not enough inventory of a given material`() {
|
|
||||||
val materialQuantities = listOf(
|
|
||||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
|
||||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
|
||||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
|
||||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
|
||||||
)
|
|
||||||
val inventoryQuantity = 3000f
|
|
||||||
|
|
||||||
materialQuantities.forEach {
|
|
||||||
withGivenQuantities(inventoryQuantity, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThrows<MultiplesNotEnoughInventoryException> { logic.deduct(materialQuantities) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `deduct(materialQuantity) updates material's quantity`() {
|
|
||||||
withGivenQuantities(5000f, 1000f) {
|
|
||||||
val updatedQuantity = it - this.quantity
|
|
||||||
whenever(materialLogic.updateQuantity(any(), eq(-this.quantity))).doReturn(updatedQuantity)
|
|
||||||
|
|
||||||
val found = logic.deduct(this)
|
|
||||||
|
|
||||||
verify(materialLogic).updateQuantity(
|
|
||||||
argThat { this.id == this@withGivenQuantities.material },
|
|
||||||
eq(-this.quantity)
|
|
||||||
)
|
|
||||||
assertEquals(updatedQuantity, found)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `deduct(materialQuantity) throws NotEnoughInventoryException when there is not enough inventory of the given material`() {
|
|
||||||
withGivenQuantities(0f, 1000f) {
|
|
||||||
assertThrows<NotEnoughInventoryException> { logic.deduct(this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun withGivenQuantities(
|
|
||||||
stored: Float,
|
|
||||||
quantity: Float,
|
|
||||||
materialId: Long = 0L,
|
|
||||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
|
||||||
) {
|
|
||||||
val materialQuantity = materialQuantityDto(materialId = materialId, quantity = quantity)
|
|
||||||
|
|
||||||
withGivenQuantities(stored, materialQuantity, test)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun withGivenQuantities(
|
|
||||||
stored: Float,
|
|
||||||
materialQuantity: MaterialQuantityDto,
|
|
||||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
|
||||||
) {
|
|
||||||
val material = material(id = materialQuantity.material, inventoryQuantity = stored)
|
|
||||||
|
|
||||||
whenever(materialLogic.getById(material.id!!)).doReturn(materialDto(material))
|
|
||||||
|
|
||||||
materialQuantity.test(stored)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
//class MixLogicTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixLogic, MixRepository>() {
|
|
||||||
// override val repository: MixRepository = mock()
|
|
||||||
// private val recipeService: RecipeLogic = mock()
|
|
||||||
// private val materialTypeService: MaterialTypeLogic = mock()
|
|
||||||
// private val mixMaterialService: MixMaterialLogic = mock()
|
|
||||||
// private val mixTypeService: MixTypeLogic = mock()
|
|
||||||
// override val logic: MixLogic =
|
|
||||||
// spy(DefaultMixLogic(repository, recipeService, materialTypeService, mixMaterialService, mixTypeService))
|
|
||||||
//
|
|
||||||
// override val entity: Mix = mix(id = 0L, location = "location")
|
|
||||||
// override val anotherEntity: Mix = mix(id = 1L)
|
|
||||||
// override val entitySaveDto: MixSaveDto = spy(mixSaveDto(mixMaterials = setOf<MixMaterialDto>()))
|
|
||||||
// override val entityUpdateDto: MixUpdateDto = spy(mixUpdateDto(id = entity.id!!))
|
|
||||||
//
|
|
||||||
// @AfterEach
|
|
||||||
// override fun afterEach() {
|
|
||||||
// super.afterEach()
|
|
||||||
// reset(recipeService, materialTypeService, mixMaterialService, mixTypeService)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // getAllByMixType()
|
|
||||||
//
|
|
||||||
//// @Test
|
|
||||||
//// fun `getAllByMixType() returns all mixes with the given mix type`() {
|
|
||||||
//// val mixType = mixType(id = 0L)
|
|
||||||
////
|
|
||||||
//// whenever(repository.findAllByMixType(mixType)).doReturn(entityList)
|
|
||||||
////
|
|
||||||
//// val found = logic.getAllByMixType(mixType)
|
|
||||||
////
|
|
||||||
//// assertEquals(entityList, found)
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // save()
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// override fun `save(dto) calls and returns save() with the created entity`() {
|
|
||||||
//// val recipe = recipe(id = entitySaveDto.recipeId)
|
|
||||||
//// val materialType = materialType(id = entitySaveDto.materialTypeId)
|
|
||||||
//// val material = material(
|
|
||||||
//// name = entitySaveDto.name,
|
|
||||||
//// inventoryQuantity = Float.MIN_VALUE,
|
|
||||||
//// isMixType = true,
|
|
||||||
//// materialType = materialType
|
|
||||||
//// )
|
|
||||||
//// val mixType = mixType(name = entitySaveDto.name, material = material)
|
|
||||||
//// val mix = mix(recipe = recipe, mixType = mixType)
|
|
||||||
//// val mixWithId = mix(id = 0L, recipe = recipe, mixType = mixType)
|
|
||||||
//// val mixMaterials = setOf(mixMaterial(material = material(id = 1L), quantity = 1000f))
|
|
||||||
////
|
|
||||||
//// whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
|
|
||||||
//// whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
|
||||||
//// whenever(mixMaterialService.create(entitySaveDto.mixMaterials!!)).doReturn(mixMaterials)
|
|
||||||
//// whenever(
|
|
||||||
//// mixTypeService.getOrCreateForNameAndMaterialType(
|
|
||||||
//// mixType.name,
|
|
||||||
//// mixType.material.materialType!!
|
|
||||||
//// )
|
|
||||||
//// ).doReturn(mixType)
|
|
||||||
//// doReturn(true).whenever(logic).existsById(mixWithId.id!!)
|
|
||||||
//// doReturn(mixWithId).whenever(logic).save(any<Mix>())
|
|
||||||
////
|
|
||||||
//// val found = logic.save(entitySaveDto)
|
|
||||||
////
|
|
||||||
//// verify(logic).save(argThat<Mix> { this.recipe == mix.recipe })
|
|
||||||
//// verify(recipeService).addMix(recipe, mixWithId)
|
|
||||||
////
|
|
||||||
//// // Verify if this method is called instead of the MixType's constructor, which does not check if the name is already taken by a material.
|
|
||||||
//// verify(mixTypeService).getOrCreateForNameAndMaterialType(mixType.name, mixType.material.materialType!!)
|
|
||||||
////
|
|
||||||
//// assertEquals(mixWithId, found)
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // update()
|
|
||||||
////
|
|
||||||
//// private fun mixUpdateDtoTest(
|
|
||||||
//// scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
|
||||||
//// sharedMixType: Boolean = false,
|
|
||||||
//// op: MixUpdateDtoTestScope.() -> Unit
|
|
||||||
//// ) {
|
|
||||||
//// with(scope) {
|
|
||||||
//// doReturn(true).whenever(logic).existsById(mix.id!!)
|
|
||||||
//// doReturn(mix).whenever(logic).getById(mix.id!!)
|
|
||||||
//// doReturn(sharedMixType).whenever(logic).mixTypeIsShared(mix.mixType)
|
|
||||||
//// doAnswer { it.arguments[0] }.whenever(logic).update(any<Mix>())
|
|
||||||
////
|
|
||||||
//// if (mixUpdateDto.materialTypeId != null) {
|
|
||||||
//// whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// op()
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// private fun mixUpdateDtoMixTypeTest(sharedMixType: Boolean = false, op: MixUpdateDtoTestScope.() -> Unit) {
|
|
||||||
//// with(MixUpdateDtoTestScope(mixUpdateDto = mixUpdateDto(id = 0L, name = "name", materialTypeId = 0L))) {
|
|
||||||
//// mixUpdateDtoTest(this, sharedMixType, op)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// override fun `update(dto) calls and returns update() with the created entity`() {
|
|
||||||
//// val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = null, materialTypeId = null))
|
|
||||||
////
|
|
||||||
//// doReturn(entity).whenever(logic).getById(any())
|
|
||||||
//// doReturn(entity).whenever(logic).update(entity)
|
|
||||||
////
|
|
||||||
//// val found = logic.update(mixUpdateDto)
|
|
||||||
////
|
|
||||||
//// verify(logic).update(entity)
|
|
||||||
////
|
|
||||||
//// assertEquals(entity, found)
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// fun `update(dto) calls MixTypeService saveForNameAndMaterialType() when mix type is shared`() {
|
|
||||||
//// mixUpdateDtoMixTypeTest(sharedMixType = true) {
|
|
||||||
//// whenever(mixTypeService.saveForNameAndMaterialType(mixUpdateDto.name!!, materialType))
|
|
||||||
//// .doReturn(newMixType)
|
|
||||||
////
|
|
||||||
//// val found = logic.update(mixUpdateDto)
|
|
||||||
////
|
|
||||||
//// verify(mixTypeService).saveForNameAndMaterialType(mixUpdateDto.name!!, materialType)
|
|
||||||
////
|
|
||||||
//// assertEquals(newMixType, found.mixType)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// fun `update(dto) calls MixTypeService updateForNameAndMaterialType() when mix type is not shared`() {
|
|
||||||
//// mixUpdateDtoMixTypeTest {
|
|
||||||
//// whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType))
|
|
||||||
//// .doReturn(newMixType)
|
|
||||||
////
|
|
||||||
//// val found = logic.update(mixUpdateDto)
|
|
||||||
////
|
|
||||||
//// verify(mixTypeService).updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)
|
|
||||||
////
|
|
||||||
//// assertEquals(newMixType, found.mixType)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// fun `update(dto) update, create and delete mix materials according to the given mix materials map`() {
|
|
||||||
//// mixUpdateDtoTest {
|
|
||||||
//// val mixMaterials = setOf(
|
|
||||||
//// mixMaterialDto(materialId = 0L, quantity = 100f, position = 0),
|
|
||||||
//// mixMaterialDto(materialId = 1L, quantity = 200f, position = 1),
|
|
||||||
//// mixMaterialDto(materialId = 2L, quantity = 300f, position = 2),
|
|
||||||
//// mixMaterialDto(materialId = 3L, quantity = 400f, position = 3),
|
|
||||||
//// )
|
|
||||||
//// mixUpdateDto.mixMaterials = mixMaterials
|
|
||||||
////
|
|
||||||
//// whenever(mixMaterialService.create(any<Set<MixMaterialDto>>())).doAnswer {
|
|
||||||
//// (it.arguments[0] as Set<MixMaterialDto>).map { dto ->
|
|
||||||
//// mixMaterial(
|
|
||||||
//// material = material(id = dto.materialId),
|
|
||||||
//// quantity = dto.quantity,
|
|
||||||
//// position = dto.position
|
|
||||||
//// )
|
|
||||||
//// }.toSet()
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// val found = logic.update(mixUpdateDto)
|
|
||||||
////
|
|
||||||
//// mixMaterials.forEach {
|
|
||||||
//// assertTrue {
|
|
||||||
//// found.mixMaterials.any { mixMaterial ->
|
|
||||||
//// mixMaterial.material.id == it.materialId && mixMaterial.quantity == it.quantity && mixMaterial.position == it.position
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // updateLocations()
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// fun `updateLocations() calls updateLocation() for each given MixLocationDto`() {
|
|
||||||
//// val locations = setOf(
|
|
||||||
//// mixLocationDto(mixId = 0, location = "Loc 0"),
|
|
||||||
//// mixLocationDto(mixId = 1, location = "Loc 1"),
|
|
||||||
//// mixLocationDto(mixId = 2, location = "Loc 2"),
|
|
||||||
//// mixLocationDto(mixId = 3, location = "Loc 3")
|
|
||||||
//// )
|
|
||||||
////
|
|
||||||
//// logic.updateLocations(locations)
|
|
||||||
////
|
|
||||||
//// locations.forEach {
|
|
||||||
//// verify(logic).updateLocation(it)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // updateLocation()
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// fun `updateLocation() updates the location of a mix in the repository according to the given MixLocationDto`() {
|
|
||||||
//// val locationDto = mixLocationDto(mixId = 0L, location = "Location")
|
|
||||||
////
|
|
||||||
//// logic.updateLocation(locationDto)
|
|
||||||
////
|
|
||||||
//// verify(repository).updateLocationById(locationDto.mixId, locationDto.location)
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // delete()
|
|
||||||
////
|
|
||||||
//// override fun `delete() deletes in the repository`() {
|
|
||||||
//// whenCanBeDeleted {
|
|
||||||
//// super.`delete() deletes in the repository`()
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// // deleteById()
|
|
||||||
////
|
|
||||||
//// @Test
|
|
||||||
//// override fun `deleteById() deletes the entity with the given id in the repository`() {
|
|
||||||
//// whenCanBeDeleted {
|
|
||||||
//// super.`deleteById() deletes the entity with the given id in the repository`()
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
|
||||||
//// whenever(repository.canBeDeleted(id)).doReturn(true)
|
|
||||||
////
|
|
||||||
//// test()
|
|
||||||
//// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//data class MixUpdateDtoTestScope(
|
|
||||||
// val mixType: MixType = mixType(name = "mix type"),
|
|
||||||
// val newMixType: MixType = mixType(name = "another mix type"),
|
|
||||||
// val materialType: MaterialType = materialType(id = 0L),
|
|
||||||
// val mix: Mix = mix(id = 0L, mixType = mixType),
|
|
||||||
// val mixUpdateDto: MixUpdateDto = spy(
|
|
||||||
// mixUpdateDto(
|
|
||||||
// id = 0L,
|
|
||||||
// name = null,
|
|
||||||
// materialTypeId = null,
|
|
||||||
// mixMaterials = setOf()
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//)
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MixLocationDto
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.CachedFile
|
import dev.fyloz.colorrecipesexplorer.logic.files.CachedFile
|
||||||
|
@ -228,8 +229,8 @@ class RecipeLogicTest :
|
||||||
fun `updatePublicData() update the location of a recipe mixes in the mix logic according to the RecipePublicDataDto`() {
|
fun `updatePublicData() update the location of a recipe mixes in the mix logic according to the RecipePublicDataDto`() {
|
||||||
val publicData = recipePublicDataDto(
|
val publicData = recipePublicDataDto(
|
||||||
mixesLocation = setOf(
|
mixesLocation = setOf(
|
||||||
mixLocationDto(mixId = 0L, location = "Loc 1"),
|
MixLocationDto(mixId = 0L, location = "Loc 1"),
|
||||||
mixLocationDto(mixId = 1L, location = "Loc 2")
|
MixLocationDto(mixId = 1L, location = "Loc 2")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue