This commit is contained in:
parent
cb355c9e0d
commit
b598652594
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
object ControllerPaths {
|
||||||
|
const val file = "/api/file"
|
||||||
|
const val material = "/api/material"
|
||||||
|
}
|
||||||
|
|
||||||
|
object FilePaths {
|
||||||
|
const val pdfs = "pdf"
|
||||||
|
|
||||||
|
const val simdut = "$pdfs/simdut"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,6 @@ import javax.validation.constraints.NotBlank
|
||||||
data class CompanyDto(
|
data class CompanyDto(
|
||||||
override val id: Long = 0L,
|
override val id: Long = 0L,
|
||||||
|
|
||||||
@NotBlank
|
@field:NotBlank
|
||||||
val name: String
|
val name: String
|
||||||
) : EntityDto
|
) : EntityDto
|
|
@ -0,0 +1,34 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.dtos
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.MaterialType
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
import javax.validation.constraints.Min
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
|
||||||
|
data class MaterialDto(
|
||||||
|
override val id: Long = 0L,
|
||||||
|
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val inventoryQuantity: Float,
|
||||||
|
|
||||||
|
val isMixType: Boolean,
|
||||||
|
|
||||||
|
val materialType: MaterialType,
|
||||||
|
|
||||||
|
val simdutUrl: String? = null
|
||||||
|
) : EntityDto
|
||||||
|
|
||||||
|
data class MaterialSaveDto(
|
||||||
|
override val id: Long = 0L,
|
||||||
|
|
||||||
|
@field:NotBlank
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:Min(0)
|
||||||
|
val inventoryQuantity: Float,
|
||||||
|
|
||||||
|
val materialTypeId: Long,
|
||||||
|
|
||||||
|
val simdutFile: MultipartFile?
|
||||||
|
) : EntityDto
|
|
@ -23,7 +23,7 @@ class DefaultCompanyLogic(service: CompanyService) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteById(id: Long) {
|
override fun deleteById(id: Long) {
|
||||||
if (service.recipesDependsOnCompanyById(id)) {
|
if (service.isUsedByRecipe(id)) {
|
||||||
throw cannotDeleteException("Cannot delete the company with the id '$id' because one or more recipes depends on it")
|
throw cannotDeleteException("Cannot delete the company with the id '$id' because one or more recipes depends on it")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
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.exception.RestException
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.*
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
|
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
|
||||||
|
@ -89,7 +90,7 @@ class DefaultInventoryLogic(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotEnoughInventoryException(quantity: Float, material: Material) :
|
class NotEnoughInventoryException(quantity: Float, material: MaterialDto) :
|
||||||
RestException(
|
RestException(
|
||||||
"notenoughinventory",
|
"notenoughinventory",
|
||||||
"Not enough inventory",
|
"Not enough inventory",
|
||||||
|
|
|
@ -1,145 +1,120 @@
|
||||||
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.logic.config.ConfigurationLogic
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
import dev.fyloz.colorrecipesexplorer.service.MaterialService
|
||||||
import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
|
|
||||||
import io.jsonwebtoken.lang.Assert
|
|
||||||
import org.springframework.context.annotation.Lazy
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.net.URLEncoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
interface MaterialLogic :
|
interface MaterialLogic : Logic<MaterialDto, MaterialService> {
|
||||||
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository> {
|
/** Checks if a material with the given [name] exists. */
|
||||||
/** Checks if a material with the given [materialType] exists. */
|
fun existsByName(name: String): Boolean
|
||||||
fun existsByMaterialType(materialType: MaterialType): Boolean
|
|
||||||
|
|
||||||
/** Checks if the given [material] has a SIMDUT file. */
|
|
||||||
fun hasSimdut(material: Material): Boolean
|
|
||||||
|
|
||||||
/** Gets all materials that are not a mix type. */
|
/** Gets all materials that are not a mix type. */
|
||||||
fun getAllNotMixType(): Collection<MaterialOutputDto>
|
fun getAllNotMixType(): Collection<MaterialDto>
|
||||||
|
|
||||||
/** Gets all materials available for the creation of a mix for the recipe with the given [recipeId], including normal materials and materials from [MixType]s included in the said recipe. */
|
/**
|
||||||
fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto>
|
* Gets all materials available for the creation of a mix for the recipe with the given [recipeId],
|
||||||
|
* including normal materials and materials from mix types included in the said recipe.
|
||||||
|
*/
|
||||||
|
fun getAllForMixCreation(recipeId: Long): Collection<MaterialDto>
|
||||||
|
|
||||||
/** Gets all materials available for updating the mix with the given [mixId], including normal materials and materials from [MixType]s included in the mix recipe, excluding the material of the [MixType] of the said mix. */
|
/**
|
||||||
fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto>
|
* Gets all materials available for updating the mix with the given [mixId],
|
||||||
|
* including normal materials and materials from mix types included in the mix recipe
|
||||||
|
* and excluding the material of the mix type of the said mix.
|
||||||
|
*/
|
||||||
|
fun getAllForMixUpdate(mixId: Long): Collection<MaterialDto>
|
||||||
|
|
||||||
|
/** Saves the given [dto]. */
|
||||||
|
fun save(dto: MaterialSaveDto): MaterialDto
|
||||||
|
|
||||||
|
/** Updates the given [dto]. */
|
||||||
|
fun update(dto: MaterialSaveDto): MaterialDto
|
||||||
|
|
||||||
/** Updates the quantity of the given [material] with the given [factor] and returns the updated quantity. */
|
/** Updates the quantity of the given [material] with the given [factor] and returns the updated quantity. */
|
||||||
fun updateQuantity(material: Material, factor: Float): Float
|
fun updateQuantity(material: MaterialDto, factor: Float): Float
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@LogicComponent
|
||||||
@RequireDatabase
|
|
||||||
class DefaultMaterialLogic(
|
class DefaultMaterialLogic(
|
||||||
materialRepository: MaterialRepository,
|
service: MaterialService,
|
||||||
val recipeLogic: RecipeLogic,
|
val recipeLogic: RecipeLogic,
|
||||||
val mixLogic: MixLogic,
|
val mixLogic: MixLogic,
|
||||||
@Lazy val materialTypeLogic: MaterialTypeLogic,
|
val materialTypeLogic: MaterialTypeLogic,
|
||||||
val fileService: WriteableFileLogic,
|
val fileLogic: WriteableFileLogic
|
||||||
val configService: ConfigurationLogic
|
) : BaseLogic<MaterialDto, MaterialService>(service, Material::class.simpleName!!), MaterialLogic {
|
||||||
) :
|
override fun existsByName(name: String) = service.existsByName(name, null)
|
||||||
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository>(
|
override fun getAllNotMixType() = service.getAllNotMixType()
|
||||||
materialRepository
|
|
||||||
),
|
|
||||||
MaterialLogic {
|
|
||||||
override fun idNotFoundException(id: Long) = materialIdNotFoundException(id)
|
|
||||||
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
|
|
||||||
override fun nameNotFoundException(name: String) = materialNameNotFoundException(name)
|
|
||||||
override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name)
|
|
||||||
|
|
||||||
override fun Material.toOutput(): MaterialOutputDto =
|
override fun getAllForMixCreation(recipeId: Long): Collection<MaterialDto> {
|
||||||
MaterialOutputDto(
|
|
||||||
id = this.id!!,
|
|
||||||
name = this.name,
|
|
||||||
inventoryQuantity = this.inventoryQuantity,
|
|
||||||
isMixType = this.isMixType,
|
|
||||||
materialType = this.materialType!!,
|
|
||||||
simdutUrl = if (fileService.exists(this.simdutFilePath))
|
|
||||||
"${configService.getContent(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=${
|
|
||||||
URLEncoder.encode(
|
|
||||||
this.simdutFilePath,
|
|
||||||
StandardCharsets.UTF_8
|
|
||||||
)
|
|
||||||
}"
|
|
||||||
else null
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun existsByMaterialType(materialType: MaterialType): Boolean =
|
|
||||||
repository.existsByMaterialType(materialType)
|
|
||||||
|
|
||||||
override fun hasSimdut(material: Material): Boolean = fileService.exists(material.simdutFilePath)
|
|
||||||
override fun getAllNotMixType(): Collection<MaterialOutputDto> = getAllForOutput().filter { !it.isMixType }
|
|
||||||
|
|
||||||
override fun save(entity: MaterialSaveDto): Material =
|
|
||||||
save(with(entity) {
|
|
||||||
material(
|
|
||||||
name = entity.name,
|
|
||||||
inventoryQuantity = entity.inventoryQuantity,
|
|
||||||
materialType = materialTypeLogic.getById(materialTypeId),
|
|
||||||
isMixType = false
|
|
||||||
)
|
|
||||||
}).apply {
|
|
||||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) fileService.write(
|
|
||||||
entity.simdutFile,
|
|
||||||
this.simdutFilePath,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(entity: MaterialUpdateDto): Material {
|
|
||||||
val persistedMaterial by lazy {
|
|
||||||
getById(entity.id).apply { assertPersistedMaterial(this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return update(with(entity) {
|
|
||||||
material(
|
|
||||||
id = id,
|
|
||||||
name = if (name != null && name.isNotBlank()) name else persistedMaterial.name,
|
|
||||||
inventoryQuantity = if (inventoryQuantity != null && inventoryQuantity != Float.MIN_VALUE) inventoryQuantity else persistedMaterial.inventoryQuantity,
|
|
||||||
isMixType = persistedMaterial.isMixType,
|
|
||||||
materialType = if (materialTypeId != null) materialTypeLogic.getById(materialTypeId) else persistedMaterial.materialType
|
|
||||||
)
|
|
||||||
}).apply {
|
|
||||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) fileService.write(
|
|
||||||
entity.simdutFile,
|
|
||||||
this.simdutFilePath,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateQuantity(material: Material, factor: Float) = with(material) {
|
|
||||||
val updatedQuantity = this.inventoryQuantity + factor
|
|
||||||
repository.updateInventoryQuantityById(this.id!!, updatedQuantity)
|
|
||||||
updatedQuantity
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto> {
|
|
||||||
val recipesMixTypes = recipeLogic.getById(recipeId).mixTypes
|
val recipesMixTypes = recipeLogic.getById(recipeId).mixTypes
|
||||||
return getAllForOutput()
|
|
||||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
return getAll().filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto> {
|
override fun getAllForMixUpdate(mixId: Long): Collection<MaterialDto> {
|
||||||
val mix = mixLogic.getById(mixId)
|
val mix = mixLogic.getById(mixId)
|
||||||
val recipesMixTypes = mix.recipe.mixTypes
|
val recipesMixTypes = mix.recipe.mixTypes
|
||||||
return getAllForOutput()
|
|
||||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
return getAll().filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||||
.filter { it.id != mix.mixType.material.id }
|
.filter { it.id != mix.mixType.material.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertPersistedMaterial(material: Material) {
|
override fun save(dto: MaterialSaveDto) = save(saveDtoToDto(dto, false)).also { saveSimdutFile(dto, false) }
|
||||||
Assert.notNull(material.name, "The persisted material with the id ${material.id} has a null name")
|
override fun save(dto: MaterialDto): MaterialDto {
|
||||||
|
throwIfNameAlreadyExists(dto.name)
|
||||||
|
|
||||||
|
return super.save(dto)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(entity: Material) {
|
override fun update(dto: MaterialSaveDto) = update(saveDtoToDto(dto, true)).also { saveSimdutFile(dto, true) }
|
||||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
|
override fun update(dto: MaterialDto): MaterialDto {
|
||||||
if (fileService.exists(entity.simdutFilePath)) fileService.delete(entity.simdutFilePath)
|
throwIfNameAlreadyExists(dto.name, dto.id)
|
||||||
super.delete(entity)
|
|
||||||
|
return super.update(dto)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override fun updateQuantity(material: MaterialDto, factor: Float): Float {
|
||||||
|
val updatedQuantity = material.inventoryQuantity + factor
|
||||||
|
service.updateInventoryQuantityById(material.id, updatedQuantity)
|
||||||
|
|
||||||
|
return updatedQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long) {
|
||||||
|
if (service.isUsedByMixMaterialOrMixType(id)) {
|
||||||
|
throw cannotDeleteException("Cannot delete the material with the id '$id' because mix types and/or recipes depends on it")
|
||||||
|
}
|
||||||
|
|
||||||
|
val material = getById(id)
|
||||||
|
val simdutPath = Material.getSimdutFilePath(material.name)
|
||||||
|
if (fileLogic.exists(simdutPath)) {
|
||||||
|
fileLogic.delete(simdutPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.deleteById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveDtoToDto(saveDto: MaterialSaveDto, updating: Boolean): MaterialDto {
|
||||||
|
val isMixType = !updating || getById(saveDto.id).isMixType
|
||||||
|
val materialType = materialTypeLogic.getById(saveDto.materialTypeId)
|
||||||
|
|
||||||
|
return MaterialDto(saveDto.id, saveDto.name, saveDto.inventoryQuantity, isMixType, materialType, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveSimdutFile(dto: MaterialSaveDto, updating: Boolean) {
|
||||||
|
val file = dto.simdutFile
|
||||||
|
|
||||||
|
if (file != null && !file.isEmpty) {
|
||||||
|
fileLogic.write(file, Material.getSimdutFilePath(dto.name), updating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun throwIfNameAlreadyExists(name: String, id: Long? = null) {
|
||||||
|
if (service.existsByName(name, id)) {
|
||||||
|
throw alreadyExistsException(value = name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,6 @@ interface MaterialTypeLogic :
|
||||||
/** Checks if a material type with the given [prefix] exists. */
|
/** Checks if a material type with the given [prefix] exists. */
|
||||||
fun existsByPrefix(prefix: String): Boolean
|
fun existsByPrefix(prefix: String): Boolean
|
||||||
|
|
||||||
/** Checks if the given [materialType] is used by one or more materials. */
|
|
||||||
fun isUsedByMaterial(materialType: MaterialType): Boolean
|
|
||||||
|
|
||||||
/** Gets all system material types. */
|
/** Gets all system material types. */
|
||||||
fun getAllSystemTypes(): Collection<MaterialType>
|
fun getAllSystemTypes(): Collection<MaterialType>
|
||||||
|
|
||||||
|
@ -26,7 +23,7 @@ interface MaterialTypeLogic :
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequireDatabase
|
@RequireDatabase
|
||||||
class DefaultMaterialTypeLogic(repository: MaterialTypeRepository, private val materialLogic: MaterialLogic) :
|
class DefaultMaterialTypeLogic(repository: MaterialTypeRepository) :
|
||||||
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
|
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
|
||||||
repository
|
repository
|
||||||
), MaterialTypeLogic {
|
), MaterialTypeLogic {
|
||||||
|
@ -38,8 +35,6 @@ class DefaultMaterialTypeLogic(repository: MaterialTypeRepository, private val m
|
||||||
override fun MaterialType.toOutput() = this
|
override fun MaterialType.toOutput() = this
|
||||||
|
|
||||||
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
|
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
|
||||||
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
|
|
||||||
materialLogic.existsByMaterialType(materialType)
|
|
||||||
|
|
||||||
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
|
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
|
||||||
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)
|
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)
|
||||||
|
|
|
@ -44,7 +44,7 @@ class DefaultMixMaterialLogic(
|
||||||
|
|
||||||
override fun MixMaterial.toOutput() = MixMaterialOutputDto(
|
override fun MixMaterial.toOutput() = MixMaterialOutputDto(
|
||||||
this.id!!,
|
this.id!!,
|
||||||
with(materialLogic) { this@toOutput.material.toOutput() },
|
this.material,
|
||||||
this.quantity,
|
this.quantity,
|
||||||
this.position
|
this.position
|
||||||
)
|
)
|
||||||
|
@ -55,7 +55,7 @@ class DefaultMixMaterialLogic(
|
||||||
|
|
||||||
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
|
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
|
||||||
mixMaterial(
|
mixMaterial(
|
||||||
material = materialLogic.getById(mixMaterial.materialId),
|
material = material(materialLogic.getById(mixMaterial.materialId)),
|
||||||
quantity = mixMaterial.quantity,
|
quantity = mixMaterial.quantity,
|
||||||
position = mixMaterial.position
|
position = mixMaterial.position
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,27 +32,27 @@ class DefaultMixTypeLogic(
|
||||||
mixTypeRepository: MixTypeRepository,
|
mixTypeRepository: MixTypeRepository,
|
||||||
@Lazy val materialLogic: MaterialLogic
|
@Lazy val materialLogic: MaterialLogic
|
||||||
) :
|
) :
|
||||||
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeLogic {
|
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeLogic {
|
||||||
override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id)
|
override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id)
|
||||||
override fun idAlreadyExistsException(id: Long) = mixTypeIdAlreadyExistsException(id)
|
override fun idAlreadyExistsException(id: Long) = mixTypeIdAlreadyExistsException(id)
|
||||||
override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name)
|
override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name)
|
||||||
override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name)
|
override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name)
|
||||||
|
|
||||||
override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean =
|
override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean =
|
||||||
repository.existsByNameAndMaterialType(name, materialType)
|
repository.existsByNameAndMaterialType(name, materialType)
|
||||||
|
|
||||||
override fun getByMaterial(material: Material): MixType =
|
override fun getByMaterial(material: Material): MixType =
|
||||||
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
|
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
|
||||||
|
|
||||||
override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||||
repository.findByNameAndMaterialType(name, materialType)
|
repository.findByNameAndMaterialType(name, materialType)
|
||||||
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
|
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
|
||||||
|
|
||||||
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||||
if (existsByNameAndMaterialType(name, materialType))
|
if (existsByNameAndMaterialType(name, materialType))
|
||||||
getByNameAndMaterialType(name, materialType)
|
getByNameAndMaterialType(name, materialType)
|
||||||
else
|
else
|
||||||
saveForNameAndMaterialType(name, materialType)
|
saveForNameAndMaterialType(name, materialType)
|
||||||
|
|
||||||
override fun save(entity: MixType): MixType {
|
override fun save(entity: MixType): MixType {
|
||||||
if (materialLogic.existsByName(entity.name))
|
if (materialLogic.existsByName(entity.name))
|
||||||
|
@ -61,24 +61,20 @@ class DefaultMixTypeLogic(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
override fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||||
save(
|
save(
|
||||||
mixType(
|
mixType(
|
||||||
name = name,
|
name = name,
|
||||||
material = material(
|
material = material(
|
||||||
name = name,
|
name = name,
|
||||||
inventoryQuantity = Float.MIN_VALUE,
|
inventoryQuantity = Float.MIN_VALUE,
|
||||||
isMixType = true,
|
isMixType = true,
|
||||||
materialType = materialType
|
materialType = materialType
|
||||||
)
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
override fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType =
|
override fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType =
|
||||||
update(mixType.apply {
|
update(mixType.copy(material = mixType.material.copy(name = name, materialType = materialType)))
|
||||||
this.name = name
|
|
||||||
material.name = name
|
|
||||||
material.materialType = materialType
|
|
||||||
})
|
|
||||||
|
|
||||||
override fun delete(entity: MixType) {
|
override fun delete(entity: MixType) {
|
||||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity)
|
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity)
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model
|
package dev.fyloz.colorrecipesexplorer.model
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
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 org.springframework.web.multipart.MultipartFile
|
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.Min
|
import javax.validation.constraints.Min
|
||||||
import javax.validation.constraints.NotBlank
|
|
||||||
|
|
||||||
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
const val SIMDUT_FILES_PATH = "pdf/simdut"
|
||||||
|
|
||||||
|
@ -19,59 +18,24 @@ data class Material(
|
||||||
override val id: Long?,
|
override val id: Long?,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
override var name: String,
|
val name: String,
|
||||||
|
|
||||||
@Column(name = "inventory_quantity")
|
@Column(name = "inventory_quantity")
|
||||||
var inventoryQuantity: Float,
|
val inventoryQuantity: Float,
|
||||||
|
|
||||||
@Column(name = "mix_type")
|
@Column(name = "mix_type")
|
||||||
val isMixType: Boolean,
|
val isMixType: Boolean,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "material_type_id")
|
@JoinColumn(name = "material_type_id")
|
||||||
var materialType: MaterialType?
|
val materialType: MaterialType?
|
||||||
) : NamedModelEntity {
|
) : ModelEntity {
|
||||||
val simdutFilePath
|
companion object {
|
||||||
@JsonIgnore
|
fun getSimdutFilePath(name: String) =
|
||||||
@Transient
|
"${Constants.FilePaths.simdut}/$name.pdf"
|
||||||
get() = "$SIMDUT_FILES_PATH/$name.pdf"
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class MaterialSaveDto(
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
|
||||||
val inventoryQuantity: Float,
|
|
||||||
|
|
||||||
val materialTypeId: Long,
|
|
||||||
|
|
||||||
val simdutFile: MultipartFile? = null
|
|
||||||
) : EntityDto<Material>
|
|
||||||
|
|
||||||
open class MaterialUpdateDto(
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:NotBlank
|
|
||||||
val name: String?,
|
|
||||||
|
|
||||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
|
||||||
val inventoryQuantity: Float?,
|
|
||||||
|
|
||||||
val materialTypeId: Long?,
|
|
||||||
|
|
||||||
val simdutFile: MultipartFile? = null
|
|
||||||
) : EntityDto<Material>
|
|
||||||
|
|
||||||
data class MaterialOutputDto(
|
|
||||||
override val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val inventoryQuantity: Float,
|
|
||||||
val isMixType: Boolean,
|
|
||||||
val materialType: MaterialType,
|
|
||||||
val simdutUrl: String?
|
|
||||||
) : ModelEntity
|
|
||||||
|
|
||||||
data class MaterialQuantityDto(
|
data class MaterialQuantityDto(
|
||||||
val material: Long,
|
val material: Long,
|
||||||
|
|
||||||
|
@ -99,22 +63,15 @@ fun material(
|
||||||
?: material.name, material.inventoryQuantity, material.isMixType, material.materialType
|
?: material.name, material.inventoryQuantity, material.isMixType, material.materialType
|
||||||
)
|
)
|
||||||
|
|
||||||
fun materialSaveDto(
|
@Deprecated("Temporary DSL for transition")
|
||||||
name: String = "name",
|
fun material(
|
||||||
inventoryQuantity: Float = 0f,
|
dto: MaterialDto
|
||||||
materialTypeId: Long = 0L,
|
) = Material(dto.id, dto.name, dto.inventoryQuantity, dto.isMixType, dto.materialType)
|
||||||
simdutFile: MultipartFile? = null,
|
|
||||||
op: MaterialSaveDto.() -> Unit = {}
|
|
||||||
) = MaterialSaveDto(name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
|
|
||||||
|
|
||||||
fun materialUpdateDto(
|
@Deprecated("Temporary DSL for transition")
|
||||||
id: Long = 0L,
|
fun materialDto(
|
||||||
name: String? = "name",
|
entity: Material
|
||||||
inventoryQuantity: Float? = 0f,
|
) = MaterialDto(entity.id!!, entity.name, entity.inventoryQuantity, entity.isMixType, entity.materialType!!)
|
||||||
materialTypeId: Long? = 0L,
|
|
||||||
simdutFile: MultipartFile? = null,
|
|
||||||
op: MaterialUpdateDto.() -> Unit = {}
|
|
||||||
) = MaterialUpdateDto(id, name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
|
|
||||||
|
|
||||||
fun materialQuantityDto(
|
fun materialQuantityDto(
|
||||||
materialId: Long,
|
materialId: Long,
|
||||||
|
|
|
@ -32,7 +32,7 @@ data class MixMaterialDto(
|
||||||
|
|
||||||
data class MixMaterialOutputDto(
|
data class MixMaterialOutputDto(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val material: MaterialOutputDto,
|
val material: Material, // TODO move to MaterialDto
|
||||||
val quantity: Float,
|
val quantity: Float,
|
||||||
val position: Int
|
val position: Int
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.model
|
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.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
|
||||||
import dev.fyloz.colorrecipesexplorer.model.account.group
|
import dev.fyloz.colorrecipesexplorer.model.account.group
|
||||||
import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
import javax.validation.constraints.*
|
import javax.validation.constraints.Max
|
||||||
|
import javax.validation.constraints.Min
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.Pattern
|
||||||
|
|
||||||
private const val VALIDATION_COLOR_PATTERN = "^#([0-9a-f]{6})$"
|
private const val VALIDATION_COLOR_PATTERN = "^#([0-9a-f]{6})$"
|
||||||
|
|
||||||
|
@ -67,7 +70,7 @@ data class Recipe(
|
||||||
groupsInformation.firstOrNull { it.group.id == groupId }
|
groupsInformation.firstOrNull { it.group.id == groupId }
|
||||||
|
|
||||||
fun imageUrl(deploymentUrl: String, name: String) =
|
fun imageUrl(deploymentUrl: String, name: String) =
|
||||||
"$deploymentUrl$FILE_CONTROLLER_PATH?path=${
|
"$deploymentUrl${Constants.ControllerPaths.file}?path=${
|
||||||
URLEncoder.encode(
|
URLEncoder.encode(
|
||||||
"${this.imagesDirectoryPath}/$name",
|
"${this.imagesDirectoryPath}/$name",
|
||||||
StandardCharsets.UTF_8
|
StandardCharsets.UTF_8
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface CompanyRepository : JpaRepository<Company, Long> {
|
interface CompanyRepository : JpaRepository<Company, Long> {
|
||||||
/** Checks if a company with the given [name] and an id different from the given [id] exists. */
|
/** Checks if a company with the given [name] and a different [id] exists. */
|
||||||
fun existsByNameAndIdNot(name: String, id: Long): Boolean
|
fun existsByNameAndIdNot(name: String, id: Long): Boolean
|
||||||
|
|
||||||
/** Checks if a recipe depends on the company with the given [id]. */
|
/** Checks if a recipe depends on the company with the given [id]. */
|
||||||
|
@ -17,5 +17,5 @@ interface CompanyRepository : JpaRepository<Company, Long> {
|
||||||
from Recipe r where r.company.id = :id
|
from Recipe r where r.company.id = :id
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
fun recipesDependsOnCompanyById(id: Long): Boolean
|
fun isUsedByRecipe(id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.repository
|
package dev.fyloz.colorrecipesexplorer.repository
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||||
import dev.fyloz.colorrecipesexplorer.model.MaterialType
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Modifying
|
import org.springframework.data.jpa.repository.Modifying
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface MaterialRepository : NamedJpaRepository<Material> {
|
interface MaterialRepository : JpaRepository<Material, Long> {
|
||||||
/** Checks if one or more materials have the given [materialType]. */
|
/** Checks if a material with the given [name] and a different [id] exists. */
|
||||||
fun existsByMaterialType(materialType: MaterialType): Boolean
|
fun existsByNameAndIdNot(name: String, id: Long): Boolean
|
||||||
|
|
||||||
|
/** Gets all non mix type materials. */
|
||||||
|
fun getAllByIsMixTypeIsFalse(): Collection<Material>
|
||||||
|
|
||||||
/** Updates the [inventoryQuantity] of the [Material] with the given [id]. */
|
/** Updates the [inventoryQuantity] of the [Material] with the given [id]. */
|
||||||
@Modifying
|
@Modifying
|
||||||
@Query("UPDATE Material m SET m.inventoryQuantity = :inventoryQuantity WHERE m.id = :id")
|
@Query("UPDATE Material m SET m.inventoryQuantity = :inventoryQuantity WHERE m.id = :id")
|
||||||
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
|
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
|
||||||
|
|
||||||
|
/** Checks if a mix material or a mix type depends on the material with the given [id]. */
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
select case when(count(mm.id) + count(mt.id) > 0) then false else true end
|
select case when(count(mm.id) + count(mt.id) > 0) then false else true end
|
||||||
from Material m
|
from Material m
|
||||||
left join MixMaterial mm on m.id = mm.material.id
|
left join MixMaterial mm on mm.material.id = m.id
|
||||||
left join MixType mt on m.id = mt.material.id
|
left join MixType mt on mt.material.id = m.id
|
||||||
where m.id = :id
|
where m.id = :id
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
fun canBeDeleted(id: Long): Boolean
|
fun isUsedByMixMaterialOrMixType(id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
||||||
|
@ -10,10 +11,8 @@ import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
const val FILE_CONTROLLER_PATH = "/api/file"
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(FILE_CONTROLLER_PATH)
|
@RequestMapping(Constants.ControllerPaths.file)
|
||||||
class FileController(
|
class FileController(
|
||||||
private val fileLogic: WriteableFileLogic,
|
private val fileLogic: WriteableFileLogic,
|
||||||
private val configurationLogic: ConfigurationLogic
|
private val configurationLogic: ConfigurationLogic
|
||||||
|
@ -44,6 +43,6 @@ class FileController(
|
||||||
|
|
||||||
private fun created(path: String): ResponseEntity<Void> =
|
private fun created(path: String): ResponseEntity<Void> =
|
||||||
ResponseEntity
|
ResponseEntity
|
||||||
.created(URI.create("${configurationLogic.get(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=$path"))
|
.created(URI.create("${configurationLogic.get(ConfigurationType.INSTANCE_URL)}${Constants.ControllerPaths.file}?path=$path"))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.rest
|
package dev.fyloz.colorrecipesexplorer.rest
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
|
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.MaterialLogic
|
import dev.fyloz.colorrecipesexplorer.logic.MaterialLogic
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.context.annotation.Profile
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
@ -10,10 +12,8 @@ import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import javax.validation.Valid
|
import javax.validation.Valid
|
||||||
|
|
||||||
private const val MATERIAL_CONTROLLER_PATH = "api/material"
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(MATERIAL_CONTROLLER_PATH)
|
@RequestMapping(Constants.ControllerPaths.material)
|
||||||
@Profile("!emergency")
|
@Profile("!emergency")
|
||||||
@PreAuthorizeViewCatalog
|
@PreAuthorizeViewCatalog
|
||||||
class MaterialController(
|
class MaterialController(
|
||||||
|
@ -21,7 +21,7 @@ class MaterialController(
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getAll() =
|
fun getAll() =
|
||||||
ok(materialLogic.getAllForOutput())
|
ok(materialLogic.getAll())
|
||||||
|
|
||||||
@GetMapping("notmixtype")
|
@GetMapping("notmixtype")
|
||||||
fun getAllNotMixType() =
|
fun getAllNotMixType() =
|
||||||
|
@ -29,37 +29,20 @@ class MaterialController(
|
||||||
|
|
||||||
@GetMapping("{id}")
|
@GetMapping("{id}")
|
||||||
fun getById(@PathVariable id: Long) =
|
fun getById(@PathVariable id: Long) =
|
||||||
ok(materialLogic.getByIdForOutput(id))
|
ok(materialLogic.getById(id))
|
||||||
|
|
||||||
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||||
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
||||||
fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
|
fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
|
||||||
created<MaterialOutputDto>(MATERIAL_CONTROLLER_PATH) {
|
created<MaterialDto>(Constants.ControllerPaths.material) {
|
||||||
with(materialLogic) {
|
materialLogic.save(material.copy(simdutFile = simdutFile))
|
||||||
save(
|
|
||||||
materialSaveDto(
|
|
||||||
name = material.name,
|
|
||||||
inventoryQuantity = material.inventoryQuantity,
|
|
||||||
materialTypeId = material.materialTypeId,
|
|
||||||
simdutFile = simdutFile
|
|
||||||
)
|
|
||||||
).toOutput()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||||
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
||||||
fun update(@Valid material: MaterialUpdateDto, simdutFile: MultipartFile?) =
|
fun update(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
|
||||||
noContent {
|
noContent {
|
||||||
materialLogic.update(
|
materialLogic.update(material.copy(simdutFile = simdutFile))
|
||||||
materialUpdateDto(
|
|
||||||
id = material.id,
|
|
||||||
name = material.name,
|
|
||||||
inventoryQuantity = material.inventoryQuantity,
|
|
||||||
materialTypeId = material.materialTypeId,
|
|
||||||
simdutFile = simdutFile
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("{id}")
|
@DeleteMapping("{id}")
|
||||||
|
|
|
@ -10,14 +10,14 @@ interface CompanyService : Service<CompanyDto, Company, CompanyRepository> {
|
||||||
fun existsByName(name: String, id: Long?): Boolean
|
fun existsByName(name: String, id: Long?): Boolean
|
||||||
|
|
||||||
/** Checks if a recipe depends on the company with the given [id]. */
|
/** Checks if a recipe depends on the company with the given [id]. */
|
||||||
fun recipesDependsOnCompanyById(id: Long): Boolean
|
fun isUsedByRecipe(id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ServiceComponent
|
@ServiceComponent
|
||||||
class DefaultCompanyService(repository: CompanyRepository) :
|
class DefaultCompanyService(repository: CompanyRepository) :
|
||||||
BaseService<CompanyDto, Company, CompanyRepository>(repository), CompanyService {
|
BaseService<CompanyDto, Company, CompanyRepository>(repository), CompanyService {
|
||||||
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0)
|
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0)
|
||||||
override fun recipesDependsOnCompanyById(id: Long) = repository.recipesDependsOnCompanyById(id)
|
override fun isUsedByRecipe(id: Long) = repository.isUsedByRecipe(id)
|
||||||
|
|
||||||
override fun toDto(entity: Company) =
|
override fun toDto(entity: Company) =
|
||||||
CompanyDto(entity.id!!, entity.name)
|
CompanyDto(entity.id!!, entity.name)
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.service
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.Constants
|
||||||
|
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.logic.files.FileLogic
|
||||||
|
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||||
|
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
interface MaterialService : Service<MaterialDto, Material, MaterialRepository> {
|
||||||
|
/** Checks if a material with the given [name] and a different [id] exists. */
|
||||||
|
fun existsByName(name: String, id: Long?): Boolean
|
||||||
|
|
||||||
|
/** Gets all non mix type materials. */
|
||||||
|
fun getAllNotMixType(): Collection<MaterialDto>
|
||||||
|
|
||||||
|
/** Updates the [inventoryQuantity] of the [Material] with the given [id]. */
|
||||||
|
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
|
||||||
|
|
||||||
|
/** Checks if a mix material or a mix type depends on the material with the given [id]. */
|
||||||
|
fun isUsedByMixMaterialOrMixType(id: Long): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceComponent
|
||||||
|
class DefaultMaterialService(repository: MaterialRepository, @Qualifier("defaultFileLogic") val fileLogic: FileLogic) :
|
||||||
|
BaseService<MaterialDto, Material, MaterialRepository>(repository), MaterialService {
|
||||||
|
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0)
|
||||||
|
override fun getAllNotMixType() = repository.getAllByIsMixTypeIsFalse().map(this::toDto)
|
||||||
|
override fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float) = repository.updateInventoryQuantityById(id, inventoryQuantity)
|
||||||
|
override fun isUsedByMixMaterialOrMixType(id: Long) = repository.isUsedByMixMaterialOrMixType(id)
|
||||||
|
|
||||||
|
override fun toDto(entity: Material) =
|
||||||
|
MaterialDto(
|
||||||
|
entity.id!!,
|
||||||
|
entity.name,
|
||||||
|
entity.inventoryQuantity,
|
||||||
|
entity.isMixType,
|
||||||
|
entity.materialType!!,
|
||||||
|
getSimdutUrl(entity)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toEntity(dto: MaterialDto) =
|
||||||
|
Material(dto.id, dto.name, dto.inventoryQuantity, dto.isMixType, dto.materialType)
|
||||||
|
|
||||||
|
private fun getSimdutUrl(material: Material): String? {
|
||||||
|
val filePath = "${Constants.FilePaths.simdut}/${material.name}.pdf"
|
||||||
|
|
||||||
|
if (!fileLogic.exists(filePath)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val encodedPath = URLEncoder.encode(filePath, StandardCharsets.UTF_8)
|
||||||
|
return "${Constants.ControllerPaths.file}?path=$encodedPath"
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ class DefaultCompanyLogicTest {
|
||||||
@Test
|
@Test
|
||||||
fun deleteById_recipesDependsOnCompany_throwsCannotDeleteException() {
|
fun deleteById_recipesDependsOnCompany_throwsCannotDeleteException() {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { companyServiceMock.recipesDependsOnCompanyById(company.id) } returns true
|
every { companyServiceMock.isUsedByRecipe(company.id) } returns true
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
@ -0,0 +1,382 @@
|
||||||
|
package dev.fyloz.colorrecipesexplorer.logic
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||||
|
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
|
||||||
|
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.service.MaterialService
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
import kotlin.test.assertContains
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class DefaultMaterialLogicTest {
|
||||||
|
private val materialServiceMock = mockk<MaterialService>()
|
||||||
|
private val recipeLogicMock = mockk<RecipeLogic>()
|
||||||
|
private val mixLogicMock = mockk<MixLogic>()
|
||||||
|
private val materialTypeLogicMock = mockk<MaterialTypeLogic>()
|
||||||
|
private val fileLogicMock = mockk<WriteableFileLogic>()
|
||||||
|
|
||||||
|
private val materialLogic = spyk(
|
||||||
|
DefaultMaterialLogic(
|
||||||
|
materialServiceMock, recipeLogicMock, mixLogicMock, materialTypeLogicMock, fileLogicMock
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val materialType = MaterialType(
|
||||||
|
1L, "Unit test material type", "UNT", usePercentages = false, systemType = false
|
||||||
|
) // TODO move to DTO
|
||||||
|
private val material = MaterialDto(1L, "Unit test material", 1000f, false, materialType)
|
||||||
|
private val materialMixType = material.copy(id = 2L, isMixType = true)
|
||||||
|
private val materialMixType2 = material.copy(id = 3L, isMixType = true)
|
||||||
|
private val company = Company(1L, "Unit test company")
|
||||||
|
private val recipe = Recipe(
|
||||||
|
1L,
|
||||||
|
"Unit test recipe",
|
||||||
|
"Unit test recipe",
|
||||||
|
"#FFFFFF",
|
||||||
|
0,
|
||||||
|
123,
|
||||||
|
null,
|
||||||
|
"A remark",
|
||||||
|
company,
|
||||||
|
mutableListOf(),
|
||||||
|
setOf()
|
||||||
|
)
|
||||||
|
private val mix = Mix(
|
||||||
|
1L, "location", recipe, mixType = MixType(1L, "Unit test mix type", material(materialMixType)), mutableSetOf()
|
||||||
|
)
|
||||||
|
private val mix2 = mix.copy(id = 2L, mixType = mix.mixType.copy(id = 2L, material = material(materialMixType2)))
|
||||||
|
|
||||||
|
private val simdutFileMock = MockMultipartFile(
|
||||||
|
"Unit test SIMDUT",
|
||||||
|
byteArrayOf(1, 2, 3, 4)
|
||||||
|
) // Put some content in the mock file so it is not ignored
|
||||||
|
private val materialSaveDto = MaterialSaveDto(1L, "Unit test material", 1000f, materialType.id!!, simdutFileMock)
|
||||||
|
|
||||||
|
init {
|
||||||
|
recipe.mixes.addAll(listOf(mix, mix2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
internal fun afterEach() {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun existsByName_normalBehavior_returnsTrue() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.existsByName(any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val exists = materialLogic.existsByName(material.name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun existsByName_notFound_returnsFalse() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.existsByName(any(), any()) } returns false
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val exists = materialLogic.existsByName(material.name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllNotMixType_normalBehavior_returnsMaterialsFromService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.getAllNotMixType() } returns listOf(material)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllNotMixType()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertContains(materials, material)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllForMixCreation_normalBehavior_returnsNonMixTypeMaterials() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getAll() } returns listOf(material, materialMixType2)
|
||||||
|
every { recipeLogicMock.getById(any()) } returns recipe
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllForMixCreation(recipe.id!!)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertContains(materials, material)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllForMixCreation_normalBehavior_returnsRecipeMixTypesMaterials() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getAll() } returns listOf(material, materialMixType2)
|
||||||
|
every { recipeLogicMock.getById(any()) } returns recipe
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllForMixCreation(recipe.id!!)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertContains(materials, materialMixType2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllForMixUpdate_normalBehavior_returnsNonMixTypeMaterials() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
|
||||||
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertContains(materials, material)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllForMixUpdate_normalBehavior_returnsRecipeMixTypesMaterials() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
|
||||||
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertContains(materials, materialMixType2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getAllForMixUpdate_normalBehavior_excludesGivenMixTypeMaterial() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
|
||||||
|
every { mixLogicMock.getById(any()) } returns mix
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse { materialMixType in materials }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_materialSaveDto_normalBehavior_callsSave() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.save(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.save(materialSaveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
materialLogic.save(any<MaterialDto>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_materialSaveDto_normalBehavior_callsWriteInFileService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.save(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.save(materialSaveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
fileLogicMock.write(simdutFileMock, any(), false)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_materialSaveDto_noSimdutFile_doesNotCallWriteInFileService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.save(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
val saveDto = materialSaveDto.copy(simdutFile = null)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.save(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(exactly = 0) {
|
||||||
|
fileLogicMock.write(simdutFileMock, any(), false)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun save_nameExists_throwsNameAlreadyExists() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.existsByName(any(), any()) } returns true
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<AlreadyExistsException> { materialLogic.save(material) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_saveDto_normalBehavior_callsUpdate() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialLogic.update(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.update(materialSaveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
materialLogic.update(any<MaterialDto>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_materialSaveDto_normalBehavior_callsWriteInFileService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialLogic.update(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.update(materialSaveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
fileLogicMock.write(simdutFileMock, any(), true)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_materialSaveDto_noSimdutFile_doesNotCallWriteInFileService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialLogic.update(any<MaterialDto>()) } returns material
|
||||||
|
every { materialTypeLogicMock.getById(any()) } returns materialType
|
||||||
|
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
|
||||||
|
|
||||||
|
val saveDto = materialSaveDto.copy(simdutFile = null)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.update(saveDto)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(exactly = 0) {
|
||||||
|
fileLogicMock.write(simdutFileMock, any(), true)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateQuantity_normalBehavior_callsUpdateInventoryQuantityByIdInService() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.updateInventoryQuantityById(any(), any()) } just runs
|
||||||
|
|
||||||
|
val factor = 3f
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.updateQuantity(material, factor)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
materialServiceMock.updateInventoryQuantityById(material.id, material.inventoryQuantity + factor)
|
||||||
|
}
|
||||||
|
confirmVerified(materialServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateQuantity_normalBehavior_returnsUpdatedQuantity() {
|
||||||
|
// Arrange
|
||||||
|
every { materialServiceMock.updateInventoryQuantityById(any(), any()) } just runs
|
||||||
|
|
||||||
|
val factor = 3f
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val updatedQuantity = materialLogic.updateQuantity(material, factor)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(material.inventoryQuantity + factor, updatedQuantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteById_normalBehavior_callsDeleteInFileLogic() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns false
|
||||||
|
every { materialServiceMock.deleteById(any()) } just runs
|
||||||
|
every { fileLogicMock.exists(any()) } returns true
|
||||||
|
every { fileLogicMock.delete(any()) } just runs
|
||||||
|
|
||||||
|
val simdutPath = Material.getSimdutFilePath(material.name)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.deleteById(material.id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
fileLogicMock.exists(simdutPath)
|
||||||
|
fileLogicMock.delete(simdutPath)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteById_simdutFileNotExists_doesNotCallDeleteInFileLogic() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns false
|
||||||
|
every { materialServiceMock.deleteById(any()) } just runs
|
||||||
|
every { fileLogicMock.exists(any()) } returns false
|
||||||
|
every { fileLogicMock.delete(any()) } just runs
|
||||||
|
|
||||||
|
val simdutPath = Material.getSimdutFilePath(material.name)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
materialLogic.deleteById(material.id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify {
|
||||||
|
fileLogicMock.exists(simdutPath)
|
||||||
|
}
|
||||||
|
verify(exactly = 0) {
|
||||||
|
fileLogicMock.delete(simdutPath)
|
||||||
|
}
|
||||||
|
confirmVerified(fileLogicMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteById_usedByMixMaterialOrMixType_throwsCannotDeleteException() {
|
||||||
|
// Arrange
|
||||||
|
every { materialLogic.getById(any()) } returns material
|
||||||
|
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns true
|
||||||
|
every { materialServiceMock.deleteById(any()) } just runs
|
||||||
|
every { fileLogicMock.exists(any()) } returns false
|
||||||
|
every { fileLogicMock.delete(any()) } just runs
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Assert
|
||||||
|
assertThrows<CannotDeleteException> { materialLogic.deleteById(material.id) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -175,7 +175,7 @@ class InventoryLogicTest {
|
||||||
) {
|
) {
|
||||||
val material = material(id = materialQuantity.material, inventoryQuantity = stored)
|
val material = material(id = materialQuantity.material, inventoryQuantity = stored)
|
||||||
|
|
||||||
whenever(materialLogic.getById(material.id!!)).doReturn(material)
|
whenever(materialLogic.getById(material.id!!)).doReturn(materialDto(material))
|
||||||
|
|
||||||
materialQuantity.test(stored)
|
materialQuantity.test(stored)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,249 +0,0 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.logic
|
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
|
||||||
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
|
|
||||||
import dev.fyloz.colorrecipesexplorer.model.*
|
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
|
||||||
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 org.springframework.mock.web.MockMultipartFile
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class MaterialLogicTest :
|
|
||||||
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialLogic, MaterialRepository>() {
|
|
||||||
override val repository: MaterialRepository = mock()
|
|
||||||
private val recipeService: RecipeLogic = mock()
|
|
||||||
private val mixService: MixLogic = mock()
|
|
||||||
private val materialTypeService: MaterialTypeLogic = mock()
|
|
||||||
private val fileService: WriteableFileLogic = mock()
|
|
||||||
override val logic: MaterialLogic =
|
|
||||||
spy(DefaultMaterialLogic(repository, recipeService, mixService, materialTypeService, fileService, mock()))
|
|
||||||
|
|
||||||
override val entity: Material = material(id = 0L, name = "material")
|
|
||||||
private val entityOutput = materialOutputDto(entity)
|
|
||||||
override val anotherEntity: Material = material(id = 1L, name = "another material")
|
|
||||||
override val entityWithEntityName: Material = material(id = 2L, name = "material")
|
|
||||||
override val entitySaveDto: MaterialSaveDto = spy(materialSaveDto())
|
|
||||||
override val entityUpdateDto: MaterialUpdateDto = spy(materialUpdateDto(id = 0L))
|
|
||||||
|
|
||||||
private val materialType = materialType()
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
override fun afterEach() {
|
|
||||||
reset(recipeService, mixService, materialTypeService, fileService)
|
|
||||||
super.afterEach()
|
|
||||||
}
|
|
||||||
|
|
||||||
// existsByMaterialType
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `existsByMaterialType() returns true when a material with the given material type exists in the repository`() {
|
|
||||||
whenever(repository.existsByMaterialType(materialType)).doReturn(true)
|
|
||||||
|
|
||||||
val found = logic.existsByMaterialType(materialType)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `existsByMaterialType() returns false when no material with the given material type exists in the repository`() {
|
|
||||||
whenever(repository.existsByMaterialType(materialType)).doReturn(false)
|
|
||||||
|
|
||||||
val found = logic.existsByMaterialType(materialType)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasSimdut()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `hasSimdut() returns false when simdutService_exists() returns false`() {
|
|
||||||
whenever(fileService.exists(any())).doReturn(false)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
val found = logic.hasSimdut(entity)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `hasSimdut() returns true when simdutService_exists() returns true`() {
|
|
||||||
whenever(fileService.exists(any())).doReturn(true)
|
|
||||||
doReturn(entity).whenever(logic).getById(entity.id!!)
|
|
||||||
|
|
||||||
val found = logic.hasSimdut(entity)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAllNotMixType()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getAllNotMixType() returns a list containing every material that are not a mix type`() {
|
|
||||||
val mixTypeMaterial = material(id = 1L, name = "mix type material", isMixType = true)
|
|
||||||
val mixTypeMaterialOutput = materialOutputDto(mixTypeMaterial)
|
|
||||||
val materialList = listOf(entity, mixTypeMaterial)
|
|
||||||
|
|
||||||
doReturn(materialList).whenever(logic).getAll()
|
|
||||||
|
|
||||||
val found = logic.getAllNotMixType()
|
|
||||||
|
|
||||||
assertTrue(found.contains(entityOutput))
|
|
||||||
assertFalse(found.contains(mixTypeMaterialOutput))
|
|
||||||
}
|
|
||||||
|
|
||||||
// save()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `save() throws AlreadyExistsException when a material with the given name exists in the repository`() {
|
|
||||||
doReturn(true).whenever(logic).existsByName(entity.name)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.save(entity) }
|
|
||||||
.assertErrorCode("name")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
|
||||||
withBaseSaveDtoTest(entity, entitySaveDto, logic, { any() })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `save(dto) calls simdutService_write() with the saved entity`() {
|
|
||||||
val mockMultipartFile = spy(MockMultipartFile("simdut", byteArrayOf()))
|
|
||||||
val materialSaveDto = spy(materialSaveDto(simdutFile = mockMultipartFile))
|
|
||||||
|
|
||||||
doReturn(false).whenever(mockMultipartFile).isEmpty
|
|
||||||
doReturn(entity).whenever(logic).save(any<Material>())
|
|
||||||
|
|
||||||
logic.save(materialSaveDto)
|
|
||||||
|
|
||||||
verify(fileService).write(mockMultipartFile, entity.simdutFilePath, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `update() throws AlreadyExistsException when another material with the updated name exists in the repository`() {
|
|
||||||
val material = material(id = 0L, name = "name")
|
|
||||||
val anotherMaterial = material(id = 1L, name = "name")
|
|
||||||
|
|
||||||
whenever(repository.findByName(material.name)).doReturn(anotherMaterial)
|
|
||||||
doReturn(entity).whenever(logic).getById(material.id!!)
|
|
||||||
|
|
||||||
assertThrows<AlreadyExistsException> { logic.update(material) }
|
|
||||||
.assertErrorCode("name")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
override fun `update(dto) calls and returns update() with the created entity`() {
|
|
||||||
val mockSimdutFile = MockMultipartFile("simdut", byteArrayOf(1, 2, 3, 4, 5))
|
|
||||||
val materialUpdateDto = spy(materialUpdateDto(id = 0L, simdutFile = mockSimdutFile))
|
|
||||||
|
|
||||||
doReturn(entity).whenever(logic).getById(any())
|
|
||||||
doReturn(entity).whenever(logic).update(any<Material>())
|
|
||||||
doReturn(entity).whenever(materialUpdateDto).toEntity()
|
|
||||||
|
|
||||||
logic.update(materialUpdateDto)
|
|
||||||
|
|
||||||
verify(fileService).write(mockSimdutFile, entity.simdutFilePath, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateQuantity()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `updateQuantity() updates the quantity of the the given material in the repository`() {
|
|
||||||
val material = material(id = 0L, inventoryQuantity = 4321f)
|
|
||||||
val quantity = 1234f
|
|
||||||
val totalQuantity = material.inventoryQuantity + quantity
|
|
||||||
|
|
||||||
val found = logic.updateQuantity(material, quantity)
|
|
||||||
|
|
||||||
verify(repository).updateInventoryQuantityById(material.id!!, totalQuantity)
|
|
||||||
assertEquals(totalQuantity, found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAllForMixCreation()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getAllForMixCreation() returns all normal materials and all mix type materials for the given recipe`() {
|
|
||||||
val normalMaterial = material(id = 0L, isMixType = false)
|
|
||||||
val mixTypeMaterial = material(id = 1L, isMixType = true)
|
|
||||||
val anotherMixTypeMaterial = material(id = 2L, isMixType = true)
|
|
||||||
val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial)
|
|
||||||
val recipe =
|
|
||||||
recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial))))
|
|
||||||
|
|
||||||
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
|
|
||||||
doReturn(materials).whenever(logic).getAll()
|
|
||||||
|
|
||||||
val found = logic.getAllForMixCreation(recipe.id!!)
|
|
||||||
|
|
||||||
assertTrue(materialOutputDto(normalMaterial) in found)
|
|
||||||
assertTrue(materialOutputDto(mixTypeMaterial) in found)
|
|
||||||
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAllForMixUpdate()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getAllForMixUpdate() returns all normal materials and all mix type materials for the recipe of the given mix without the mix type of the said mix`() {
|
|
||||||
val normalMaterial = material(id = 0L, isMixType = false)
|
|
||||||
val mixTypeMaterial = material(id = 1L, isMixType = true)
|
|
||||||
val anotherMixTypeMaterial = material(id = 2L, isMixType = true)
|
|
||||||
val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial)
|
|
||||||
val recipe = recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(material = mixTypeMaterial))))
|
|
||||||
val mix = mix(id = 1L, recipe = recipe, mixType = mixType(material = anotherMixTypeMaterial))
|
|
||||||
recipe.mixes.add(mix)
|
|
||||||
|
|
||||||
whenever(mixService.getById(mix.id!!)).doReturn(mix)
|
|
||||||
doReturn(materials).whenever(logic).getAll()
|
|
||||||
|
|
||||||
val found = logic.getAllForMixUpdate(mix.id!!)
|
|
||||||
|
|
||||||
assertTrue(materialOutputDto(normalMaterial) in found)
|
|
||||||
assertTrue(materialOutputDto(mixTypeMaterial) in found)
|
|
||||||
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// delete()
|
|
||||||
|
|
||||||
override fun `delete() deletes in the repository`() {
|
|
||||||
whenCanBeDeleted {
|
|
||||||
super.`delete() deletes in the repository`()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteById()
|
|
||||||
|
|
||||||
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`()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Utility property to check if the identifier of the given [Material] is even. */
|
|
||||||
private val Material.evenId: Boolean
|
|
||||||
get() = this.id!! % 2 == 0L
|
|
||||||
|
|
||||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
|
||||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
|
||||||
|
|
||||||
test()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun materialOutputDto(material: Material) = MaterialOutputDto(
|
|
||||||
id = material.id!!,
|
|
||||||
name = material.name,
|
|
||||||
inventoryQuantity = material.inventoryQuantity,
|
|
||||||
isMixType = material.isMixType,
|
|
||||||
materialType = material.materialType!!,
|
|
||||||
simdutUrl = null
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ class MaterialTypeLogicTest :
|
||||||
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeLogic, MaterialTypeRepository>() {
|
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeLogic, MaterialTypeRepository>() {
|
||||||
override val repository: MaterialTypeRepository = mock()
|
override val repository: MaterialTypeRepository = mock()
|
||||||
private val materialService: MaterialLogic = mock()
|
private val materialService: MaterialLogic = mock()
|
||||||
override val logic: MaterialTypeLogic = spy(DefaultMaterialTypeLogic(repository, materialService))
|
override val logic: MaterialTypeLogic = spy(DefaultMaterialTypeLogic(repository))
|
||||||
override val entity: MaterialType = materialType(id = 0L, name = "material type", prefix = "MAT")
|
override val entity: MaterialType = materialType(id = 0L, name = "material type", prefix = "MAT")
|
||||||
override val anotherEntity: MaterialType = materialType(id = 1L, name = "another material type", prefix = "AMT")
|
override val anotherEntity: MaterialType = materialType(id = 1L, name = "another material type", prefix = "AMT")
|
||||||
override val entityWithEntityName: MaterialType = materialType(2L, name = entity.name, prefix = "EEN")
|
override val entityWithEntityName: MaterialType = materialType(2L, name = entity.name, prefix = "EEN")
|
||||||
|
@ -56,26 +56,6 @@ class MaterialTypeLogicTest :
|
||||||
assertFalse(found)
|
assertFalse(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUsedByMaterial()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `isUsedByMaterial() returns true when materialService_existsByMaterialType() returns true`() {
|
|
||||||
whenever(materialService.existsByMaterialType(entity)).doReturn(true)
|
|
||||||
|
|
||||||
val found = logic.isUsedByMaterial(entity)
|
|
||||||
|
|
||||||
assertTrue(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `isUsedByMaterial() returns false when materialService_existsByMaterialType() returns false`() {
|
|
||||||
whenever(materialService.existsByMaterialType(entity)).doReturn(false)
|
|
||||||
|
|
||||||
val found = logic.isUsedByMaterial(entity)
|
|
||||||
|
|
||||||
assertFalse(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAllSystemTypes()
|
// getAllSystemTypes()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -78,7 +78,7 @@ class MixMaterialLogicTest : AbstractModelServiceTest<MixMaterial, MixMaterialLo
|
||||||
fun `create() creates a mix material according to the given MixUpdateDto`() {
|
fun `create() creates a mix material according to the given MixUpdateDto`() {
|
||||||
val mixMaterialDto = mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1)
|
val mixMaterialDto = mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1)
|
||||||
|
|
||||||
whenever(materialService.getById(mixMaterialDto.materialId)).doAnswer { material(id = it.arguments[0] as Long) }
|
whenever(materialService.getById(mixMaterialDto.materialId)).doAnswer { materialDto(material(id = it.arguments[0] as Long)) }
|
||||||
|
|
||||||
val found = logic.create(mixMaterialDto)
|
val found = logic.create(mixMaterialDto)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue