Empêcher la suppression des type de produits par défaut

This commit is contained in:
FyloZ 2020-02-23 23:45:47 -05:00
parent af58fc47a1
commit b1a4c4af40
24 changed files with 224 additions and 60 deletions

2
.gitignore vendored
View File

@ -28,5 +28,7 @@ HELP.md
### VS Code ###
.vscode/
/logs/
*.log
/workdir/
*.db

View File

@ -1,9 +1,12 @@
package dev.fyloz.trial.colorrecipesexplorer;
import dev.fyloz.trial.colorrecipesexplorer.core.model.config.MaterialTypeProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(MaterialTypeProperties.class)
public class ColorRecipesExplorerApplication {
public static void main(String[] args) {

View File

@ -1,41 +1,43 @@
package dev.fyloz.trial.colorrecipesexplorer.core.configuration;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.Preferences;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.config.MaterialTypeProperties;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class InitialDataLoader implements ApplicationListener<ApplicationReadyEvent> {
private MaterialTypeService materialTypeService;
private MaterialTypeProperties materialTypeProperties;
@Autowired
public InitialDataLoader(MaterialTypeService materialTypeService) {
public InitialDataLoader(MaterialTypeService materialTypeService, MaterialTypeProperties materialTypeProperties) {
this.materialTypeService = materialTypeService;
this.materialTypeProperties = materialTypeProperties;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
if (!materialTypeService.existsByName(MaterialType.DEFAULT_MATERIAL_TYPE.getName()))
createInitialMaterialType(MaterialType.DEFAULT_MATERIAL_TYPE);
if (!materialTypeService.existsByName(MaterialType.BASE_MATERIAL_TYPE.getName()))
createInitialMaterialType(MaterialType.BASE_MATERIAL_TYPE);
}
for (MaterialType materialType : materialTypeProperties.getDefaults()) {
if (!materialTypeService.existsByName(materialType.getName())) {
materialTypeService.save(materialType);
} else {
MaterialType found = materialTypeService.getByName(materialType.getName());
private void createInitialMaterialType(MaterialType materialType) {
try {
materialTypeService.save(materialType);
} catch (ModelException ex) {
Preferences.logger.warn(String.format("Échec de la création du type de produit par défaut '%s': %s", materialType.getName(), ex.getMessage()));
found.setPrefix(materialType.getPrefix());
found.setUsePercentages(materialType.isUsePercentages());
materialTypeService.update(found);
}
materialTypeService.addDefault(materialType);
}
}
}

View File

@ -0,0 +1,14 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import lombok.Getter;
@Getter
public class CannotDeleteDefaultMaterialTypeException extends RuntimeException {
private MaterialType materialType;
public CannotDeleteDefaultMaterialTypeException(MaterialType materialType) {
this.materialType = materialType;
}
}

View File

@ -0,0 +1,14 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MaterialTypeEditorDto;
import lombok.Getter;
@Getter
public class CannotEditDefaultMaterialTypeException extends RuntimeException {
private MaterialTypeEditorDto materialType;
public CannotEditDefaultMaterialTypeException(MaterialTypeEditorDto materialType) {
this.materialType = materialType;
}
}

View File

@ -36,6 +36,8 @@ public enum ResponseCode {
SUCCESS_DELETING_RECIPE(33, ResponseCodeType.SUCCESS, 1),
SUCCESS_DELETING_MATERIAL_TYPE(34, ResponseCodeType.SUCCESS, 1),
RECIPE_ALREADY_EXIST(35, ResponseCodeType.ERROR, 1),
CANNOT_REMOVE_DEFAULT_MATERIAL_TYPE(36, ResponseCodeType.ERROR, 0),
CANNOT_EDIT_DEFAULT_MATERIAL_TYPE(37, ResponseCodeType.ERROR, 0),
// HTTP Errors
_500(100, ResponseCodeType.ERROR, 0),

View File

@ -1,6 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.core.io.response;
import dev.fyloz.trial.colorrecipesexplorer.core.model.*;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MaterialTypeEditorDto;
import java.util.ArrayList;
import java.util.HashMap;
@ -11,11 +12,12 @@ public enum ResponseDataType {
MATERIALS("materials", ArrayList.class, MATERIAL),
MATERIAL_ID("materialId", Long.class),
MATERIAL_CODE("materialCode", String.class),
MATERIALS_JSON("materialsJson", String.class),
MATERIAL_TYPE("materialType", MaterialType.class),
MATERIAL_TYPES("materialTypes", ArrayList.class, MATERIAL_TYPE),
MATERIAL_TYPE_NAME("materialTypeName", String.class),
MATERIALS_JSON("materialsJson", String.class),
MATERIAL_TYPE_DTO("materialTypeDto", MaterialTypeEditorDto.class),
RECIPE("recipe", Recipe.class),
RECIPES("recipes", ArrayList.class, RECIPE),
@ -46,7 +48,7 @@ public enum ResponseDataType {
IMAGE("image", String.class),
IMAGES("images", ArrayList.class, IMAGE),
BLOCK_BUTTON("blockButton", Boolean.class);
BLOCK_BUTTON("blockButton", boolean.class);
private String dataTypeName;
private Class dataType;

View File

@ -30,7 +30,7 @@ public class Material implements IModel {
@NonNull
@NotNull
private Boolean isMixType;
private boolean isMixType;
@NonNull
@NotNull

View File

@ -6,24 +6,17 @@ import org.hibernate.annotations.ColumnDefault;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
public class MaterialType implements IModel {
public static final MaterialType DEFAULT_MATERIAL_TYPE;
public static final MaterialType BASE_MATERIAL_TYPE;
public static final String IDENTIFIER_PREFIX_NAME = "prefix";
static {
DEFAULT_MATERIAL_TYPE = new MaterialType("Aucun", "", false);
BASE_MATERIAL_TYPE = new MaterialType("Base", "BAS", false);
}
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ -41,5 +34,19 @@ public class MaterialType implements IModel {
@NonNull
@NotNull
@ColumnDefault("false")
private Boolean usePercentages;
private boolean usePercentages;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MaterialType that = (MaterialType) o;
return name.equals(that.name) &&
prefix.equals(that.prefix);
}
@Override
public int hashCode() {
return Objects.hash(name, prefix);
}
}

View File

@ -0,0 +1,23 @@
package dev.fyloz.trial.colorrecipesexplorer.core.model.config;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "entities.material-types")
@Getter
@Setter
public class MaterialTypeProperties {
public MaterialTypeProperties() {
System.out.println("TEST");
}
private List<MaterialType> defaults;
}

View File

@ -1,12 +1,42 @@
package dev.fyloz.trial.colorrecipesexplorer.core.model.dto;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MaterialTypeEditorDto {
private Long id;
private String oldName;
private MaterialType materialType;
private String name;
private String oldPrefix;
private String prefix;
private boolean usePercentages;
public MaterialTypeEditorDto(MaterialType materialType) {
this.id = materialType.getId();
this.oldName = materialType.getName();
this.name = materialType.getName();
this.oldPrefix = materialType.getPrefix();
this.prefix = materialType.getPrefix();
this.usePercentages = materialType.isUsePercentages();
}
public MaterialType getMaterialType() {
return new MaterialType(id, name, prefix, usePercentages);
}
public MaterialType getOldMaterialType() {
return new MaterialType(id, oldName, oldPrefix, usePercentages);
}
}

View File

@ -1,5 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.CannotDeleteDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.CannotEditDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
@ -7,20 +9,47 @@ import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MaterialTypeEditorDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.GenericService;
import dev.fyloz.trial.colorrecipesexplorer.dao.MaterialTypeDao;
import org.commonmark.node.Link;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class MaterialTypeService extends GenericService<MaterialType, MaterialTypeDao> {
private MaterialService materialService;
private List<MaterialType> defaultMaterialTypes = new LinkedList<>();
@Autowired
public MaterialTypeService(MaterialTypeDao materialTypeDao, MaterialService materialService) {
super(materialTypeDao, MaterialType.class);
this.materialService = materialService;
}
/**
* Ajoute un type de produit dans les types de produit par défaut.
*
* @param materialType Le type de produit
*/
public void addDefault(MaterialType materialType) {
defaultMaterialTypes.add(materialType);
}
/**
* Vérifie si un type de produit est un type de produit par défaut.
*
* @param materialType Le type de produit
* @return Si le type de produite est un type de produit par défaut
*/
public boolean isDefault(MaterialType materialType) {
return defaultMaterialTypes.contains(materialType);
}
/**
* Vérifie si un type de produit correspondant à un nom existe.
*
@ -51,6 +80,17 @@ public class MaterialTypeService extends GenericService<MaterialType, MaterialTy
return !materialService.existsByMaterialType(materialType);
}
/**
* Récupère tous les type de produits qui ne sont pas des types de produit.
*
* @return Tous les type de produits qui ne sont pas des types de produit
*/
public List<MaterialType> getAllNotDefault() {
return getAll().stream()
.filter(t -> !isDefault(t))
.collect(Collectors.toList());
}
/**
* Récupère un type de produit par son nom.
*
@ -71,24 +111,28 @@ public class MaterialTypeService extends GenericService<MaterialType, MaterialTy
return dao.findByPrefix(prefix);
}
@Override
public MaterialType update(MaterialType entity) {
throw new UnsupportedOperationException("Cette méthode n'est pas supportée pour les types de produits. Utiliser MaterialTypeService::update(MaterialTypeEditorDto)");
}
public MaterialType update(MaterialTypeEditorDto materialTypeDto) {
MaterialType materialType = materialTypeDto.getMaterialType();
if (isDefault(materialTypeDto.getOldMaterialType()))
throw new CannotEditDefaultMaterialTypeException(materialTypeDto);
if (!existsByName(materialTypeDto.getOldName()))
throw new EntityNotFoundException(type, ModelException.IdentifierType.NAME, materialTypeDto.getOldName());
if (materialTypeDto.getOldName().equals(materialType.getName()) && existsByName(materialType.getName()))
if (!materialTypeDto.getOldName().equals(materialType.getName()) && existsByName(materialType.getName()))
throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.NAME, materialType.getName());
if (existsByPrefix(materialType.getPrefix()) && !getByPrefix(materialType.getPrefix()).getId().equals(materialType.getId()))
if (!materialTypeDto.getOldPrefix().equals(materialType.getPrefix()) && existsByPrefix(materialType.getPrefix()))
throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, MaterialType.IDENTIFIER_PREFIX_NAME, materialType.getPrefix());
return dao.save(materialType);
}
@Override
public void delete(MaterialType materialType) {
if (isDefault(materialType)) throw new CannotDeleteDefaultMaterialTypeException(materialType);
super.delete(materialType);
}
@Deprecated(since = "1.3.0", forRemoval = true)
public void deleteIfNotLinked(MaterialType materialType) {
if (!isLinkedToMaterials(materialType)) {

View File

@ -3,16 +3,14 @@ package dev.fyloz.trial.colorrecipesexplorer.dao;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MaterialTypeDao extends JpaRepository<MaterialType, Long> {
MaterialType findByName(String name);
boolean existsByName(String name);
MaterialType findByPrefix(String prefix);
boolean existsByPrefix(String prefix);
MaterialType findByPrefix(String prefix);
MaterialType findByName(String name);
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.editors;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.CannotEditDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
@ -33,19 +34,19 @@ public class MaterialTypeEditorController {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(EDITOR_MATERIAL_TYPE)
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll())
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAllNotDefault())
.build();
}
@GetMapping(EDITOR_MATERIAL_TYPE_SPECIFIC)
public ModelAndView getEditPage(ModelAndView model, @PathVariable Long id, MaterialType materialType) {
public ModelAndView getEditPage(ModelAndView model, @PathVariable Long id, MaterialTypeEditorDto materialTypeDto) {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder(model).withView(EDITOR_MATERIAL_TYPE_EDITOR);
try {
if (materialType == null) materialType = materialTypeService.getById(id);
if (materialTypeDto.getName() == null) materialTypeDto = new MaterialTypeEditorDto(materialTypeService.getById(id));
return responseBuilder
.addResponseData(ResponseDataType.MATERIAL_TYPE, materialType)
.addResponseData(ResponseDataType.MATERIAL_TYPE_DTO, materialTypeDto)
.build();
} catch (EntityNotFoundException ex) {
return getPage(responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, id).build());
@ -67,8 +68,10 @@ public class MaterialTypeEditorController {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST, materialTypeDto.getMaterialType().getName());
else if (ex.getIdentifierName().equals(MaterialType.IDENTIFIER_PREFIX_NAME))
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_ALREADY_EXIST_PREFIX, materialTypeDto.getMaterialType().getPrefix());
} catch(CannotEditDefaultMaterialTypeException ex) {
responseBuilder.addResponseCode(ResponseCode.CANNOT_EDIT_DEFAULT_MATERIAL_TYPE);
}
return getEditPage(responseBuilder.build(), materialTypeDto.getMaterialType().getId(), materialTypeDto.getMaterialType());
return getEditPage(responseBuilder.build(), materialTypeDto.getMaterialType().getId(), materialTypeDto);
}
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.removers;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.CannotDeleteDefaultMaterialTypeException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityLinkedException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
@ -34,7 +35,7 @@ public class MaterialTypeRemoverController {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(REMOVER_MATERIAL_TYPE)
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll())
.addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAllNotDefault())
.build();
}
@ -50,6 +51,8 @@ public class MaterialTypeRemoverController {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_LINKED, materialTypeService.getById(id).getName());
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, id);
} catch (CannotDeleteDefaultMaterialTypeException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.CANNOT_REMOVE_DEFAULT_MATERIAL_TYPE);
}
return getPage(modelResponseBuilder.build());

View File

@ -71,7 +71,7 @@ public class XlsxExporter {
for (MixQuantity mixQuantity : mix.getMixQuantities()) {
mixTable.setRowName(row, mixQuantity.getMaterial().getName());
mixTable.setContent(new Position(1, row + 1), mixQuantity.getQuantity());
mixTable.setContent(new Position(3, row + 1), mixQuantity.getMaterial().getMaterialType().getUsePercentages() ? "%" : "mL");
mixTable.setContent(new Position(3, row + 1), mixQuantity.getMaterial().getMaterialType().isUsePercentages() ? "%" : "mL");
row++;
}

View File

@ -26,6 +26,15 @@ server.passwords.file-name=passwords.txt
url.useport=true
# DEFAULT MATERIAL TYPES
entities.material-types.defaults[0].name=Aucun
entities.material-types.defaults[0].prefix=
entities.material-types.defaults[0].use-percentages=false
entities.material-types.defaults[1].name=Base
entities.material-types.defaults[1].prefix=BAS
entities.material-types.defaults[1].use-percentages=false
# DEBUG
spring.jpa.show-sql=true
spring.h2.console.enabled=true

View File

@ -36,3 +36,5 @@ response.32=The banner {0} has been saved
response.33=The recipe {0} has been deleted
response.34=The material type {0} has been deleted
response.35=There is already a recipe with the ID {0}
response.36=You can't remove a default material type
response.37=You can't edit a default material type

View File

@ -36,3 +36,5 @@ response.32=La bannière {0} a bien été sauvegardée
response.33=La recette {0} a bien été supprimée
response.34=Le type de produit {0} a bien été supprimé
response.35=Il y a déjà une recette avec l''ID {0}
response.37=Vous ne pouvez pas modifier un type de produit par défaut
response.36=Vous ne pouvez pas supprimer un type de produit par défaut

View File

@ -55,7 +55,7 @@ input[type=file] {
border-style: none;
}
input[disabled] {
input[readonly] {
border-style: none;
color: black;
}
@ -102,7 +102,7 @@ table:not(.noStyle) tr:nth-child(odd) {
background-color: #ffc299;
}
.nosimdut input:not(:disabled) {
.nosimdut input:not(:readonly) {
background-color: #fafafa;
}

View File

@ -40,7 +40,7 @@
</div>
<div class="formColumn">
<div>
<input type="number" th:field="*{id}" required disabled/>
<input type="number" th:field="*{id}" required readonly/>
</div>
<div>
<input type="text" th:field="*{name}"/>

View File

@ -2,7 +2,7 @@
<html lang="fr" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block
th:include="fragments.html :: head(#{materialType.editing.title(${materialType.name})}, 'form')"></th:block>
th:include="fragments.html :: head(#{materialType.editing.title(${materialTypeDto.name})}, 'form')"></th:block>
</head>
<body>
@ -12,9 +12,12 @@
<section>
<div th:include="fragments.html :: messages"></div>
<h1 class="materialCode" th:text="#{materialType.editing.title(${materialType.name})}"></h1>
<h1 class="materialCode" th:text="#{materialType.editing.title(${materialTypeDto.name})}"></h1>
<form th:action="@{/materialType/editor}" th:object="${materialTypeDto}" class="requireAuth" method="POST">
<input type="hidden" th:field="*{oldName}"/>
<input type="hidden" th:field="*{oldPrefix}"/>
<form th:action="@{/materialType/editor}" th:object="${materialType}" class="requireAuth" method="POST">
<div class="content">
<div class="formWrap">
<div class="formColumn">
@ -35,7 +38,7 @@
</div>
<div class="formColumn">
<div>
<input type="number" th:field="*{id}" required disabled/>
<input type="number" th:field="*{id}" required readonly/>
</div>
<div>
<input type="text" th:field="*{name}" required/>
@ -44,7 +47,8 @@
<input type="text" minlength="3" maxlength="3" th:field="*{prefix}" required/>
</div>
<div>
<input type="checkbox" th:field="*{usePercentages}"/>
<input type="checkbox" th:field="*{usePercentages}"
th:checked="${materialTypeDto.usePercentages}"/>
</div>
</div>
</div>

View File

@ -82,7 +82,7 @@
</div>
<div class="formColumn">
<div>
<input type="number" th:field="*{id}" required disabled/>
<input type="number" th:field="*{id}" required readonly/>
</div>
<div>
<input type="text" th:field="*{name}" required/>

View File

@ -142,7 +142,7 @@
th:data-isMixType="${material.isMixType}"
th:data-defaultvalue="${mixQuantity.quantity}"
th:value="${mixQuantity.quantity}"
th:disabled="${material.materialType.usePercentages}"
th:readonly="${material.materialType.usePercentages}"
type="number"/></td>
<td class="unitsColumn">
<p class="inventoryQuantityUnits"