feature/#25-dtos #28
|
@ -5,6 +5,7 @@ object Constants {
|
|||
const val FILE = "/api/file"
|
||||
const val MATERIAL = "/api/material"
|
||||
const val MATERIAL_TYPE = "/api/materialtype"
|
||||
const val MIX = "/api/recipe/mix"
|
||||
}
|
||||
|
||||
object FilePaths {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package dev.fyloz.colorrecipesexplorer.config.initializers
|
||||
|
||||
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.model.Mix
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.utils.merge
|
||||
import mu.KotlinLogging
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
@ -31,12 +31,12 @@ class MixInitializer(
|
|||
logger.debug("Mix materials positions are valid!")
|
||||
}
|
||||
|
||||
private fun fixMixPositions(mix: Mix) {
|
||||
private fun fixMixPositions(mix: MixDto) {
|
||||
val maxPosition = mix.mixMaterials.maxOf { it.position }
|
||||
|
||||
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) {
|
||||
orderMixMaterials(this)
|
||||
} else {
|
||||
|
@ -52,16 +52,16 @@ class MixInitializer(
|
|||
}
|
||||
}
|
||||
|
||||
private fun increaseMixMaterialsPosition(mixMaterials: Iterable<MixMaterial>, firstPosition: Int) =
|
||||
private fun increaseMixMaterialsPosition(mixMaterials: Iterable<MixMaterialDto>, firstPosition: Int) =
|
||||
mixMaterials
|
||||
.mapIndexed { index, mixMaterial -> mixMaterial.copy(position = firstPosition + index) }
|
||||
.onEach {
|
||||
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 {
|
||||
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
|
||||
val pop = this.pop()
|
||||
this.add(pop)
|
||||
|
|
|
@ -36,7 +36,7 @@ class RecipeInitializer(
|
|||
.filter { groupInfo -> groupInfo.steps!!.any { it.position == 0 } }
|
||||
.map { fixGroupInformationPositions(recipe, it) }
|
||||
|
||||
val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation)
|
||||
val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation) { it.id }
|
||||
|
||||
with(recipe.copy(groupsInformation = updatedGroupInformation.toMutableSet())) {
|
||||
recipeLogic.update(this)
|
||||
|
@ -54,7 +54,7 @@ class RecipeInitializer(
|
|||
|
||||
val invalidRecipeSteps = steps.filter { it.position == 0 }
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -32,3 +32,10 @@ data class MaterialSaveDto(
|
|||
|
||||
val simdutFile: MultipartFile?
|
||||
) : 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.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.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
|
@ -34,83 +37,87 @@ class DefaultInventoryLogic(
|
|||
) : InventoryLogic {
|
||||
@Transactional
|
||||
override fun add(materialQuantities: Collection<MaterialQuantityDto>) =
|
||||
materialQuantities.map {
|
||||
materialQuantityDto(materialId = it.material, quantity = add(it))
|
||||
}
|
||||
materialQuantities.map { MaterialQuantityDto(it.materialId, add(it)) }
|
||||
|
||||
override fun add(materialQuantity: MaterialQuantityDto) =
|
||||
materialLogic.updateQuantity(
|
||||
materialLogic.getById(materialQuantity.material),
|
||||
materialQuantity.quantity
|
||||
)
|
||||
materialLogic.updateQuantity(
|
||||
materialLogic.getById(materialQuantity.materialId),
|
||||
materialQuantity.quantity
|
||||
)
|
||||
|
||||
@Transactional
|
||||
override fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto> {
|
||||
val mix = mixLogic.getById(mixRatio.id)
|
||||
val firstMixMaterial = mix.mixMaterials.first()
|
||||
val adjustedFirstMaterialQuantity = firstMixMaterial.quantity * mixRatio.ratio
|
||||
|
||||
fun adjustQuantity(mixMaterial: MixMaterial): Float =
|
||||
if (!mixMaterial.material.materialType!!.usePercentages)
|
||||
mixMaterial.quantity * mixRatio.ratio
|
||||
else
|
||||
(mixMaterial.quantity * adjustedFirstMaterialQuantity) / 100f
|
||||
|
||||
return deduct(mix.mixMaterials.map {
|
||||
materialQuantityDto(
|
||||
materialId = it.material.id!!,
|
||||
quantity = adjustQuantity(it)
|
||||
)
|
||||
})
|
||||
return deduct(getMaterialsWithAdjustedQuantities(mix.mixMaterials, mixRatio))
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
|
||||
val thrown = mutableListOf<NotEnoughInventoryException>()
|
||||
|
||||
val updatedQuantities =
|
||||
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
|
||||
{ thrown.add(it) }
|
||||
) {
|
||||
materialQuantityDto(materialId = it.material, quantity = deduct(it))
|
||||
}
|
||||
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
|
||||
{ thrown.add(it) }
|
||||
) {
|
||||
MaterialQuantityDto(it.materialId, deduct(it))
|
||||
}
|
||||
|
||||
if (thrown.isNotEmpty()) {
|
||||
throw MultiplesNotEnoughInventoryException(thrown)
|
||||
}
|
||||
|
||||
return updatedQuantities
|
||||
}
|
||||
|
||||
override fun deduct(materialQuantity: MaterialQuantityDto): Float =
|
||||
with(materialLogic.getById(materialQuantity.material)) {
|
||||
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
||||
materialLogic.updateQuantity(this, -materialQuantity.quantity)
|
||||
} else {
|
||||
throw NotEnoughInventoryException(materialQuantity.quantity, this)
|
||||
}
|
||||
}
|
||||
with(materialLogic.getById(materialQuantity.materialId)) {
|
||||
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
||||
materialLogic.updateQuantity(this, -materialQuantity.quantity)
|
||||
} else {
|
||||
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) :
|
||||
RestException(
|
||||
"notenoughinventory",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
|
||||
mapOf(
|
||||
"material" to material.name,
|
||||
"materialId" to material.id.toString(),
|
||||
"requestQuantity" to quantity,
|
||||
"availableQuantity" to material.inventoryQuantity
|
||||
RestException(
|
||||
"notenoughinventory",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
|
||||
mapOf(
|
||||
"material" to material.name,
|
||||
"materialId" to material.id.toString(),
|
||||
"requestQuantity" to quantity,
|
||||
"availableQuantity" to material.inventoryQuantity
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
class MultiplesNotEnoughInventoryException(exceptions: List<NotEnoughInventoryException>) :
|
||||
RestException(
|
||||
"notenoughinventory-multiple",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct requested quantities because there is no enough of them in inventory",
|
||||
mapOf(
|
||||
"lowQuantities" to exceptions.map { it.extensions }
|
||||
RestException(
|
||||
"notenoughinventory-multiple",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct requested quantities because there is no enough of them in inventory",
|
||||
mapOf(
|
||||
"lowQuantities" to exceptions.map { it.extensions }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,102 +1,66 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
||||
import dev.fyloz.colorrecipesexplorer.utils.setAll
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixLocationDto
|
||||
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.stereotype.Service
|
||||
import javax.transaction.Transactional
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
interface MixLogic : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository> {
|
||||
/** Gets all mixes with the given [mixType]. */
|
||||
fun getAllByMixType(mixType: MixType): Collection<Mix>
|
||||
interface MixLogic : Logic<MixDto, MixService> {
|
||||
/** Saves the given [dto]. */
|
||||
fun save(dto: MixSaveDto): MixDto
|
||||
|
||||
/** Checks if a [MixType] is shared by several [Mix]es or not. */
|
||||
fun mixTypeIsShared(mixType: MixType): Boolean
|
||||
/** Updates the given [dto]. */
|
||||
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>)
|
||||
|
||||
/** Updates the location of a given [Mix] to the given [MixLocationDto]. */
|
||||
fun updateLocation(updatedLocation: MixLocationDto)
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequireDatabase
|
||||
@LogicComponent
|
||||
class DefaultMixLogic(
|
||||
mixRepository: MixRepository,
|
||||
@Lazy val recipeLogic: RecipeLogic,
|
||||
@Lazy val materialTypeLogic: MaterialTypeLogic,
|
||||
val mixMaterialLogic: MixMaterialLogic,
|
||||
val mixTypeLogic: MixTypeLogic
|
||||
) : AbstractExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository>(mixRepository),
|
||||
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()
|
||||
)
|
||||
|
||||
service: MixService,
|
||||
@Lazy private val recipeLogic: RecipeLogic,
|
||||
@Lazy private val materialTypeLogic: MaterialTypeLogic,
|
||||
private val mixTypeLogic: MixTypeLogic,
|
||||
private val mixMaterialLogic: MixMaterialLogic
|
||||
) : BaseLogic<MixDto, MixService>(service, Mix::class.simpleName!!), MixLogic {
|
||||
@Transactional
|
||||
override fun save(entity: MixSaveDto): Mix {
|
||||
val recipe = recipeLogic.getById(entity.recipeId)
|
||||
val materialType = materialTypeLogic.getById(entity.materialTypeId)
|
||||
val mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(entity.name, materialType)
|
||||
override fun save(dto: MixSaveDto): MixDto {
|
||||
val recipe = recipeLogic.getById(dto.recipeId)
|
||||
val materialType = materialTypeLogic.getById(dto.materialTypeId)
|
||||
|
||||
val mixMaterials =
|
||||
if (entity.mixMaterials != null) mixMaterialLogic.saveAll(entity.mixMaterials).toSet() else setOf()
|
||||
mixMaterialLogic.validateMixMaterials(mixMaterials)
|
||||
val mix = MixDto(
|
||||
recipe = recipe,
|
||||
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())
|
||||
mix = save(mix)
|
||||
|
||||
recipeLogic.addMix(recipe, mix)
|
||||
|
||||
return mix
|
||||
return save(mix)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun update(entity: MixUpdateDto): Mix {
|
||||
val mix = getById(entity.id)
|
||||
if (entity.name != null || entity.materialTypeId != null) {
|
||||
val name = entity.name ?: mix.mixType.name
|
||||
val materialType = if (entity.materialTypeId != null)
|
||||
materialType(materialTypeLogic.getById(entity.materialTypeId))
|
||||
else
|
||||
mix.mixType.material.materialType!!
|
||||
override fun update(dto: MixSaveDto): MixDto {
|
||||
val materialType = materialTypeLogic.getById(dto.materialTypeId)
|
||||
val mix = getById(dto.id)
|
||||
|
||||
mix.mixType = if (mixTypeIsShared(mix.mixType)) {
|
||||
mixType(mixTypeLogic.saveForNameAndMaterialType(name, materialTypeDto(materialType)))
|
||||
} else {
|
||||
mixType(mixTypeLogic.updateForNameAndMaterialType(mixTypeDto(mix.mixType), name, materialTypeDto(materialType)))
|
||||
}
|
||||
}
|
||||
if (entity.mixMaterials != null) {
|
||||
mix.mixMaterials.setAll(mixMaterialLogic.saveAll(entity.mixMaterials!!).map(::mixMaterial).toMutableSet())
|
||||
}
|
||||
return update(mix)
|
||||
return update(
|
||||
MixDto(
|
||||
id = dto.id,
|
||||
recipe = recipeLogic.getById(dto.recipeId),
|
||||
mixType = mixTypeLogic.updateOrCreateForNameAndMaterialType(mix.mixType, dto.name, materialType),
|
||||
mixMaterials = mixMaterialLogic.validateAndSaveAll(dto.mixMaterials).toSet()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateLocations(updatedLocations: Collection<MixLocationDto>) {
|
||||
override fun updateLocations(updatedLocations: Collection<MixLocationDto>) =
|
||||
updatedLocations.forEach(::updateLocation)
|
||||
}
|
||||
|
||||
override fun updateLocation(updatedLocation: MixLocationDto) {
|
||||
repository.updateLocationById(updatedLocation.mixId, updatedLocation.location)
|
||||
private fun updateLocation(updatedLocation: MixLocationDto) {
|
||||
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.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialSaveDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.service.MixMaterialService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
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.
|
||||
*/
|
||||
fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>)
|
||||
|
||||
/** Validates the given mix materials [dtos] and save them. */
|
||||
fun validateAndSaveAll(dtos: Collection<MixMaterialSaveDto>): Collection<MixMaterialDto>
|
||||
}
|
||||
|
||||
@LogicComponent
|
||||
class DefaultMixMaterialLogic(service: MixMaterialService) :
|
||||
class DefaultMixMaterialLogic(service: MixMaterialService, @Lazy private val materialLogic: MaterialLogic) :
|
||||
BaseLogic<MixMaterialDto, MixMaterialService>(service, MixMaterial::class.simpleName!!), MixMaterialLogic {
|
||||
override fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>) {
|
||||
if (mixMaterials.isEmpty()) return
|
||||
|
@ -37,6 +42,21 @@ class DefaultMixMaterialLogic(service: MixMaterialService) :
|
|||
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
|
||||
|
|
|
@ -6,27 +6,50 @@ import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto
|
|||
import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixType
|
||||
import dev.fyloz.colorrecipesexplorer.service.MixTypeService
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
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
|
||||
|
||||
/** Returns a new and persisted [MixType] with the given [name] and [materialType]. */
|
||||
fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto
|
||||
|
||||
/** Returns the given [mixType] updated with the given [name] and [materialType]. */
|
||||
fun updateForNameAndMaterialType(mixType: MixTypeDto, name: String, materialType: MaterialTypeDto): MixTypeDto
|
||||
/** Updates the [mixType] with the given [name] and [materialType], or create a new one if it is shared with other mixes. */
|
||||
fun updateOrCreateForNameAndMaterialType(
|
||||
mixType: MixTypeDto,
|
||||
name: String,
|
||||
materialType: MaterialTypeDto
|
||||
): MixTypeDto
|
||||
}
|
||||
|
||||
@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 {
|
||||
@Transactional
|
||||
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto) =
|
||||
service.getByNameAndMaterialType(name, materialType.id) ?: saveForNameAndMaterialType(name, materialType)
|
||||
|
||||
@Transactional
|
||||
override fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto {
|
||||
override fun updateOrCreateForNameAndMaterialType(
|
||||
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(
|
||||
MaterialDto(
|
||||
name = name,
|
||||
|
@ -39,7 +62,7 @@ class DefaultMixTypeLogic(service: MixTypeService, private val materialLogic: Ma
|
|||
return save(MixTypeDto(name = name, material = material))
|
||||
}
|
||||
|
||||
override fun updateForNameAndMaterialType(
|
||||
private fun updateForNameAndMaterialType(
|
||||
mixType: MixTypeDto,
|
||||
name: String,
|
||||
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))
|
||||
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),
|
||||
this.remark,
|
||||
this.company,
|
||||
this.mixes.map {
|
||||
with(mixLogic) {
|
||||
it.toOutput()
|
||||
}
|
||||
}.toSet(),
|
||||
this.mixes.map { mix(it) }.toSet(),
|
||||
this.groupsInformation,
|
||||
recipeImageLogic.getAllImages(this)
|
||||
.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.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.validation.constraints.Min
|
||||
|
||||
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
||||
|
||||
@Entity
|
||||
@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 ===
|
||||
|
||||
fun material(
|
||||
|
@ -71,10 +58,4 @@ fun material(
|
|||
@Deprecated("Temporary DSL for transition")
|
||||
fun materialDto(
|
||||
entity: Material
|
||||
) = 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)
|
||||
) = MaterialDto(entity.id!!, entity.name, entity.inventoryQuantity, entity.isMixType, materialTypeDto(entity.materialType!!))
|
|
@ -1,15 +1,11 @@
|
|||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
import javax.validation.constraints.NotBlank
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "mix")
|
||||
|
@ -20,7 +16,6 @@ data class Mix(
|
|||
|
||||
var location: String?,
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val recipe: Recipe,
|
||||
|
@ -31,53 +26,9 @@ data class Mix(
|
|||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "mix_id")
|
||||
var mixMaterials: MutableSet<MixMaterial>,
|
||||
var mixMaterials: Set<MixMaterial>,
|
||||
) : 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 ====
|
||||
fun mix(
|
||||
id: Long? = null,
|
||||
|
@ -88,59 +39,12 @@ fun mix(
|
|||
op: Mix.() -> Unit = {}
|
||||
) = Mix(id, location, recipe, mixType, mixMaterials).apply(op)
|
||||
|
||||
fun mixSaveDto(
|
||||
name: String = "name",
|
||||
recipeId: Long = 0L,
|
||||
materialTypeId: Long = 0L,
|
||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
||||
op: MixSaveDto.() -> Unit = {}
|
||||
) = MixSaveDto(name, recipeId, materialTypeId, mixMaterials).apply(op)
|
||||
@Deprecated("Temporary DSL for transition")
|
||||
fun mix(
|
||||
dto: MixDto
|
||||
) = Mix(dto.id, dto.location, dto.recipe, mixType(dto.mixType), dto.mixMaterials.map(::mixMaterial).toSet())
|
||||
|
||||
fun mixUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
materialTypeId: Long? = 0L,
|
||||
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"
|
||||
)
|
||||
@Deprecated("Temporary DSL for transition")
|
||||
fun mix(
|
||||
entity: Mix
|
||||
) = MixDto(entity.id!!, entity.location, entity.recipe, mixTypeDto(entity.mixType), entity.mixMaterials.map(::mixMaterialDto).toSet())
|
|
@ -2,6 +2,8 @@ package dev.fyloz.colorrecipesexplorer.model
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
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.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.account.Group
|
||||
|
@ -150,7 +152,7 @@ data class RecipeOutputDto(
|
|||
val approbationExpired: Boolean?,
|
||||
val remark: String?,
|
||||
val company: Company,
|
||||
val mixes: Set<MixOutputDto>,
|
||||
val mixes: Set<MixDto>,
|
||||
val groupsInformation: Set<RecipeGroupInformation>,
|
||||
var imagesUrls: Set<String>
|
||||
) : ModelEntity
|
||||
|
|
|
@ -7,21 +7,11 @@ import org.springframework.data.jpa.repository.Modifying
|
|||
import org.springframework.data.jpa.repository.Query
|
||||
|
||||
interface MixRepository : JpaRepository<Mix, Long> {
|
||||
/** Finds all mixes with the given [mixType]. */
|
||||
fun findAllByMixType(mixType: MixType): Collection<Mix>
|
||||
/** Finds all mixes with the mix type with the given [mixTypeId]. */
|
||||
fun findAllByMixTypeId(mixTypeId: Long): Collection<Mix>
|
||||
|
||||
/** Updates the [location] of the [Mix] with the given [id]. */
|
||||
@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?)
|
||||
|
||||
@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
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialType
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixType
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
|
@ -24,4 +23,13 @@ interface MixTypeRepository : JpaRepository<MixType, Long> {
|
|||
"""
|
||||
)
|
||||
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
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialQuantityDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixDeductDto
|
||||
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.security.access.prepost.PreAuthorize
|
||||
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 MIX_CONTROLLER_PATH = "api/recipe/mix"
|
||||
|
||||
@RestController
|
||||
@RequestMapping(RECIPE_CONTROLLER_PATH)
|
||||
|
@ -83,34 +82,3 @@ class RecipeController(
|
|||
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]. */
|
||||
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
|
||||
|
@ -25,8 +28,8 @@ class DefaultMixTypeService(repository: MixTypeRepository, val materialService:
|
|||
override fun getByNameAndMaterialType(name: String, materialTypeId: Long) =
|
||||
repository.findByNameAndMaterialType(name, materialTypeId)?.let(::toDto)
|
||||
|
||||
override fun isUsedByMixes(id: Long) =
|
||||
repository.isUsedByMixes(id)
|
||||
override fun isUsedByMixes(id: Long) = repository.isUsedByMixes(id)
|
||||
override fun isShared(id: Long) = repository.isShared(id)
|
||||
|
||||
override fun toDto(entity: MixType) =
|
||||
MixTypeDto(entity.id!!, entity.name, materialService.toDto(entity.material))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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]. */
|
||||
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]. */
|
||||
fun <T> Iterable<T>.findDuplicated() =
|
||||
this.groupBy { it }
|
||||
.filter { it.value.count() > 1 }
|
||||
.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. */
|
||||
fun Iterable<Int>.hasGaps() =
|
||||
this.sorted()
|
||||
|
@ -58,8 +44,20 @@ inline fun <T> MutableCollection<T>.excludeAll(predicate: (T) -> Boolean): Itera
|
|||
return matching
|
||||
}
|
||||
|
||||
/** Merge to [ModelEntity] [Iterable]s and prevent id duplication. */
|
||||
fun <T : ModelEntity> Iterable<T>.merge(other: Iterable<T>) =
|
||||
this
|
||||
.filter { model -> other.all { it.id != model.id } }
|
||||
/**
|
||||
* Merge two [EntityDto] [Iterable]s and prevent duplication of their ids.
|
||||
* In case of collision, the items from the [other] iterable will be taken.
|
||||
*/
|
||||
@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)
|
||||
|
|
|
@ -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
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
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 io.mockk.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
@ -50,10 +51,10 @@ class DefaultMaterialLogicTest {
|
|||
mutableListOf(),
|
||||
setOf()
|
||||
)
|
||||
private val mix = Mix(
|
||||
1L, "location", recipe, mixType = MixType(1L, "Unit test mix type", material(materialMixType)), mutableSetOf()
|
||||
private val mix = MixDto(
|
||||
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(
|
||||
"Unit test SIMDUT",
|
||||
|
@ -62,7 +63,7 @@ class DefaultMaterialLogicTest {
|
|||
private val materialSaveDto = MaterialSaveDto(1L, "Unit test material", 1000f, materialType.id, simdutFileMock)
|
||||
|
||||
init {
|
||||
recipe.mixes.addAll(listOf(mix, mix2))
|
||||
recipe.mixes.addAll(listOf(mix(mix), mix(mix2)))
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
@ -139,7 +140,7 @@ class DefaultMaterialLogicTest {
|
|||
every { mixLogicMock.getById(any()) } returns mix
|
||||
|
||||
// Act
|
||||
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
||||
val materials = materialLogic.getAllForMixUpdate(mix.id)
|
||||
|
||||
// Assert
|
||||
assertContains(materials, material)
|
||||
|
@ -152,7 +153,7 @@ class DefaultMaterialLogicTest {
|
|||
every { mixLogicMock.getById(any()) } returns mix
|
||||
|
||||
// Act
|
||||
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
||||
val materials = materialLogic.getAllForMixUpdate(mix.id)
|
||||
|
||||
// Assert
|
||||
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 {
|
||||
private val mixMaterialServiceMock = mockk<MixMaterialService>()
|
||||
private val materialLogicMock = mockk<MaterialLogic>()
|
||||
|
||||
private val mixMaterialLogic = DefaultMixMaterialLogic(mixMaterialServiceMock)
|
||||
private val mixMaterialLogic = DefaultMixMaterialLogic(mixMaterialServiceMock, materialLogicMock)
|
||||
|
||||
@AfterEach
|
||||
internal fun afterEach() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.mockk.*
|
|||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.math.exp
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DefaultMixTypeLogicTest {
|
||||
|
@ -39,84 +40,105 @@ class DefaultMixTypeLogicTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun getOrCreateForNameAndMaterialType_notFound_returnsFromSaveForNameAndMaterialType() {
|
||||
fun getOrCreateForNameAndMaterialType_notFound_callsSave() {
|
||||
// Arrange
|
||||
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
|
||||
val actualMixType = mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType)
|
||||
|
||||
// Assert
|
||||
assertEquals(mixType, actualMixType)
|
||||
assertEquals(expectedMixType, actualMixType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() {
|
||||
fun updateOrCreateForNameAndMaterialType_mixTypeShared_callsSave() {
|
||||
// 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
|
||||
|
||||
val expectedMixType = mixType.copy(id = 0L, name = "${mixType.name} updated")
|
||||
|
||||
// Act
|
||||
mixTypeLogic.saveForNameAndMaterialType(mixType.name, materialType)
|
||||
mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||
|
||||
// Assert
|
||||
verify {
|
||||
materialLogicMock.save(match<MaterialDto> { it.name == mixType.name && it.materialType == materialType })
|
||||
mixTypeLogic.save(expectedMixType)
|
||||
}
|
||||
confirmVerified(materialLogicMock)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveForNameAndMaterialType_normalBehavior_callsSave() {
|
||||
fun updateOrCreateForNameAndMaterialType_mixTypeShared_returnsFromSave() {
|
||||
// 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
|
||||
|
||||
val expectedMixType = mixType.copy(id = 0L, name = "${mixType.name} updated")
|
||||
|
||||
// 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
|
||||
verify {
|
||||
mixTypeLogic.save(match { it.name == mixType.name && it.material.name == mixType.name && it.material.materialType == materialType })
|
||||
mixTypeLogic.update(expectedMixType)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() {
|
||||
fun updateOrCreateForNameAndMaterialType_mixTypeNotShared_returnsFromUpdate() {
|
||||
// Arrange
|
||||
val updatedName = mixType.name + " updated"
|
||||
val updatedMaterialType = materialType.copy(id = 2L)
|
||||
|
||||
every { materialLogicMock.update(any<MaterialDto>()) } returnsArgument 0
|
||||
every { mixTypeServiceMock.isShared(any()) } returns false
|
||||
every { materialLogicMock.update(any<MaterialDto>()) } returns material
|
||||
every { mixTypeLogic.update(any()) } returnsArgument 0
|
||||
|
||||
// Act
|
||||
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
|
||||
val expectedMixType = mixType.copy(name = "${mixType.name} updated")
|
||||
|
||||
// Act
|
||||
mixTypeLogic.updateForNameAndMaterialType(mixType, updatedName, updatedMaterialType)
|
||||
val actualMixType = mixTypeLogic.updateOrCreateForNameAndMaterialType(mixType, expectedMixType.name, materialType)
|
||||
|
||||
// Assert
|
||||
verify {
|
||||
mixTypeLogic.update(match { it.name == updatedName && it.material.name == updatedName && it.material.materialType == updatedMaterialType })
|
||||
}
|
||||
assertEquals(expectedMixType, actualMixType)
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixLocationDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
||||
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`() {
|
||||
val publicData = recipePublicDataDto(
|
||||
mixesLocation = setOf(
|
||||
mixLocationDto(mixId = 0L, location = "Loc 1"),
|
||||
mixLocationDto(mixId = 1L, location = "Loc 2")
|
||||
MixLocationDto(mixId = 0L, location = "Loc 1"),
|
||||
MixLocationDto(mixId = 1L, location = "Loc 2")
|
||||
)
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue