Tout fonctionne!

This commit is contained in:
FyloZ 2021-04-28 17:50:05 -04:00
parent 64829f74cb
commit ef27e57f47
25 changed files with 722 additions and 615 deletions

View File

@ -1,6 +1,8 @@
package dev.fyloz.colorrecipesexplorer.config
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.ApplicationListener
@ -10,10 +12,13 @@ import org.springframework.core.annotation.Order
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
class InitialDataLoader(
private val materialTypeService: MaterialTypeService,
private val materialTypeProperties: MaterialTypeProperties
class ApplicationReadyListener(
private val materialTypeService: MaterialTypeService,
private val materialTypeProperties: MaterialTypeProperties,
private val creProperties: CreProperties
) : ApplicationListener<ApplicationReadyEvent> {
override fun onApplicationEvent(event: ApplicationReadyEvent) =
materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes)
override fun onApplicationEvent(event: ApplicationReadyEvent) {
materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes)
CRE_PROPERTIES = creProperties
}
}

View File

@ -1,7 +1,5 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
@ -25,139 +23,143 @@ private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit co
@Entity
@Table(name = "employee")
data class Employee(
@Id
override val id: Long,
@Id
override val id: Long,
@Column(name = "first_name")
val firstName: String = "",
@Column(name = "first_name")
val firstName: String = "",
@Column(name = "last_name")
val lastName: String = "",
@Column(name = "last_name")
val lastName: String = "",
@JsonIgnore
val password: String = "",
val password: String = "",
@JsonIgnore
@Column(name = "default_group_user")
val isDefaultGroupUser: Boolean = false,
@Column(name = "default_group_user")
val isDefaultGroupUser: Boolean = false,
@JsonIgnore
@Column(name = "system_user")
val isSystemUser: Boolean = false,
@Column(name = "system_user")
val isSystemUser: Boolean = false,
@ManyToOne
@JoinColumn(name = "group_id")
@Fetch(FetchMode.SELECT)
var group: EmployeeGroup? = null,
@ManyToOne
@JoinColumn(name = "group_id")
@Fetch(FetchMode.SELECT)
var group: EmployeeGroup? = null,
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
@get:JsonProperty("explicitPermissions")
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
@Column(name = "last_login_time")
var lastLoginTime: LocalDateTime? = null
) : Model {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = permissions
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
.flatMap { it.flat() }
.filter { !it.deprecated }
.toMutableSet()
.apply {
if (group != null) this.addAll(group!!.flatPermissions)
}
@get:JsonIgnore
val authorities: Set<GrantedAuthority>
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
}
/** DTO for creating employees. Allows a [password] a [groupId]. */
open class EmployeeSaveDto(
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
val id: Long,
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
val id: Long,
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String,
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String,
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String,
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String,
@field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE)
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
val password: String,
@field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE)
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
val password: String,
val groupId: Long?,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
@Enumerated(EnumType.STRING)
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
) : EntityDto<Employee>
open class EmployeeUpdateDto(
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
val id: Long,
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
val id: Long,
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String?,
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
val firstName: String?,
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
val lastName: String?,
val groupId: Long?,
val groupId: Long?,
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission>?
@Enumerated(EnumType.STRING)
val permissions: Set<EmployeePermission>?
) : EntityDto<Employee>
data class EmployeeOutputDto(
override val id: Long,
val firstName: String,
val lastName: String,
val group: EmployeeGroup?,
val permissions: Set<EmployeePermission>,
val explicitPermissions: Set<EmployeePermission>,
val lastLoginTime: LocalDateTime?
) : Model
data class EmployeeLoginRequest(val id: Long, val password: String)
// ==== DSL ====
fun employee(
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
password: String = passwordEncoder.encode("password"),
isDefaultGroupUser: Boolean = false,
isSystemUser: Boolean = false,
group: EmployeeGroup? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
lastLoginTime: LocalDateTime? = null,
op: Employee.() -> Unit = {}
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
password: String = passwordEncoder.encode("password"),
isDefaultGroupUser: Boolean = false,
isSystemUser: Boolean = false,
group: EmployeeGroup? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
lastLoginTime: LocalDateTime? = null,
op: Employee.() -> Unit = {}
) = Employee(
id,
firstName,
lastName,
password,
isDefaultGroupUser,
isSystemUser,
group,
permissions,
lastLoginTime
id,
firstName,
lastName,
password,
isDefaultGroupUser,
isSystemUser,
group,
permissions,
lastLoginTime
).apply(op)
fun employeeSaveDto(
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
password: String = passwordEncoder.encode("password"),
groupId: Long? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeSaveDto.() -> Unit = {}
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
password: String = passwordEncoder.encode("password"),
groupId: Long? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeSaveDto.() -> Unit = {}
) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
fun employeeUpdateDto(
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
groupId: Long? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeUpdateDto.() -> Unit = {}
id: Long = 0L,
firstName: String = "firstName",
lastName: String = "lastName",
groupId: Long? = null,
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
op: EmployeeUpdateDto.() -> Unit = {}
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
// ==== Exceptions ====
@ -166,26 +168,26 @@ private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already ex
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee"
fun employeeIdNotFoundException(id: Long) =
NotFoundException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
"An employee with the id $id could not be found",
id
)
NotFoundException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
"An employee with the id $id could not be found",
id
)
fun employeeIdAlreadyExistsException(id: Long) =
AlreadyExistsException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
"An employee with the id $id already exists",
id
)
AlreadyExistsException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
"An employee with the id $id already exists",
id
)
fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) =
AlreadyExistsException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
"An employee with the name '$firstName $lastName' already exists",
"$firstName $lastName",
"fullName"
)
AlreadyExistsException(
EMPLOYEE_EXCEPTION_ERROR_CODE,
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
"An employee with the name '$firstName $lastName' already exists",
"$firstName $lastName",
"fullName"
)

View File

@ -31,10 +31,8 @@ data class EmployeeGroup(
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
@Column(name = "permission")
@Fetch(FetchMode.SUBSELECT)
@get:JsonProperty("explicitPermissions")
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
) : NamedModel {
@get:JsonProperty("permissions")
val flatPermissions: Set<EmployeePermission>
get() = this.permissions
.flatMap { it.flat() }
@ -69,6 +67,13 @@ open class EmployeeGroupUpdateDto(
EmployeeGroup(id, name, permissions)
}
data class EmployeeGroupOutputDto(
override val id: Long,
val name: String,
val permissions: Set<EmployeePermission>,
val explicitPermissions: Set<EmployeePermission>
): Model
fun employeeGroup(
id: Long? = null,
name: String = "name",

View File

@ -6,8 +6,11 @@ import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
import org.springframework.web.multipart.MultipartFile
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
@ -49,6 +52,8 @@ data class Material(
@JsonIgnore
@Transient
get() = "$SIMDUT_FILES_PATH/$name.pdf"
}
open class MaterialSaveDto(
@ -81,13 +86,13 @@ open class MaterialUpdateDto(
) : EntityDto<Material>
data class MaterialOutputDto(
val id: Long,
override val id: Long,
val name: String,
val inventoryQuantity: Float,
val isMixType: Boolean,
val materialType: MaterialType,
val simdutUrl: String?
)
) : Model
data class MaterialQuantityDto(
@field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE)
@ -135,19 +140,6 @@ fun materialUpdateDto(
op: MaterialUpdateDto.() -> Unit = {}
) = MaterialUpdateDto(id, name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
fun materialOutputDto(
material: Material,
simdutUrl: String?,
op: MaterialOutputDto.() -> Unit = {}
) = MaterialOutputDto(
id = material.id!!,
name = material.name,
inventoryQuantity = material.inventoryQuantity,
isMixType = material.isMixType,
materialType = material.materialType!!,
simdutUrl = simdutUrl
).apply(op)
fun materialQuantityDto(
materialId: Long,
quantity: Float,
@ -155,7 +147,8 @@ fun materialQuantityDto(
) = MaterialQuantityDto(materialId, quantity).apply(op)
// ==== Exceptions ====
private const val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
private const
val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists"
private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material"
private const val MATERIAL_EXCEPTION_ERROR_CODE = "material"

View File

@ -22,107 +22,116 @@ private const val MIX_DEDUCT_RATION_NEGATIVE_MESSAGE = "Le ratio doit être éga
@Entity
@Table(name = "mix")
data class Mix(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override val id: Long?,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override val id: Long?,
var location: String?,
var location: String?,
@JsonIgnore
@ManyToOne
@JoinColumn(name = "recipe_id")
val recipe: Recipe,
@JsonIgnore
@ManyToOne
@JoinColumn(name = "recipe_id")
val recipe: Recipe,
@ManyToOne
@JoinColumn(name = "mix_type_id")
var mixType: MixType,
@ManyToOne
@JoinColumn(name = "mix_type_id")
var mixType: MixType,
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "mix_id")
var mixMaterials: MutableSet<MixMaterial>,
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "mix_id")
var mixMaterials: MutableSet<MixMaterial>,
) : Model
open class MixSaveDto(
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)
val name: String,
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)
val name: String,
@field:NotNull(message = MIX_RECIPE_NULL_MESSAGE)
val recipeId: Long,
@field:NotNull(message = MIX_RECIPE_NULL_MESSAGE)
val recipeId: Long,
@field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE)
val materialTypeId: Long,
@field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE)
val materialTypeId: Long,
val mixMaterials: Set<MixMaterialDto>?
val mixMaterials: Set<MixMaterialDto>?
) : EntityDto<Mix> {
override fun toEntity(): Mix = throw UnsupportedOperationException()
}
open class MixUpdateDto(
@field:NotNull(message = MIX_ID_NULL_MESSAGE)
val id: Long,
@field:NotNull(message = MIX_ID_NULL_MESSAGE)
val id: Long,
@field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE)
val name: String?,
@field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE)
val name: String?,
val materialTypeId: Long?,
val materialTypeId: Long?,
var mixMaterials: Set<MixMaterialDto>?
var mixMaterials: Set<MixMaterialDto>?
) : EntityDto<Mix> {
override fun toEntity(): Mix = throw UnsupportedOperationException()
}
data class MixDeductDto(
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
val id: Long,
data class MixOutputDto(
val id: Long,
val location: String?,
val mixType: MixType,
val mixMaterials: Set<MixMaterialOutputDto>
)
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
val ratio: Float
data class MixDeductDto(
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
val id: Long,
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
val ratio: Float
)
data class MixLocationDto(
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
val mixId: Long,
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
val mixId: Long,
val location: String?
val location: String?
)
//fun Mix.toOutput() =
// ==== DSL ====
fun mix(
id: Long? = null,
location: String? = "location",
recipe: Recipe = recipe(),
mixType: MixType = mixType(),
mixMaterials: MutableSet<MixMaterial> = mutableSetOf(),
op: Mix.() -> Unit = {}
id: Long? = null,
location: String? = "location",
recipe: Recipe = recipe(),
mixType: MixType = mixType(),
mixMaterials: MutableSet<MixMaterial> = mutableSetOf(),
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 = {}
name: String = "name",
recipeId: Long = 0L,
materialTypeId: Long = 0L,
mixMaterials: Set<MixMaterialDto>? = setOf(),
op: MixSaveDto.() -> Unit = {}
) = MixSaveDto(name, recipeId, materialTypeId, mixMaterials).apply(op)
fun mixUpdateDto(
id: Long = 0L,
name: String? = "name",
materialTypeId: Long? = 0L,
mixMaterials: Set<MixMaterialDto>? = setOf(),
op: MixUpdateDto.() -> Unit = {}
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 = {}
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 = {}
mixId: Long = 0L,
location: String? = "location",
op: MixLocationDto.() -> Unit = {}
) = MixLocationDto(mixId, location).apply(op)
// ==== Exceptions ====
@ -132,24 +141,24 @@ 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
)
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
)
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"
)
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"
)

View File

@ -13,44 +13,51 @@ private const val MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE = "La quantité ne
@Entity
@Table(name = "mix_material")
data class MixMaterial(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override val id: Long?,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
override val id: Long?,
@ManyToOne
@JoinColumn(name = "material_id")
val material: Material,
@ManyToOne
@JoinColumn(name = "material_id")
val material: Material,
var quantity: Float,
var quantity: Float,
var position: Int
var position: Int
) : Model
data class MixMaterialOutputDto(
val id: Long,
val material: MaterialOutputDto,
val quantity: Float,
val position: Int
)
data class MixMaterialDto(
@field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE)
val materialId: Long,
@field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE)
val materialId: Long,
@field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE)
@field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE)
val quantity: Float,
@field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE)
@field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE)
val quantity: Float,
val position: Int
val position: Int
)
// ==== DSL ====
fun mixMaterial(
id: Long? = null,
material: Material = material(),
quantity: Float = 0f,
position: Int = 0,
op: MixMaterial.() -> Unit = {}
id: Long? = null,
material: Material = material(),
quantity: Float = 0f,
position: Int = 0,
op: MixMaterial.() -> Unit = {}
) = MixMaterial(id, material, quantity, position).apply(op)
fun mixMaterialDto(
materialId: Long = 0L,
quantity: Float = 0f,
position: Int = 0,
op: MixMaterialDto.() -> Unit = {}
materialId: Long = 0L,
quantity: Float = 0f,
position: Int = 0,
op: MixMaterialDto.() -> Unit = {}
) = MixMaterialDto(materialId, quantity, position).apply(op)
// ==== Exceptions ====
@ -59,17 +66,17 @@ private const val MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix material al
private const val MIX_MATERIAL_EXCEPTION_ERROR_CODE = "mixmaterial"
fun mixMaterialIdNotFoundException(id: Long) =
NotFoundException(
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
"A mix material with the id $id could not be found",
id
)
NotFoundException(
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
"A mix material with the id $id could not be found",
id
)
fun mixMaterialIdAlreadyExistsException(id: Long) =
AlreadyExistsException(
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
"A mix material with the id $id already exists",
id
)
AlreadyExistsException(
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
"A mix material with the id $id already exists",
id
)

View File

@ -3,10 +3,12 @@ package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
import org.springframework.http.HttpStatus
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
@ -76,6 +78,14 @@ data class Recipe(
fun groupInformationForGroup(groupId: Long) =
groupsInformation.firstOrNull { it.group.id == groupId }
fun imageUrl(name: String) =
"${CRE_PROPERTIES.deploymentUrl}$FILE_CONTROLLER_PATH?path=${
URLEncoder.encode(
"${this.imagesDirectoryPath}/$name",
StandardCharsets.UTF_8
)
}"
}
open class RecipeSaveDto(
@ -143,7 +153,7 @@ open class RecipeUpdateDto(
) : EntityDto<Recipe>
data class RecipeOutputDto(
val id: Long,
override val id: Long,
val name: String,
val description: String,
val color: String,
@ -152,10 +162,10 @@ data class RecipeOutputDto(
val approbationDate: LocalDate?,
val remark: String?,
val company: Company,
val mixes: Set<Mix>,
val mixes: Set<MixOutputDto>,
val groupsInformation: Set<RecipeGroupInformation>,
val imagesUrls: Set<String>
)
var imagesUrls: Set<String>
) : Model
@Entity
@Table(name = "recipe_group_information")
@ -252,25 +262,6 @@ fun recipeUpdateDto(
op: RecipeUpdateDto.() -> Unit = {}
) = RecipeUpdateDto(id, name, description, color, gloss, sample, approbationDate, remark, steps).apply(op)
fun recipeOutputDto(
recipe: Recipe,
imagesUrls: Set<String>,
op: RecipeOutputDto.() -> Unit = {}
) = RecipeOutputDto(
recipe.id!!,
recipe.name,
recipe.description,
recipe.color,
recipe.gloss,
recipe.sample,
recipe.approbationDate,
recipe.remark,
recipe.company,
recipe.mixes.toSet(),
recipe.groupsInformation,
imagesUrls
).apply(op)
fun recipeGroupInformation(
id: Long? = null,
group: EmployeeGroup = employeeGroup(),

View File

@ -4,7 +4,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupServiceImpl
import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupService
import dev.fyloz.colorrecipesexplorer.service.EmployeeService
import org.springframework.http.MediaType
import org.springframework.security.access.prepost.PreAuthorize
@ -23,52 +23,56 @@ class EmployeeController(private val employeeService: EmployeeService) {
@GetMapping
@PreAuthorizeViewUsers
fun getAll() =
ok(employeeService.getAll())
ok(employeeService.getAllForOutput())
@GetMapping("{id}")
@PreAuthorizeViewUsers
fun getById(@PathVariable id: Long) =
ok(employeeService.getById(id))
ok(employeeService.getByIdForOutput(id))
@GetMapping("current")
fun getCurrent(loggedInEmployee: Principal?) =
if (loggedInEmployee != null)
ok(
employeeService.getById(
loggedInEmployee.name.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = false
if (loggedInEmployee != null)
ok(
with(employeeService) {
getById(
loggedInEmployee.name.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = false
).toOutput()
}
)
)
else
forbidden()
else
forbidden()
@PostMapping
@PreAuthorizeEditUsers
fun save(@Valid @RequestBody employee: EmployeeSaveDto) =
created<Employee>(EMPLOYEE_CONTROLLER_PATH) {
employeeService.save(employee)
}
created<EmployeeOutputDto>(EMPLOYEE_CONTROLLER_PATH) {
with(employeeService) {
save(employee).toOutput()
}
}
@PutMapping
@PreAuthorizeEditUsers
fun update(@Valid @RequestBody employee: EmployeeUpdateDto) =
noContent {
employeeService.update(employee)
}
noContent {
employeeService.update(employee)
}
@PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE])
@PreAuthorizeEditUsers
fun updatePassword(@PathVariable id: Long, @RequestBody password: String) =
noContent {
employeeService.updatePassword(id, password)
}
noContent {
employeeService.updatePassword(id, password)
}
@PutMapping("{employeeId}/permissions/{permission}")
@PreAuthorizeEditUsers
fun addPermission(
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
) = noContent {
employeeService.addPermission(employeeId, permission)
}
@ -76,8 +80,8 @@ class EmployeeController(private val employeeService: EmployeeService) {
@DeleteMapping("{employeeId}/permissions/{permission}")
@PreAuthorizeEditUsers
fun removePermission(
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
@PathVariable employeeId: Long,
@PathVariable permission: EmployeePermission
) = noContent {
employeeService.removePermission(employeeId, permission)
}
@ -85,59 +89,69 @@ class EmployeeController(private val employeeService: EmployeeService) {
@DeleteMapping("{id}")
@PreAuthorizeRemoveUsers
fun deleteById(@PathVariable id: Long) =
employeeService.deleteById(id)
employeeService.deleteById(id)
}
@RestController
@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH)
class GroupsController(private val groupService: EmployeeGroupServiceImpl) {
class GroupsController(
private val groupService: EmployeeGroupService,
private val employeeService: EmployeeService
) {
@GetMapping
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
fun getAll() =
ok(groupService.getAll())
ok(groupService.getAllForOutput())
@GetMapping("{id}")
@PreAuthorizeViewUsers
fun getById(@PathVariable id: Long) =
ok(groupService.getById(id))
ok(groupService.getByIdForOutput(id))
@GetMapping("{id}/employees")
@PreAuthorizeViewUsers
fun getEmployeesForGroup(@PathVariable id: Long) =
ok(groupService.getEmployeesForGroup(id))
ok(with(employeeService) {
groupService.getEmployeesForGroup(id)
.map { it.toOutput() }
})
@PostMapping("default/{groupId}")
@PreAuthorizeViewUsers
fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) =
noContent {
groupService.setResponseDefaultGroup(groupId, response)
}
noContent {
groupService.setResponseDefaultGroup(groupId, response)
}
@GetMapping("default")
@PreAuthorizeViewUsers
fun getRequestDefaultGroup(request: HttpServletRequest) =
ok(groupService.getRequestDefaultGroup(request))
ok(with(groupService) {
getRequestDefaultGroup(request).toOutput()
})
@PostMapping
@PreAuthorizeEditUsers
fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) =
created<EmployeeGroup>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
groupService.save(group)
}
created<EmployeeGroupOutputDto>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
with(groupService) {
save(group).toOutput()
}
}
@PutMapping
@PreAuthorizeEditUsers
fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) =
noContent {
groupService.update(group)
}
noContent {
groupService.update(group)
}
@DeleteMapping("{id}")
@PreAuthorizeRemoveUsers
fun deleteById(@PathVariable id: Long) =
noContent {
groupService.deleteById(id)
}
noContent {
groupService.deleteById(id)
}
}
@RestController
@ -145,7 +159,7 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) {
class LogoutController(private val employeeService: EmployeeService) {
@GetMapping("logout")
fun logout(request: HttpServletRequest) =
ok<Void> {
employeeService.logout(request)
}
ok<Void> {
employeeService.logout(request)
}
}

View File

@ -17,11 +17,11 @@ private const val COMPANY_CONTROLLER_PATH = "api/company"
class CompanyController(private val companyService: CompanyService) {
@GetMapping
fun getAll() =
ok(companyService.getAll())
ok(companyService.getAllForOutput())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(companyService.getById(id))
ok(companyService.getByIdForOutput(id))
@PostMapping
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")

View File

@ -15,21 +15,20 @@ private const val INVENTORY_CONTROLLER_PATH = "api/inventory"
@RestController
@RequestMapping(INVENTORY_CONTROLLER_PATH)
class InventoryController(
private val inventoryService: InventoryService
private val inventoryService: InventoryService
) {
@PutMapping("add")
@PreAuthorize("hasAuthority('ADD_TO_INVENTORY')")
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Collection<MaterialQuantityDto>> {
return ResponseEntity.ok(inventoryService.add(quantities))
}
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>) =
ok(inventoryService.add(quantities))
@PutMapping("deduct")
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>) =
ok(inventoryService.deduct(quantities))
ok(inventoryService.deduct(quantities))
@PutMapping("deduct/mix")
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
fun deduct(@RequestBody mixRatio: MixDeductDto) =
ok(inventoryService.deductMix(mixRatio))
ok(inventoryService.deductMix(mixRatio))
}

View File

@ -3,7 +3,6 @@ package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
import dev.fyloz.colorrecipesexplorer.service.MaterialService
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
@ -11,8 +10,6 @@ import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import javax.validation.Valid
private const val MATERIAL_CONTROLLER_PATH = "api/material"
@ -21,12 +18,11 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material"
@RequestMapping(MATERIAL_CONTROLLER_PATH)
@PreAuthorizeViewCatalog
class MaterialController(
private val materialService: MaterialService,
private val creProperties: CreProperties
private val materialService: MaterialService
) {
@GetMapping
fun getAll() =
ok(materialService.getAll())
ok(materialService.getAllForOutput())
@GetMapping("notmixtype")
fun getAllNotMixType() =
@ -34,20 +30,22 @@ class MaterialController(
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(materialService.getById(id))
ok(materialService.getByIdForOutput(id))
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
created {
materialService.save(
materialSaveDto(
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
)
created<MaterialOutputDto>(MATERIAL_CONTROLLER_PATH) {
with(materialService) {
save(
materialSaveDto(
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
).toOutput()
}
}
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@ -79,30 +77,5 @@ class MaterialController(
@GetMapping("mix/update/{mixId}")
fun getAllForMixUpdate(@PathVariable mixId: Long) =
ok(materialService.getAllForMixUpdate(mixId))
private fun ok(material: Material) =
ok(material.toOutput())
private fun ok(materials: Collection<Material>) =
ok(materials.map { it.toOutput() })
private fun created(producer: () -> Material): ResponseEntity<MaterialOutputDto> = with(producer().toOutput()) {
ResponseEntity
.created(URI.create("$MATERIAL_CONTROLLER_PATH/${this.id}"))
.body(this)
}
private fun Material.toOutput() = materialOutputDto(
this,
if (materialService.hasSimdut(this)) this.simdutUrl() else null
)
private fun Material.simdutUrl() =
"${creProperties.deploymentUrl}$FILE_CONTROLLER_PATH?path=${
URLEncoder.encode(
this.simdutFilePath,
StandardCharsets.UTF_8
)
}"
}

View File

@ -17,11 +17,11 @@ private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype"
class MaterialTypeController(private val materialTypeService: MaterialTypeService) {
@GetMapping
fun getAll() =
ok(materialTypeService.getAll())
ok(materialTypeService.getAllForOutput())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(materialTypeService.getById(id))
ok(materialTypeService.getByIdForOutput(id))
@PostMapping
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")

View File

@ -27,22 +27,23 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix"
@PreAuthorizeViewRecipes
class RecipeController(
private val recipeService: RecipeService,
private val recipeImageService: RecipeImageService,
private val creProperties: CreProperties
private val recipeImageService: RecipeImageService
) {
@GetMapping
fun getAll() =
ok(recipeService.getAll())
ok(recipeService.getAllForOutput())
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(recipeService.getById(id))
ok(recipeService.getByIdForOutput(id))
@PostMapping
@PreAuthorizeEditRecipes
fun save(@Valid @RequestBody recipe: RecipeSaveDto) =
created<Recipe>(RECIPE_CONTROLLER_PATH) {
recipeService.save(recipe)
created<RecipeOutputDto>(RECIPE_CONTROLLER_PATH) {
with(recipeService) {
save(recipe).toOutput()
}
}
@PutMapping
@ -66,7 +67,7 @@ class RecipeController(
recipeService.deleteById(id)
}
@PutMapping("{recipeId}/image", consumes = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
@PutMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorizeEditRecipes
fun downloadImage(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity<RecipeOutputDto> {
recipeImageService.download(image, recipeService.getById(recipeId))
@ -79,28 +80,6 @@ class RecipeController(
noContent {
recipeImageService.delete(recipeService.getById(recipeId), name)
}
private fun ok(recipe: Recipe) =
ok(recipe.toOutput())
private fun ok(recipes: Collection<Recipe>) =
ok(recipes.map { it.toOutput() })
private fun Recipe.toOutput() =
recipeOutputDto(
this,
recipeImageService.getAllImages(this)
.map { this.imageUrl(it) }
.toSet()
)
private fun Recipe.imageUrl(name: String) =
"${creProperties.deploymentUrl}$FILE_CONTROLLER_PATH?path=${
URLEncoder.encode(
"${this.imagesDirectoryPath}/$name",
StandardCharsets.UTF_8
)
}"
}
@RestController
@ -109,7 +88,7 @@ class RecipeController(
class MixController(private val mixService: MixService) {
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(mixService.getById(id))
ok(mixService.getByIdForOutput(id))
@PostMapping
@PreAuthorizeEditRecipes

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
import dev.fyloz.colorrecipesexplorer.model.Model
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
@ -7,6 +8,8 @@ import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import java.net.URI
lateinit var CRE_PROPERTIES: CreProperties
/** Creates a HTTP OK [ResponseEntity] from the given [body]. */
fun <T> ok(body: T): ResponseEntity<T> =
ResponseEntity.ok(body)

View File

@ -20,7 +20,8 @@ import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.transaction.Transactional
interface EmployeeService : ExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeRepository> {
interface EmployeeService :
ExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeOutputDto, EmployeeRepository> {
/** Check if an [Employee] with the given [firstName] and [lastName] exists. */
fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean
@ -56,7 +57,7 @@ interface EmployeeService : ExternalModelService<Employee, EmployeeSaveDto, Empl
}
interface EmployeeGroupService :
ExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository> {
ExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupOutputDto, EmployeeGroupRepository> {
/** Gets all the employees of the group with the given [id]. */
fun getEmployeesForGroup(id: Long): Collection<Employee>
@ -74,50 +75,62 @@ interface EmployeeUserDetailsService : UserDetailsService {
@Service
class EmployeeServiceImpl(
employeeRepository: EmployeeRepository,
@Lazy val groupService: EmployeeGroupService,
@Lazy val passwordEncoder: PasswordEncoder,
) : AbstractExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeRepository>(employeeRepository),
EmployeeService {
employeeRepository: EmployeeRepository,
@Lazy val groupService: EmployeeGroupService,
@Lazy val passwordEncoder: PasswordEncoder,
) : AbstractExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeOutputDto, EmployeeRepository>(
employeeRepository
),
EmployeeService {
override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id)
override fun Employee.toOutput() = EmployeeOutputDto(
this.id,
this.firstName,
this.lastName,
this.group,
this.flatPermissions,
this.permissions,
this.lastLoginTime
)
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
repository.existsByFirstNameAndLastName(firstName, lastName)
repository.existsByFirstNameAndLastName(firstName, lastName)
override fun getAll(): Collection<Employee> =
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
override fun getById(id: Long): Employee =
getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee =
super.getById(id).apply {
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
throw idNotFoundException(id)
}
super.getById(id).apply {
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
throw idNotFoundException(id)
}
override fun getByGroup(group: EmployeeGroup): Collection<Employee> =
repository.findAllByGroup(group).filter {
!it.isSystemUser && !it.isDefaultGroupUser
}
repository.findAllByGroup(group).filter {
!it.isSystemUser && !it.isDefaultGroupUser
}
override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee =
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
override fun save(entity: EmployeeSaveDto): Employee =
save(with(entity) {
Employee(
id,
firstName,
lastName,
passwordEncoder.encode(password),
isDefaultGroupUser = false,
isSystemUser = false,
group = if (groupId != null) groupService.getById(groupId) else null,
permissions = permissions
)
})
save(with(entity) {
Employee(
id,
firstName,
lastName,
passwordEncoder.encode(password),
isDefaultGroupUser = false,
isSystemUser = false,
group = if (groupId != null) groupService.getById(groupId) else null,
permissions = permissions
)
})
override fun save(entity: Employee): Employee {
if (existsById(entity.id))
@ -129,14 +142,14 @@ class EmployeeServiceImpl(
override fun saveDefaultGroupEmployee(group: EmployeeGroup) {
save(
employee(
id = 1000000L + group.id!!,
firstName = group.name,
lastName = "EmployeeModel",
password = passwordEncoder.encode(group.name),
group = group,
isDefaultGroupUser = true
)
employee(
id = 1000000L + group.id!!,
firstName = group.name,
lastName = "EmployeeModel",
password = passwordEncoder.encode(group.name),
group = group,
isDefaultGroupUser = true
)
)
}
@ -144,9 +157,9 @@ class EmployeeServiceImpl(
val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
employee.lastLoginTime = time
return update(
employee,
ignoreDefaultGroupUsers = true,
ignoreSystemUsers = false
employee,
ignoreDefaultGroupUsers = true,
ignoreSystemUsers = false
)
}
@ -154,21 +167,21 @@ class EmployeeServiceImpl(
val persistedEmployee by lazy { getById(entity.id) }
return update(with(entity) {
Employee(
id = id,
firstName = firstName or persistedEmployee.firstName,
lastName = lastName or persistedEmployee.lastName,
password = persistedEmployee.password,
isDefaultGroupUser = false,
isSystemUser = false,
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
lastLoginTime = persistedEmployee.lastLoginTime
id = id,
firstName = firstName or persistedEmployee.firstName,
lastName = lastName or persistedEmployee.lastName,
password = persistedEmployee.password,
isDefaultGroupUser = false,
isSystemUser = false,
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
lastLoginTime = persistedEmployee.lastLoginTime
)
})
}
override fun update(entity: Employee): Employee =
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee {
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
@ -183,24 +196,24 @@ class EmployeeServiceImpl(
val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
return super<AbstractExternalModelService>.update(with(persistedEmployee) {
Employee(
id,
firstName,
lastName,
passwordEncoder.encode(password),
isDefaultGroupUser,
isSystemUser,
group,
permissions,
lastLoginTime
id,
firstName,
lastName,
passwordEncoder.encode(password),
isDefaultGroupUser,
isSystemUser,
group,
permissions,
lastLoginTime
)
})
}
override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee =
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions += permission })
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions += permission })
override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee =
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions -= permission })
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions -= permission })
override fun logout(request: HttpServletRequest) {
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
@ -217,20 +230,27 @@ const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
@Service
class EmployeeGroupServiceImpl(
private val employeeService: EmployeeService,
employeeGroupRepository: EmployeeGroupRepository
) : AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository>(
employeeGroupRepository
private val employeeService: EmployeeService,
employeeGroupRepository: EmployeeGroupRepository
) : AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupOutputDto, EmployeeGroupRepository>(
employeeGroupRepository
),
EmployeeGroupService {
EmployeeGroupService {
override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id)
override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name)
override fun EmployeeGroup.toOutput() = EmployeeGroupOutputDto(
this.id!!,
this.name,
this.permissions,
this.flatPermissions
)
override fun existsByName(name: String): Boolean = repository.existsByName(name)
override fun getEmployeesForGroup(id: Long): Collection<Employee> =
employeeService.getByGroup(getById(id))
employeeService.getByGroup(getById(id))
@Transactional
override fun save(entity: EmployeeGroup): EmployeeGroup {
@ -243,9 +263,9 @@ class EmployeeGroupServiceImpl(
val persistedGroup by lazy { getById(entity.id) }
return update(with(entity) {
EmployeeGroup(
entity.id,
if (name.isNotBlank()) entity.name else persistedGroup.name,
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
entity.id,
if (name.isNotBlank()) entity.name else persistedGroup.name,
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
)
})
}
@ -258,11 +278,11 @@ class EmployeeGroupServiceImpl(
override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup {
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
?: throw NoDefaultGroupException()
?: throw NoDefaultGroupException()
val defaultGroupUser = employeeService.getById(
defaultGroupCookie.value.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = true
defaultGroupCookie.value.toLong(),
ignoreDefaultGroupUsers = false,
ignoreSystemUsers = true
)
return defaultGroupUser.group!!
}
@ -271,17 +291,17 @@ class EmployeeGroupServiceImpl(
val group = getById(groupId)
val defaultGroupUser = employeeService.getDefaultGroupEmployee(group)
response.addHeader(
"Set-Cookie",
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
"Set-Cookie",
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
)
}
}
@Service
class EmployeeUserDetailsServiceImpl(
private val employeeService: EmployeeService
private val employeeService: EmployeeService
) :
EmployeeUserDetailsService {
EmployeeUserDetailsService {
override fun loadUserByUsername(username: String): UserDetails {
try {
return loadUserByEmployeeId(username.toLong(), true)
@ -294,9 +314,9 @@ class EmployeeUserDetailsServiceImpl(
override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
val employee = employeeService.getById(
employeeId,
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
ignoreSystemUsers = false
employeeId,
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
ignoreSystemUsers = false
)
return User(employee.id.toString(), employee.password, employee.authorities)
}

View File

@ -5,23 +5,28 @@ import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
interface CompanyService : ExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository> {
interface CompanyService :
ExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, Company, CompanyRepository> {
/** Checks if the given [company] is used by one or more recipes. */
fun isLinkedToRecipes(company: Company): Boolean
}
@Service
class CompanyServiceImpl(
companyRepository: CompanyRepository,
@Lazy val recipeService: RecipeService
companyRepository: CompanyRepository,
@Lazy val recipeService: RecipeService
) :
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository>(companyRepository),
CompanyService {
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, Company, CompanyRepository>(
companyRepository
),
CompanyService {
override fun idNotFoundException(id: Long) = companyIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = companyIdAlreadyExistsException(id)
override fun nameNotFoundException(name: String) = companyNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = companyNameAlreadyExistsException(name)
override fun Company.toOutput() = this
override fun isLinkedToRecipes(company: Company): Boolean = recipeService.existsByCompany(company)
override fun update(entity: CompanyUpdateDto): Company {
@ -30,8 +35,8 @@ class CompanyServiceImpl(
return update(with(entity) {
company(
id = id,
name = if (name != null && name.isNotBlank()) name else persistedCompany.name
id = id,
name = if (name != null && name.isNotBlank()) name else persistedCompany.name
)
})
}

View File

@ -2,13 +2,17 @@ package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
import dev.fyloz.colorrecipesexplorer.service.files.FileService
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 MaterialService :
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository> {
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository> {
/** Checks if a material with the given [materialType] exists. */
fun existsByMaterialType(materialType: MaterialType): Boolean
@ -16,13 +20,13 @@ interface MaterialService :
fun hasSimdut(material: Material): Boolean
/** Gets all materials that are not a mix type. */
fun getAllNotMixType(): Collection<Material>
fun getAllNotMixType(): 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 [MixType]s included in the said recipe. */
fun getAllForMixCreation(recipeId: Long): Collection<Material>
fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto>
/** 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<Material>
fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto>
/** Updates the quantity of the given [material] with the given [factor] and returns the updated quantity. */
fun updateQuantity(material: Material, factor: Float): Float
@ -36,7 +40,7 @@ class MaterialServiceImpl(
@Lazy val materialTypeService: MaterialTypeService,
val fileService: FileService
) :
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository>(
materialRepository
),
MaterialService {
@ -45,11 +49,28 @@ class MaterialServiceImpl(
override fun nameNotFoundException(name: String) = materialNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name)
override fun Material.toOutput(): MaterialOutputDto =
MaterialOutputDto(
id = this.id!!,
name = this.name,
inventoryQuantity = this.inventoryQuantity,
isMixType = this.isMixType,
materialType = this.materialType!!,
simdutUrl = if (fileService.exists(this.simdutFilePath))
"${CRE_PROPERTIES.deploymentUrl}$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<Material> = getAll().filter { !it.isMixType }
override fun getAllNotMixType(): Collection<MaterialOutputDto> = getAllForOutput().filter { !it.isMixType }
override fun save(entity: MaterialSaveDto): Material =
save(with(entity) {
@ -95,16 +116,16 @@ class MaterialServiceImpl(
updatedQuantity
}
override fun getAllForMixCreation(recipeId: Long): Collection<Material> {
override fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto> {
val recipesMixTypes = recipeService.getById(recipeId).mixTypes
return getAll()
return getAllForOutput()
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
}
override fun getAllForMixUpdate(mixId: Long): Collection<Material> {
override fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto> {
val mix = mixService.getById(mixId)
val recipesMixTypes = mix.recipe.mixTypes
return getAll()
return getAllForOutput()
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
.filter { it.id != mix.mixType.material.id }
}

View File

@ -8,7 +8,7 @@ import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
import org.springframework.stereotype.Service
interface MaterialTypeService :
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository> {
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository> {
/** Checks if a material type with the given [prefix] exists. */
fun existsByPrefix(prefix: String): Boolean
@ -27,7 +27,7 @@ interface MaterialTypeService :
@Service
class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val materialService: MaterialService) :
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository>(
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
repository
), MaterialTypeService {
override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id)
@ -35,6 +35,8 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = materialTypeNameAlreadyExistsException(name)
override fun MaterialType.toOutput() = this
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
materialService.existsByMaterialType(materialType)

View File

@ -28,31 +28,40 @@ interface MixMaterialService : ModelService<MixMaterial, MixMaterialRepository>
* If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown.
*/
fun validateMixMaterials(mixMaterials: Set<MixMaterial>)
fun MixMaterial.toOutput(): MixMaterialOutputDto
}
@Service
class MixMaterialServiceImpl(
mixMaterialRepository: MixMaterialRepository,
@Lazy val materialService: MaterialService
mixMaterialRepository: MixMaterialRepository,
@Lazy val materialService: MaterialService
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialService {
override fun idNotFoundException(id: Long) = mixMaterialIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = mixMaterialIdAlreadyExistsException(id)
override fun MixMaterial.toOutput() = MixMaterialOutputDto(
this.id!!,
with(materialService) { this@toOutput.material.toOutput() },
this.quantity,
this.position
)
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
override fun create(mixMaterials: Set<MixMaterialDto>): Set<MixMaterial> =
mixMaterials.map(::create).toSet()
mixMaterials.map(::create).toSet()
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
mixMaterial(
material = materialService.getById(mixMaterial.materialId),
quantity = mixMaterial.quantity,
position = mixMaterial.position
)
mixMaterial(
material = materialService.getById(mixMaterial.materialId),
quantity = mixMaterial.quantity,
position = mixMaterial.position
)
override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) =
update(mixMaterial.apply {
this.quantity = quantity
})
update(mixMaterial.apply {
this.quantity = quantity
})
override fun validateMixMaterials(mixMaterials: Set<MixMaterial>) {
if (mixMaterials.isEmpty()) return
@ -63,17 +72,17 @@ class MixMaterialServiceImpl(
// Check if the first mix material position is 1
fun isFirstMixMaterialPositionInvalid() =
sortedMixMaterials[0].position != 1
sortedMixMaterials[0].position != 1
// Check if the first mix material is expressed in percents
fun isFirstMixMaterialPercentages() =
sortedMixMaterials[0].material.materialType!!.usePercentages
sortedMixMaterials[0].material.materialType!!.usePercentages
// Check if any positions is duplicated
fun getDuplicatedPositionsErrors() =
sortedMixMaterials
.findDuplicated { it.position }
.map { duplicatedMixMaterialsPositions(it) }
sortedMixMaterials
.findDuplicated { it.position }
.map { duplicatedMixMaterialsPositions(it) }
// Find all errors and throw if there is any
if (isFirstMixMaterialPositionInvalid()) errors += invalidFirstMixMaterialPosition(sortedMixMaterials[0])
@ -90,32 +99,32 @@ class MixMaterialServiceImpl(
}
class InvalidMixMaterialsPositionsError(
val type: String,
val details: String
val type: String,
val details: String
)
class InvalidMixMaterialsPositionsException(
val errors: Set<InvalidMixMaterialsPositionsError>
val errors: Set<InvalidMixMaterialsPositionsError>
) : RestException(
"invalid-mixmaterial-position",
"Invalid mix materials positions",
HttpStatus.BAD_REQUEST,
"The position of mix materials are invalid",
mapOf(
"invalidMixMaterials" to errors
)
"invalid-mixmaterial-position",
"Invalid mix materials positions",
HttpStatus.BAD_REQUEST,
"The position of mix materials are invalid",
mapOf(
"invalidMixMaterials" to errors
)
)
class InvalidFirstMixMaterial(
val mixMaterial: MixMaterial
val mixMaterial: MixMaterial
) : RestException(
"invalid-mixmaterial-first",
"Invalid first mix material",
HttpStatus.BAD_REQUEST,
"The first mix material is invalid because its material must not be expressed in percents",
mapOf(
"mixMaterial" to mixMaterial
)
"invalid-mixmaterial-first",
"Invalid first mix material",
HttpStatus.BAD_REQUEST,
"The first mix material is invalid because its material must not be expressed in percents",
mapOf(
"mixMaterial" to mixMaterial
)
)
const val INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE = "first"
@ -123,20 +132,20 @@ const val DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE = "duplicated"
const val GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE = "gap"
private fun invalidFirstMixMaterialPosition(mixMaterial: MixMaterial) =
InvalidMixMaterialsPositionsError(
INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE,
"The position ${mixMaterial.position} is under the minimum of 1"
)
InvalidMixMaterialsPositionsError(
INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE,
"The position ${mixMaterial.position} is under the minimum of 1"
)
private fun duplicatedMixMaterialsPositions(position: Int) =
InvalidMixMaterialsPositionsError(
DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE,
"The position $position is duplicated"
)
InvalidMixMaterialsPositionsError(
DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE,
"The position $position is duplicated"
)
private fun gapBetweenStepsPositions() =
InvalidMixMaterialsPositionsError(
GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE,
"There is a gap between mix materials positions"
)
InvalidMixMaterialsPositionsError(
GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE,
"There is a gap between mix materials positions"
)

View File

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
import javax.transaction.Transactional
interface MixService : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixRepository> {
interface MixService : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository> {
/** Gets all mixes with the given [mixType]. */
fun getAllByMixType(mixType: MixType): Collection<Mix>
@ -23,19 +23,30 @@ interface MixService : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixRe
@Service
class MixServiceImpl(
mixRepository: MixRepository,
@Lazy val recipeService: RecipeService,
@Lazy val materialTypeService: MaterialTypeService,
val mixMaterialService: MixMaterialService,
val mixTypeService: MixTypeService
) : AbstractModelService<Mix, MixRepository>(mixRepository),
MixService {
mixRepository: MixRepository,
@Lazy val recipeService: RecipeService,
@Lazy val materialTypeService: MaterialTypeService,
val mixMaterialService: MixMaterialService,
val mixTypeService: MixTypeService
) : AbstractExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixOutputDto, MixRepository>(mixRepository),
MixService {
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 {
with(mixMaterialService) {
return@with it.toOutput()
}
}.toSet()
)
@Transactional
override fun save(entity: MixSaveDto): Mix {
val recipe = recipeService.getById(entity.recipeId)

View File

@ -11,7 +11,8 @@ import org.springframework.web.multipart.MultipartFile
import java.io.File
import javax.transaction.Transactional
interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository> {
interface RecipeService :
ExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeOutputDto, RecipeRepository> {
/** Checks if one or more recipes have the given [company]. */
fun existsByCompany(company: Company): Boolean
@ -34,13 +35,37 @@ class RecipeServiceImpl(
val companyService: CompanyService,
val mixService: MixService,
val recipeStepService: RecipeStepService,
@Lazy val groupService: EmployeeGroupService
@Lazy val groupService: EmployeeGroupService,
val recipeImageService: RecipeImageService
) :
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeOutputDto, RecipeRepository>(
recipeRepository
),
RecipeService {
override fun idNotFoundException(id: Long) = recipeIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = recipeIdAlreadyExistsException(id)
override fun Recipe.toOutput() = RecipeOutputDto(
this.id!!,
this.name,
this.description,
this.color,
this.gloss,
this.sample,
this.approbationDate,
this.remark,
this.company,
this.mixes.map {
with(mixService) {
it.toOutput()
}
}.toSet(),
this.groupsInformation,
recipeImageService.getAllImages(this)
.map { this.imageUrl(it) }
.toSet()
)
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
@ -157,7 +182,6 @@ const val RECIPE_IMAGE_EXTENSION = ".jpg"
@Service
class RecipeImageServiceImpl(
val recipeService: RecipeService,
val fileService: FileService
) : RecipeImageService {
override fun getAllImages(recipe: Recipe): Set<String> {

View File

@ -62,7 +62,7 @@ abstract class AbstractService<E, R : JpaRepository<E, *>>(override val reposito
}
abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repository: R) :
AbstractService<E, R>(repository), ModelService<E, R> {
AbstractService<E, R>(repository), ModelService<E, R> {
protected abstract fun idNotFoundException(id: Long): NotFoundException
protected abstract fun idAlreadyExistsException(id: Long): AlreadyExistsException
@ -83,7 +83,7 @@ abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repos
}
override fun deleteById(id: Long) =
delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing
delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing
protected fun assertId(id: Long?) {
Assert.notNull(id, "${javaClass.simpleName}.update() was called with a null identifier")
@ -91,7 +91,7 @@ abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repos
}
abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<E>>(repository: R) :
AbstractModelService<E, R>(repository), NamedModelService<E, R> {
AbstractModelService<E, R>(repository), NamedModelService<E, R> {
protected abstract fun nameNotFoundException(name: String): NotFoundException
protected abstract fun nameAlreadyExistsException(name: String): AlreadyExistsException
@ -126,33 +126,57 @@ abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<
* @param S The entity save DTO type
* @param U The entity update DTO type
*/
interface ExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>> : Service<E, R> {
interface ExternalService<E, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> : Service<E, R> {
/** Gets all entities mapped to their output model. */
fun getAllForOutput(): Collection<O>
/** Saves a given [entity]. */
fun save(entity: S): E = save(entity.toEntity())
/** Updates a given [entity]. */
fun update(entity: U): E = update(entity.toEntity())
fun update(entity: U): E
/** Convert the given entity to its output model. */
fun E.toOutput(): O
}
/** An [ExternalService] for entities implementing the [Model] interface. */
interface ExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>> :
ModelService<E, R>, ExternalService<E, S, U, R>
interface ExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> :
ModelService<E, R>, ExternalService<E, S, U, O, R> {
/** Gets the entity with the given [id] mapped to its output model. */
fun getByIdForOutput(id: Long): O
}
/** An [ExternalService] for entities implementing the [NamedModel] interface. */
interface ExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>> :
NamedModelService<E, R>, ExternalModelService<E, S, U, R>
interface ExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>> :
NamedModelService<E, R>, ExternalModelService<E, S, U, O, R>
/** An [AbstractService] with the functionalities of a [ExternalService]. */
@Suppress("unused")
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>>(repository: R) :
AbstractService<E, R>(repository), ExternalService<E, S, U, R>
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, *>>(repository: R) :
AbstractService<E, R>(repository), ExternalService<E, S, U, O, R> {
override fun getAllForOutput() =
getAll().map { it.toOutput() }
}
/** An [AbstractModelService] with the functionalities of a [ExternalService]. */
abstract class AbstractExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, Long>>(
repository: R
) : AbstractModelService<E, R>(repository), ExternalModelService<E, S, U, R>
abstract class AbstractExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, O, R : JpaRepository<E, Long>>(
repository: R
) : AbstractModelService<E, R>(repository), ExternalModelService<E, S, U, O, R> {
override fun getAllForOutput() =
getAll().map { it.toOutput() }
override fun getByIdForOutput(id: Long) =
getById(id).toOutput()
}
/** An [AbstractNamedModelService] with the functionalities of a [ExternalService]. */
abstract class AbstractExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : NamedJpaRepository<E>>(
repository: R
) : AbstractNamedModelService<E, R>(repository), ExternalNamedModelService<E, S, U, R>
abstract class AbstractExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, O, R : NamedJpaRepository<E>>(
repository: R
) : AbstractNamedModelService<E, R>(repository), ExternalNamedModelService<E, S, U, O, R> {
override fun getAllForOutput() =
getAll().map { it.toOutput() }
override fun getByIdForOutput(id: Long) =
getById(id).toOutput()
}

View File

@ -27,8 +27,8 @@ abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>
protected val entityList: List<E>
get() = listOf(
entity,
anotherEntity
entity,
anotherEntity
)
@AfterEach
@ -91,7 +91,7 @@ abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>
}
abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : JpaRepository<E, Long>> :
AbstractServiceTest1<E, S, R>() {
AbstractServiceTest1<E, S, R>() {
// existsById()
@ -129,7 +129,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
whenever(repository.findById(entity.id!!)).doReturn(Optional.empty())
assertThrows<NotFoundException> { service.getById(entity.id!!) }
.assertErrorCode()
.assertErrorCode()
}
// save()
@ -139,7 +139,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
doReturn(true).whenever(service).existsById(entity.id!!)
assertThrows<AlreadyExistsException> { service.save(entity) }
.assertErrorCode()
.assertErrorCode()
}
// update()
@ -161,7 +161,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
doReturn(false).whenever(service).existsById(entity.id!!)
assertThrows<NotFoundException> { service.update(entity) }
.assertErrorCode()
.assertErrorCode()
}
// deleteById()
@ -177,7 +177,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
}
abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelService<E, *>, R : NamedJpaRepository<E>> :
AbstractModelServiceTest<E, S, R>() {
AbstractModelServiceTest<E, S, R>() {
protected abstract val entityWithEntityName: E
// existsByName()
@ -216,7 +216,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
whenever(repository.findByName(entity.name)).doReturn(null)
assertThrows<NotFoundException> { service.getByName(entity.name) }
.assertErrorCode("name")
.assertErrorCode("name")
}
// save()
@ -226,7 +226,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
doReturn(true).whenever(service).existsByName(entity.name)
assertThrows<AlreadyExistsException> { service.save(entity) }
.assertErrorCode("name")
.assertErrorCode("name")
}
// update()
@ -258,7 +258,7 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
doReturn(entity).whenever(service).getById(entity.id!!)
assertThrows<AlreadyExistsException> { service.update(entity) }
.assertErrorCode("name")
.assertErrorCode("name")
}
}
@ -269,8 +269,8 @@ interface ExternalModelServiceTest {
// ==== IMPLEMENTATIONS FOR EXTERNAL SERVICES ====
// Lots of code duplication but I don't have a better solution for now
abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *>, R : JpaRepository<E, Long>> :
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *, *>, R : JpaRepository<E, Long>> :
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
protected abstract val entitySaveDto: N
protected abstract val entityUpdateDto: U
@ -281,8 +281,8 @@ abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U :
}
}
abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityDto<E>, U : EntityDto<E>, S : ExternalNamedModelService<E, N, U, *>, R : NamedJpaRepository<E>> :
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityDto<E>, U : EntityDto<E>, S : ExternalNamedModelService<E, N, U, *, *>, R : NamedJpaRepository<E>> :
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
protected abstract val entitySaveDto: N
protected abstract val entityUpdateDto: U
@ -294,10 +294,10 @@ abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityD
}
fun NotFoundException.assertErrorCode(identifierName: String = "id") =
this.assertErrorCode("notfound", identifierName)
this.assertErrorCode("notfound", identifierName)
fun AlreadyExistsException.assertErrorCode(identifierName: String = "id") =
this.assertErrorCode("exists", identifierName)
this.assertErrorCode("exists", identifierName)
fun RestException.assertErrorCode(type: String, identifierName: String) {
assertTrue {
@ -311,11 +311,11 @@ fun RestException.assertErrorCode(errorCode: String) {
}
fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
entity: E,
entitySaveDto: N,
service: ExternalService<E, N, *, *>,
saveMockMatcher: () -> E = { entity },
op: () -> Unit = {}
entity: E,
entitySaveDto: N,
service: ExternalService<E, N, *, *, *>,
saveMockMatcher: () -> E = { entity },
op: () -> Unit = {}
) {
doReturn(entity).whenever(service).save(saveMockMatcher())
doReturn(entity).whenever(entitySaveDto).toEntity()
@ -329,11 +329,11 @@ fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
}
fun <E : Model, U : EntityDto<E>> withBaseUpdateDtoTest(
entity: E,
entityUpdateDto: U,
service: ExternalModelService<E, *, U, *>,
updateMockMatcher: () -> E,
op: E.() -> Unit = {}
entity: E,
entityUpdateDto: U,
service: ExternalModelService<E, *, U, *, *>,
updateMockMatcher: () -> E,
op: E.() -> Unit = {}
) {
doAnswer { it.arguments[0] }.whenever(service).update(updateMockMatcher())
doReturn(entity).whenever(entityUpdateDto).toEntity()

View File

@ -14,16 +14,17 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
class MaterialServiceTest :
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
override val repository: MaterialRepository = mock()
private val recipeService: RecipeService = mock()
private val mixService: MixService = mock()
private val materialTypeService: MaterialTypeService = mock()
private val fileService: FileService = mock()
override val service: MaterialService =
spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService))
spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService))
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())
@ -83,15 +84,16 @@ class MaterialServiceTest :
@Test
fun `getAllNotMixType() returns a list containing every material that are not a mix type`() {
val mixTypeMaterial = material(name = "mix type material", isMixType = true)
val mixTypeMaterial = material(id = 1L, name = "mix type material", isMixType = true)
val mixTypeMaterialOutput = materialOutputDto(mixTypeMaterial)
val materialList = listOf(entity, mixTypeMaterial)
doReturn(materialList).whenever(service).getAll()
val found = service.getAllNotMixType()
assertTrue(found.contains(entity))
assertFalse(found.contains(mixTypeMaterial))
assertTrue(found.contains(entityOutput))
assertFalse(found.contains(mixTypeMaterialOutput))
}
// save()
@ -101,7 +103,7 @@ class MaterialServiceTest :
doReturn(true).whenever(service).existsByName(entity.name)
assertThrows<AlreadyExistsException> { service.save(entity) }
.assertErrorCode("name")
.assertErrorCode("name")
}
@Test
@ -133,7 +135,7 @@ class MaterialServiceTest :
doReturn(entity).whenever(service).getById(material.id!!)
assertThrows<AlreadyExistsException> { service.update(material) }
.assertErrorCode("name")
.assertErrorCode("name")
}
@Test
@ -173,16 +175,16 @@ class MaterialServiceTest :
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))))
recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial))))
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
doReturn(materials).whenever(service).getAll()
val found = service.getAllForMixCreation(recipe.id!!)
assertTrue(normalMaterial in found)
assertTrue(mixTypeMaterial in found)
assertFalse(anotherMixTypeMaterial in found)
assertTrue(materialOutputDto(normalMaterial) in found)
assertTrue(materialOutputDto(mixTypeMaterial) in found)
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
}
// getAllForMixUpdate()
@ -202,9 +204,9 @@ class MaterialServiceTest :
val found = service.getAllForMixUpdate(mix.id!!)
assertTrue(normalMaterial in found)
assertTrue(mixTypeMaterial in found)
assertFalse(anotherMixTypeMaterial in found)
assertTrue(materialOutputDto(normalMaterial) in found)
assertTrue(materialOutputDto(mixTypeMaterial) in found)
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
}
@ -233,4 +235,13 @@ class MaterialServiceTest :
test()
}
private fun materialOutputDto(material: Material) = MaterialOutputDto(
id = material.id!!,
name = material.name,
inventoryQuantity = material.inventoryQuantity,
isMixType = material.isMixType,
materialType = material.materialType!!,
simdutUrl = null
)
}

View File

@ -21,7 +21,7 @@ class RecipeServiceTest :
private val groupService: EmployeeGroupService = mock()
private val recipeStepService: RecipeStepService = mock()
override val service: RecipeService =
spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService))
spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock()))
private val company: Company = company(id = 0L)
override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company)
@ -168,7 +168,7 @@ private class RecipeImageServiceTestContext {
every { write(any(), any(), any()) } just Runs
every { delete(any()) } just Runs
}
val recipeImageService = spyk(RecipeImageServiceImpl(mockk(), fileService))
val recipeImageService = spyk(RecipeImageServiceImpl(fileService))
val recipe = spyk(recipe())
val recipeImagesIds = setOf(1L, 10L, 21L)
val recipeImagesNames = recipeImagesIds.map { it.imageName }.toSet()