Finalisation transition vers exceptions + DTO

This commit is contained in:
FyloZ 2020-02-19 15:01:10 -05:00
parent 26110beac2
commit 1b5480e800
26 changed files with 548 additions and 542 deletions

View File

@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.FileHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.services.PasswordService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.FilesService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,6 +26,7 @@ public class ColorRecipesExplorerApplication {
public static ColorRecipesExplorerApplication CREApp;
private MessageSource messageSource;
private FilesService filesService;
public static void main(String[] args) {
UPLOAD_LOCATION = args[0] != null ? args[0] : "./";
@ -33,8 +35,9 @@ public class ColorRecipesExplorerApplication {
}
@Autowired
public ColorRecipesExplorerApplication(MessageSource messageSource) {
public ColorRecipesExplorerApplication(MessageSource messageSource, FilesService filesService) {
this.messageSource = messageSource;
this.filesService = filesService;
CREApp = this;
LOGGER.info("Le fichier des utilisateurs se situe à: " + new File(UPLOAD_LOCATION + "/" + USERS_FILE_NAME).getAbsolutePath());
@ -48,16 +51,14 @@ public class ColorRecipesExplorerApplication {
* Un mot de passe correspond à une ligne dans le fichier passwords.txt.
*/
private void loadPasswords() {
FileHandler fileHandler = new FileHandler(USERS_FILE_NAME, FileHandler.FileContext.OTHERS, FileHandler.FileExtension.TEXT);
if (!fileHandler.isValid()) {
fileHandler.createFile();
}
String filePath = String.format("%s/%s.txt", UPLOAD_LOCATION, USERS_FILE_NAME);
try {
List<String> fileContent = Files.readAllLines(fileHandler.getPath());
if(filesService.fileExists(filePath)) filesService.createFile(filePath);
List<String> fileContent = filesService.readFileAsStrings(filePath);
if (fileContent.size() < 1) {
LOGGER.warn("Aucun mot de passe trouvé. Il sera impossible d'utiliser certaines fonctionnalitées de l'application.");
LOGGER.warn("Aucun mot de passe trouvé. Il sera impossible d'utiliser certaines fonctionnalités de l'application.");
}
for (String line : fileContent) {
@ -65,7 +66,7 @@ public class ColorRecipesExplorerApplication {
}
} catch (IOException e) {
LOGGER.error("Une erreur est survenue lors du chargement du fichier des utilisateurs", e);
LOGGER.warn("Il sera impossible d'utiliser certaines fonctionnalitées de l'application.");
LOGGER.warn("Il sera impossible d'utiliser certaines fonctionnalités de l'application.");
}
}

View File

@ -1,6 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.core.configuration;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialTypeService;
import org.springframework.beans.factory.annotation.Autowired;
@ -10,8 +11,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class InitialDataLoader implements ApplicationListener<ApplicationReadyEvent> {
@ -25,16 +24,17 @@ public class InitialDataLoader implements ApplicationListener<ApplicationReadyEv
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
if (!materialTypeService.getByName(MaterialType.DEFAULT_MATERIAL_TYPE.getName()).isPresent())
if (!materialTypeService.existsByName(MaterialType.DEFAULT_MATERIAL_TYPE.getName()))
createInitialMaterialType(MaterialType.DEFAULT_MATERIAL_TYPE);
if (!materialTypeService.getByName(MaterialType.BASE_MATERIAL_TYPE.getName()).isPresent())
if (!materialTypeService.existsByName(MaterialType.BASE_MATERIAL_TYPE.getName()))
createInitialMaterialType(MaterialType.BASE_MATERIAL_TYPE);
}
private void createInitialMaterialType(MaterialType materialType) {
Optional<MaterialType> optionalSavedMaterialType = materialTypeService.save(materialType);
if (optionalSavedMaterialType.isEmpty()) {
ColorRecipesExplorerApplication.LOGGER.warn(String.format("Échec de la création du type de produit par défaut '%s'.", materialType.getName()));
try {
materialTypeService.save(materialType);
} catch (ModelException ex) {
ColorRecipesExplorerApplication.LOGGER.warn(String.format("Échec de la création du type de produit par défaut '%s': %s", materialType.getName(), ex.getMessage()));
}
}
}

View File

@ -1,11 +1,12 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
import org.hibernate.type.IdentifierType;
import lombok.Getter;
/**
* Représente une exception qui sera lancée lorsqu'un objet du modèle n'est pas trouvé.
*/
@Getter
public class EntityNotFoundException extends ModelException {
/**
@ -13,6 +14,11 @@ public class EntityNotFoundException extends ModelException {
*/
private IdentifierType identifierType;
/**
* Le nom de l'identifiant utilisé (optionnel)
*/
private String identifierName;
/**
* La valeur de l'identifiant
*/
@ -24,12 +30,10 @@ public class EntityNotFoundException extends ModelException {
this.requestedId = requestedId;
}
public IdentifierType getIdentifierType() {
return identifierType;
public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, String identifierName, Object requestedId) {
super(type);
this.identifierType = identifierType;
this.identifierName = identifierName != null ? identifierName : identifierType.getName();
this.requestedId = requestedId;
}
public Object getRequestedId() {
return requestedId;
}
}

View File

@ -27,12 +27,12 @@ public class Material implements IModel {
@NonNull
@NotNull
@ColumnDefault("0")
private float inventoryQuantity;
private Float inventoryQuantity;
@NonNull
@NotNull
@ColumnDefault("false")
private boolean isMixType;
private Boolean isMixType;
@NonNull
@NotNull

View File

@ -12,6 +12,8 @@ import javax.validation.constraints.NotNull;
@NoArgsConstructor
public class MixType implements IModel {
public static final String IDENTIFIER_MATERIAL_NAME = "material";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

View File

@ -6,7 +6,9 @@ import org.hibernate.validator.constraints.Length;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Data
@ -35,7 +37,7 @@ public class Recipe implements IModel {
@NonNull
@NotNull
private int sample;
private Integer sample;
private String approbationDate;
@ -49,4 +51,12 @@ public class Recipe implements IModel {
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<RecipeStep> recipeSteps;
public Collection<MixType> getMixTypes() {
return mixes
.stream()
.map(Mix::getMixType)
.collect(Collectors.toList());
}
}

View File

@ -116,13 +116,12 @@ public class GenericService<T extends IModel, R extends JpaRepository<T, Long>>
}
@Override
@Deprecated(since = "v.1.3.0", forRemoval = true)
public boolean exists(T entity) {
return entity != null && entity.getId() != null && existsById(entity.getId());
}
@Override
public final boolean existsById(Long id) {
public boolean existsById(Long id) {
return dao.existsById(id);
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixQuantity;
@ -38,7 +39,12 @@ public class InventoryService {
if (!material.isMixType()) {
material.setInventoryQuantity(material.getInventoryQuantity() - entry.getValue());
if (materialService.update(material).isEmpty()) return false;
try {
materialService.update(material);
} catch (EntityNotFoundException ex) {
return false;
}
}
}

View File

@ -14,6 +14,7 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
@Service
public class FilesService {
@ -65,6 +66,17 @@ public class FilesService {
return Files.readAllBytes(Paths.get(path));
}
/**
* Récupère le contenu d'un fichier dans une liste de String.
*
* @param path Le chemin vers le fichier
* @return Le contenu du fichier dans une liste de String
* @throws IOException La lecture du fichier a échoué
*/
public List<String> readFileAsStrings(String path) throws IOException {
return Files.readAllLines(Paths.get(path));
}
/**
* Écrit un fichier Multipart sur le disque.
*
@ -93,7 +105,7 @@ public class FilesService {
* @throws IOException La création du fichier échoue
*/
public File createFile(String path) throws IOException {
File file = new File(path);
File file = getFile(path);
if (!file.exists() || file.isDirectory()) {
Files.createDirectories(file.getParentFile().toPath());
@ -110,7 +122,7 @@ public class FilesService {
* @throws IOException La suppression du fichier échoue
*/
public void deleteFile(String path) {
File file = new File(path);
File file = getFile(path);
try {
if (file.exists() && !file.isDirectory()) Files.delete(file.toPath());
@ -119,4 +131,20 @@ public class FilesService {
}
}
/**
* Vérifie si un fichier existe sur le disque.
*
* @param path Le chemin vers le fichier
* @return Si le fichier existe
*/
public boolean fileExists(String path) {
File file = getFile(path);
return file.exists() && !file.isDirectory();
}
private File getFile(String path) {
return new File(path);
}
}

View File

@ -3,8 +3,12 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@Service
public class ImagesService {
@ -26,6 +30,26 @@ public class ImagesService {
}
}
public boolean writeMultipartImage(MultipartFile image, String name) {
return filesService.writeMultiPartFile(image, getPath(name));
}
public void deleteImage(String name) {
filesService.deleteFile(getPath(name));
}
public boolean isImage(InputStream input) {
try {
return !(ImageIO.read(input) == null);
} catch (IOException ex) {
throw new RuntimeException("Erreur lors de la vérification d'une image: " + ex.getMessage());
}
}
public File getDirectoryFile() {
return new File(getPath(""));
}
private String getPath(String name) {
return String.format("%s/%s/%s", ColorRecipesExplorerApplication.UPLOAD_LOCATION, IMAGES_DIRECTORY, name);
}

View File

@ -0,0 +1,77 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class SimdutService {
private static final String SIMDUT_DIRECTORY = "simdut";
private FilesService filesService;
private MaterialService materialService;
@Autowired
public SimdutService(FilesService filesService, MaterialService materialService) {
this.filesService = filesService;
this.materialService = materialService;
}
/**
* Lit le fichier SIMDUT d'un produit et retourne son contenu.
*
* @param material Le produit
* @return Le contenu du fichier SIMDUT du produit
*/
public byte[] readSimdutForMaterial(Material material) {
String path = getPath(material);
if (filesService.fileExists(path)) return new byte[0];
try {
return filesService.readFileAsBytes(path);
} catch (IOException ex) {
throw new RuntimeException("Impossible de lire un fichier SIMDUT: " + ex.getMessage());
}
}
/**
* Lit le fichier SIMDUT du produit correspondant à un identifiant et retourne son contenu.
*
* @param id L'identifiant du produit
* @return Le contenu du fichier SIMDUT du produit correspondant à l'identifiant
*/
public byte[] readSimdutForMaterialId(Long id) {
return readSimdutForMaterial(materialService.getById(id));
}
/**
* Vérifie si un produit a un fichier SIMDUT.
*
* @param material Le produit
* @return Si le produit a un fichier SIMDUT
*/
public boolean simdutExistsForMaterial(Material material) {
return filesService.fileExists(getPath(material));
}
/**
* Vérifie si le produit correspondant à un identifiant a un fichier SIMDUT.
*
* @param id L'identifiant du produit
* @return si le produit correspondant à l'identifiant a un fichier SIMDUT
*/
public boolean simdutExistsForMaterialId(Long id) {
return simdutExistsForMaterial(materialService.getById(id));
}
private String getPath(Material material) {
return String.format("%s/%s/%s", ColorRecipesExplorerApplication.UPLOAD_LOCATION, SIMDUT_DIRECTORY, materialService.getSimdutFileName(material));
}
}

View File

@ -0,0 +1,68 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.xlsx.XlsxExporter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
public class XlsService {
private RecipeService recipeService;
@Autowired
public XlsService(RecipeService recipeService) {
this.recipeService = recipeService;
}
/**
* Génère le fichier XLS d'une recette.
*
* @param recipe La recette
* @return Le fichier XLS de la recette
*/
public byte[] generateXlsForRecipe(Recipe recipe) {
return new XlsxExporter().generate(recipe);
}
/**
* Génère le fichier XLS de la recette correspondant à l'identifiant.
*
* @param id L'identifiant de la recette
* @return Le fichier XLS de la recette
*/
public byte[] generateXlsForRecipeId(Long id) {
return generateXlsForRecipe(recipeService.getById(id));
}
/**
* Génère les fichiers XLS de toutes les recettes et les écrit dans un fichier ZIP.
*
* @return Le fichier ZIP contenant tous les fichiers XLS
*/
public byte[] generateXlsForAllRecipes() {
ColorRecipesExplorerApplication.LOGGER.info("Exportation de toutes les couleurs en XLS");
Collection<Recipe> recipes = recipeService.getAll();
try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); ZipOutputStream zipOutput = new ZipOutputStream(byteOutput)) {
for (Recipe recipe : recipes) {
byte[] recipeXLS = generateXlsForRecipe(recipe);
zipOutput.putNextEntry(new ZipEntry(String.format("%s_%s.xlsx", recipe.getCompany().getName(), recipe.getName())));
zipOutput.write(recipeXLS, 0, recipeXLS.length);
zipOutput.closeEntry();
}
return byteOutput.toByteArray();
} catch (IOException ex) {
throw new RuntimeException("Impossible d'exporter toutes les recettes vers le format XLS: " + ex.getMessage());
}
}
}

View File

@ -34,10 +34,6 @@ public class MaterialService extends GenericService<Material, MaterialDao> {
this.filesService = filesService;
}
public Optional<Material> getByName(String name) {
return dao.findByName(name);
}
public List<Material> getAllByMaterialType(MaterialType materialType) {
if (materialType == null) return new ArrayList<>();
@ -77,6 +73,12 @@ public class MaterialService extends GenericService<Material, MaterialDao> {
return super.update(material);
}
@Override
public void delete(Material material) {
removeSimdut(material);
super.delete(material);
}
/**
* Vérifie si un produit est lié à un ou plusieurs mélanges.
*
@ -94,30 +96,6 @@ public class MaterialService extends GenericService<Material, MaterialDao> {
}
}
@Override
public boolean exists(Material material) {
return material != null && (super.exists(material) || dao.existsByName(material.getName()));
}
@Override
public boolean isValidForUpdate(Material material) {
if (material == null) return false;
Optional<Material> materialByCode = dao.findByName(material.getName());
return super.isValidForUpdate(material) && (materialByCode.isEmpty() || material.getId().equals(materialByCode.get().getId()));
}
/**
* Crée un FileHandler pour le produit passé en paramètre.
*
* @param material Le produit dont on veut créer un FileHandler
* @return Le FileHandler correspondant au produit.
*/
private FileHandler getFileHandlerForMaterial(Material material) {
String filename = String.format("%s_%s", material.getId(), material.getName());
return new FileHandler(filename, FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
}
/**
* Récupère le chemin vers le fichier SIMDUT d'un produit.
*
@ -165,4 +143,8 @@ public class MaterialService extends GenericService<Material, MaterialDao> {
public void removeSimdut(Material material) {
filesService.deleteFile(getSimdutPath(material));
}
public String getSimdutFileName(Material material) {
return String.format("%s_%s", material.getId(), material.getName());
}
}

View File

@ -12,7 +12,7 @@ public class MixQuantityService extends GenericService<MixQuantity, MixQuantityD
@Autowired
public MixQuantityService(MixQuantityDao mixQuantityDao) {
super(mixQuantityDao);
super(mixQuantityDao, MixQuantity.class);
}
public boolean existsByMaterial(Material material) {

View File

@ -1,7 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.model.*;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.MixCreationFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.GenericService;
@ -11,38 +10,37 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static dev.fyloz.trial.colorrecipesexplorer.web.StringBank.MATERIALS;
@Service
public class MixService extends GenericService<Mix, MixDao> {
private MaterialService materialService;
private MixQuantityService mixQuantityService;
private MixTypeService mixTypeService;
private RecipeService recipeService;
@Autowired
public MixService(MixDao mixDao, MaterialService materialService, MixQuantityService mixQuantityService, MixTypeService mixTypeService, RecipeService recipeService) {
public MixService(MixDao mixDao, MaterialService materialService, MixQuantityService mixQuantityService, MixTypeService mixTypeService) {
super(mixDao, Mix.class);
this.materialService = materialService;
this.mixQuantityService = mixQuantityService;
this.mixTypeService = mixTypeService;
this.recipeService = recipeService;
}
/**
* Récupère les produits disponibles pour un mélange.
* Le mélange peut ne pas exister.
*
* @param recipeId L'identifiant de la recette dans laquelle se trouve le mélange
* @param mixId L'identifiant du mélange (-1 si le mélange n'existe pas)
* @param recipe La recette dans laquelle se trouve le mélange
* @param mixId L'identifiant du mélange (-1 si le mélange n'existe pas)
* @return Les produits disponibles pour ce mélange
*/
public Collection<Material> getAvailableMaterialsForMixId(Long recipeId, Long mixId) {
return existsById(mixId) ? getAvailableMaterialsForMix(getById(mixId)) : getAvailableMaterialsForNewMix(recipeService.getById(recipeId));
public Collection<Material> getAvailableMaterialsForMixId(Recipe recipe, Long mixId) {
return existsById(mixId) ? getAvailableMaterialsForMix(getById(mixId)) : getAvailableMaterialsForNewMix(recipe);
}
/**
@ -65,12 +63,12 @@ public class MixService extends GenericService<Mix, MixDao> {
* @return Les produits disponibles pour le nouveau mélange
*/
public Collection<Material> getAvailableMaterialsForNewMix(Recipe recipe) {
Collection<MixType> recipeMixTypes = recipeService.getAssociatedMixesTypes(recipe);
Collection<MixType> recipeMixTypes = recipe.getMixTypes();
return materialService
.getAll()
.stream()
.filter(m -> !m.isMixType() || recipeMixTypes.contains(mixTypeService.getByMaterial(m).get()))
.filter(m -> !m.isMixType() || recipeMixTypes.contains(mixTypeService.getByMaterial(m)))
.sorted(Comparator.comparing(Material::getName))
.sorted(Comparator.comparing(m -> m.getMaterialType().getName()))
.collect(Collectors.toList());
@ -78,77 +76,81 @@ public class MixService extends GenericService<Mix, MixDao> {
@Transactional
public ModelResponseBuilder create(MixCreationFormDto formDto, @NotNull Recipe recipe) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
throw new UnsupportedOperationException("TODO");
List<Material> materials = new ArrayList<>();
for (String materialCode : formDto.getMaterials()) {
Optional<Material> found = materialService.getByName(materialCode);
if (found.isEmpty()) {
return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
}
materials.add(found.get());
}
Optional<MixType> optionalMixType = mixTypeService.createByName(formDto.getMixTypeName(), formDto.getMaterialType());
if (optionalMixType.isEmpty()) return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
MixType mixType = optionalMixType.get();
if (recipeService.hasMixType(recipe, mixType))
return modelResponseBuilder.addResponseCode(ResponseCode.MIX_TYPE_ALREADY_USED, mixType.getName());
// Crée le mélange en premier pour avoir accès à son ID pour les autres éléments
Mix mix = new Mix(recipe, mixType);
Optional<Mix> savedMix = save(mix);
if (savedMix.isPresent()) {
mix = savedMix.get();
List<MixQuantity> mixQuantities = createMixQuantities(savedMix.get(), materials, formDto.getQuantities());
mix.setMixQuantities(mixQuantities);
// Retourne aucune erreur s'il la mise à jour à lieu
if (update(mix).isPresent()) return null;
deleteMix(mix);
}
return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
// ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
//
// List<Material> materials = new ArrayList<>();
// for (String materialCode : formDto.getMaterials()) {
// Optional<Material> found = materialService.getByName(materialCode);
// if (found.isEmpty()) {
// return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
// }
//
// materials.add(found.get());
// }
//
// Optional<MixType> optionalMixType = mixTypeService.createByName(formDto.getMixTypeName(), formDto.getMaterialType());
// if (optionalMixType.isEmpty()) return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
//
// MixType mixType = optionalMixType.get();
// if (recipeService.hasMixType(recipe, mixType))
// return modelResponseBuilder.addResponseCode(ResponseCode.MIX_TYPE_ALREADY_USED, mixType.getName());
//
// // Crée le mélange en premier pour avoir accès à son ID pour les autres éléments
// Mix mix = new Mix(recipe, mixType);
//
// Optional<Mix> savedMix = save(mix);
// if (savedMix.isPresent()) {
// mix = savedMix.get();
//
// List<MixQuantity> mixQuantities = createMixQuantities(savedMix.get(), materials, formDto.getQuantities());
// mix.setMixQuantities(mixQuantities);
//
// // Retourne aucune erreur s'il la mise à jour à lieu
// if (update(mix).isPresent()) return null;
//
// deleteMix(mix);
// }
//
// return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
}
@Transactional
public ModelResponseBuilder edit(Mix mix, MixCreationFormDto formDto) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Material material = mix.getMixType().getMaterial();
throw new UnsupportedOperationException("TODO");
List<Material> materials = new ArrayList<>();
for (String materialCode : formDto.getMaterials()) {
Optional<Material> found = materialService.getByName(materialCode);
if (found.isEmpty()) {
return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
}
materials.add(found.get());
}
mix.getMixType().getMaterial().setMaterialType(formDto.getMaterialType());
mix.getMixType().setName(formDto.getMixTypeName());
material.setName(formDto.getMixTypeName());
List<MixQuantity> oldQuantities = mix.getMixQuantities();
List<MixQuantity> mixQuantities = createMixQuantities(mix, materials, formDto.getQuantities());
// Supprime les anciens MixQuantity pour éviter les doublons et les entrées inutiles dans la base de données
if (!mixQuantityService.deleteAll(oldQuantities)) {
return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
}
mix.setMixQuantities(mixQuantities);
if (materialService.update(material).isPresent() && update(mix).isPresent()) {
return null;
} else {
return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
}
// ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
// Material material = mix.getMixType().getMaterial();
//
// List<Material> materials = new ArrayList<>();
// for (String materialCode : formDto.getMaterials()) {
// Optional<Material> found = materialService.getByName(materialCode);
// if (found.isEmpty()) {
// return modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND_BY_NAME, materialCode);
// }
//
// materials.add(found.get());
// }
//
// mix.getMixType().getMaterial().setMaterialType(formDto.getMaterialType());
// mix.getMixType().setName(formDto.getMixTypeName());
// material.setName(formDto.getMixTypeName());
//
// List<MixQuantity> oldQuantities = mix.getMixQuantities();
// List<MixQuantity> mixQuantities = createMixQuantities(mix, materials, formDto.getQuantities());
//
// // Supprime les anciens MixQuantity pour éviter les doublons et les entrées inutiles dans la base de données
// if (!mixQuantityService.deleteAll(oldQuantities)) {
// return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
// }
//
// mix.setMixQuantities(mixQuantities);
// if (materialService.update(material).isPresent() && update(mix).isPresent()) {
// return null;
// } else {
// return modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING);
// }
}
@Deprecated(since = "1.3.0", forRemoval = true)
@ -160,7 +162,7 @@ public class MixService extends GenericService<Mix, MixDao> {
@Override
public void delete(Mix mix) {
mixQuantityService.deleteAll(mix.getMixQuantities());
/
super.delete(mix);
}

View File

@ -1,7 +1,8 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.MixType;
import dev.fyloz.trial.colorrecipesexplorer.core.services.GenericService;
import dev.fyloz.trial.colorrecipesexplorer.dao.MixTypeDao;
@ -13,26 +14,35 @@ import java.util.Optional;
@Service
public class MixTypeService extends GenericService<MixType, MixTypeDao> {
private MaterialTypeService materialTypeService;
@Autowired
public MixTypeService(MixTypeDao mixTypeDao, MaterialTypeService materialTypeService) {
super(mixTypeDao);
this.materialTypeService = materialTypeService;
public MixTypeService(MixTypeDao mixTypeDao) {
super(mixTypeDao, MixType.class);
}
public Optional<MixType> getByName(String name) {
return dao.findByName(name);
/**
* Récupère le type de mélange correspondant à un nom.
*
* @param name Le nom du type de mélange
* @return Le type de mélange correspondant au nom
*/
public MixType getByName(String name) {
Optional<MixType> found = dao.findByName(name);
if (found.isEmpty()) throw new EntityNotFoundException(type, ModelException.IdentifierType.NAME, name);
return found.get();
}
public Optional<MixType> getByMaterial(Material material) {
return dao.findByMaterial(material);
}
/**
* Récupère le type de mélange correspondant à un produit.
*
* @param material Le produit du type de mélange
* @return Le type de mélange correspondant au produit
*/
public MixType getByMaterial(Material material) {
Optional<MixType> found = dao.findByMaterial(material);
if (found.isEmpty())
throw new EntityNotFoundException(type, ModelException.IdentifierType.OTHER, MixType.IDENTIFIER_MATERIAL_NAME, material);
public Optional<MixType> createByName(String name, MaterialType type) {
Material mixTypeMaterial = new Material(name, 0f, true, type);
MixType mixType = new MixType(name, mixTypeMaterial);
return save(mixType);
return found.get();
}
}

View File

@ -1,11 +1,10 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.FileHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.ImageHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.model.*;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeEditorFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.GenericService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.ImagesService;
import dev.fyloz.trial.colorrecipesexplorer.dao.RecipeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -22,13 +21,15 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
private CompanyService companyService;
private MixService mixService;
private StepService stepService;
private ImagesService imagesService;
@Autowired
public RecipeService(RecipeDao recipeDao, CompanyService companyService, MixService mixService, StepService stepService) {
public RecipeService(RecipeDao recipeDao, CompanyService companyService, MixService mixService, StepService stepService, ImagesService imagesService) {
super(recipeDao, Recipe.class);
this.companyService = companyService;
this.mixService = mixService;
this.stepService = stepService;
this.imagesService = imagesService;
}
/**
@ -127,16 +128,21 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
* @return Une liste contenant le nom des images liées à la recette.
*/
public List<String> getImageFiles(Recipe recipe) {
Long recipeId = recipe.getId();
String name = recipe.getName();
String fileName = String.format("%s_%s", recipeId, name);
String imageName = getImageFileName(recipe);
File[] allImages = imagesService.getDirectoryFile().listFiles((d, n) -> n.startsWith(imageName));
File imageLocation = new File(ImageHandler.IMAGES_LOCATION);
File[] result = imageLocation.listFiles((d, n) -> n.startsWith(fileName) && n.endsWith("jpeg"));
if (allImages == null) return new ArrayList<>();
return Arrays.stream(allImages).map(File::getName).collect(Collectors.toList());
}
if (result == null) return new ArrayList<>();
// TODO test
public int getNextImageIndex(Recipe recipe) {
String imageName = getImageFileName(recipe);
List<String> allImages = getImageFiles(recipe);
List<Integer> indexes = allImages.stream().map(i -> Integer.parseInt(i.replace(imageName + "-", ""))).collect(Collectors.toList());
int maxIndex = Collections.max(indexes);
return Arrays.stream(result).map(File::getName).collect(Collectors.toList());
return maxIndex + 1;
}
/**
@ -164,32 +170,6 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
.collect(Collectors.toList());
}
/**
* Récupère les types de mélanges associés à la recette.
*
* @param recipe La recette dont on veut récupérer les types de mélange
* @return Une liste contenant les types de mélanges.
*/
public List<MixType> getAssociatedMixesTypes(Recipe recipe) {
return recipe
.getMixes()
.stream()
.map(Mix::getMixType)
.collect(Collectors.toList());
}
/**
* Vérifie si une recette contient un mélange ayant le type de mélange spécifié.
*
* @param recipe La recette
* @param mixType Le type de mélange
* @return Si la recette contient le type de mélange
*/
public boolean hasMixType(Recipe recipe, MixType mixType) {
return getAssociatedMixesTypes(recipe)
.contains(mixType);
}
private Map<Company, List<Recipe>> mappedByCompany(List<Recipe> recipes) {
List<Company> companies = companyService.getAll();
Map<Company, List<Recipe>> mappedRecipes = new HashMap<>();
@ -236,18 +216,19 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
return update(recipe);
}
private boolean removeAllFiles(Recipe recipe) {
FileHandler fileHandler;
for (String image : getImageFiles(recipe)) {
fileHandler = new FileHandler(image.replace(".jpeg", ""), FileHandler.FileContext.IMAGE, FileHandler.FileExtension.JPEG);
if (!fileHandler.deleteFile()) return false;
}
return true;
private void removeAllFiles(Recipe recipe) {
getImageFiles(recipe).forEach(f -> imagesService.deleteImage(f));
}
public String getImageFileName(Recipe recipe) {
return String.format("%s_%s", recipe.getId(), recipe.getName());
}
public String getImageFileNameWithIndex(Recipe recipe) {
return getImageFileNameWithIndex(recipe, getNextImageIndex(recipe));
}
public String getImageFileNameWithIndex(Recipe recipe, int index) {
return String.format("%s-%s", getImageFileName(recipe), index);
}
}

View File

@ -50,91 +50,69 @@ public class InventoryController {
.build();
}
/**
* Déduit les quantités utilisées de chaque matériaux présents dans le corps de la requête (JSON) de l'inventaire.
* Le JSON dans le corps de la requête doit prendre cette forme:
* {
* "mixID 1": {
* "materialID 1": "quantité 1",
* "materialID 2": "quantité 2",
* "materialID 3": "quantité 3"
* },
* "mixID 2": {
* "materialID 1": "quantité 1"
* },
* ..
* }
* S'il y a une erreur, l'opération est annulée et aucun changement n'est fait dans l'inventaire.
* <p>
* Réponse de la méthode:
* - error: Contient le message d'erreur, s'il y a lieu
* - reason: Contient la raison de l'erreur (le champ fautif)
* - success: Contient le message à affiche lors du succès de la méthode
*
* @param form Le JSON contenant les quantités à utiliser.
* @return Une réponse sous forme de Map (vers Json).
*/
@PostMapping(value = USE_INVENTORY, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Transactional
// TODO vers DTO
// TODO + vers service
public Map<String, Object> consumeMaterials(@RequestBody Map<String, Map<String, Float>> form) {
JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
throw new UnsupportedOperationException("TODO");
List<Mix> mixes = new ArrayList<>();
Map<Mix, Map<Material, Float>> quantities = new HashMap<>();
for (String mixIDStr : form.keySet()) {
Long mixID = Long.parseLong(mixIDStr);
Optional<Mix> optionalMix = mixService.getById(mixID);
if (optionalMix.isEmpty()) {
return responseBuilder
.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID)
.build();
}
Mix mix = optionalMix.get();
mixes.add(mix);
Map<String, Float> formMaterials = form.get(mixIDStr);
Map<Material, Float> mixQuantities = new HashMap<>();
for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
String materialIDAsString = String.valueOf(material.getId());
if (formMaterials.containsKey(materialIDAsString)) {
Float quantityAsString = formMaterials.get(materialIDAsString);
mixQuantities.put(material, quantityAsString);
}
}
quantities.put(mix, mixQuantities);
}
for (Mix mix : mixes) {
String errorCode = inventoryService.checkQuantities(mix, quantities.get(mix));
if (errorCode != null) {
String materialCode = materialService.getById(Long.parseLong(errorCode.split("-")[1])).orElse(new Material()).getName();
return responseBuilder
.addResponseCode(ResponseCode.NOT_ENOUGH_MATERIAL, materialCode)
.addAttribute(RESPONSE_REASON, errorCode)
.build();
}
}
for (Mix mix : mixes) {
if (!inventoryService.useMix(quantities.get(mix))) {
return responseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.build();
}
}
return responseBuilder
.addResponseCode(ResponseCode.SUCCESS_USING_MATERIALS)
.build();
// JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
//
// List<Mix> mixes = new ArrayList<>();
// Map<Mix, Map<Material, Float>> quantities = new HashMap<>();
//
// for (String mixIDStr : form.keySet()) {
// Long mixID = Long.parseLong(mixIDStr);
//
// Optional<Mix> optionalMix = mixService.getById(mixID);
// if (optionalMix.isEmpty()) {
// return responseBuilder
// .addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID)
// .build();
// }
//
// Mix mix = optionalMix.get();
// mixes.add(mix);
//
// Map<String, Float> formMaterials = form.get(mixIDStr);
// Map<Material, Float> mixQuantities = new HashMap<>();
//
// for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
// String materialIDAsString = String.valueOf(material.getId());
//
// if (formMaterials.containsKey(materialIDAsString)) {
// Float quantityAsString = formMaterials.get(materialIDAsString);
// mixQuantities.put(material, quantityAsString);
// }
// }
//
// quantities.put(mix, mixQuantities);
// }
//
// for (Mix mix : mixes) {
// String errorCode = inventoryService.checkQuantities(mix, quantities.get(mix));
// if (errorCode != null) {
// String materialCode = materialService.getById(Long.parseLong(errorCode.split("-")[1])).orElse(new Material()).getName();
//
// return responseBuilder
// .addResponseCode(ResponseCode.NOT_ENOUGH_MATERIAL, materialCode)
// .addAttribute(RESPONSE_REASON, errorCode)
// .build();
// }
// }
//
// for (Mix mix : mixes) {
// if (!inventoryService.useMix(quantities.get(mix))) {
// return responseBuilder
// .addResponseCode(ResponseCode.ERROR_SAVING)
// .build();
// }
// }
//
// return responseBuilder
// .addResponseCode(ResponseCode.SUCCESS_USING_MATERIALS)
// .build();
}
}

View File

@ -5,6 +5,7 @@ import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilde
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.MarkdownFilesService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MixService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -22,11 +23,13 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
public class OthersController {
private MixService mixService;
private RecipeService recipeService;
private MarkdownFilesService markdownService;
@Autowired
public OthersController(MixService mixService, MarkdownFilesService markdownService) {
public OthersController(MixService mixService, RecipeService recipeService, MarkdownFilesService markdownService) {
this.mixService = mixService;
this.recipeService = recipeService;
this.markdownService = markdownService;
}
@ -40,7 +43,7 @@ public class OthersController {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(MATERIAL_SELECTOR_FRAGMENT);
try {
modelResponseBuilder.addAttribute(MATERIALS, mixService.getAvailableMaterialsForMixId(recipeId, mixId));
modelResponseBuilder.addAttribute(MATERIALS, mixService.getAvailableMaterialsForMixId(recipeService.getById(recipeId), mixId));
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeId);
}

View File

@ -1,7 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.ImageHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.JSONResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
@ -9,22 +8,21 @@ import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.ImagesService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.core.utils.ControllerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@ -47,17 +45,6 @@ public class ImageFilesController {
return new ResponseEntity<>(imagesService.readImage(image), headers, HttpStatus.OK);
}
/**
* Affiche la page pour ajouter une image pour une recette.
* Cette méthode requiert l'identifiant de la recette dans l'URL.
* <p>
* Modèle de la page:
* - id: Contient l'identifiant de la recette
*
* @param model Le Model injecté par Thymeleaf
* @param id L'identifiant de la recette
* @return La page à afficher.
*/
@GetMapping(ADD_IMAGE_SPECIFIC)
public ModelAndView getPage(ModelAndView model, @PathVariable Long id) {
return new ModelResponseBuilder(model)
@ -66,94 +53,32 @@ public class ImageFilesController {
.build();
}
/**
* Permet à l'utilisateur d'uploader une image.
* <p>
* L'upload échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action.
* - La recette n'existe pas
* - Une erreur est survenue lors de la création du fichier de l'image
* - Une erreur est survenue lors du transfert de l'image vers le fichier
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipeCode: Contient la couleur de la recette
* - id: Contient l'identifiant de la recette
* <p>
* REQUIERT UNE AUTORISATION
*
* @param id L'identifiant de la recette
* @param image L'image uploadée
* @return La page à afficher.
*/
@PostMapping(ADD_IMAGE)
public ModelAndView addImage(Long id, MultipartFile image) throws IOException {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(ControllerUtils.redirect(EDITOR_RECIPE_SPECIFIC.replace("{id}", String.valueOf(id))));
// Vérifie que le fichier est bien une image
if (ImageIO.read(image.getInputStream()) == null) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.FILE_NOT_IMAGE)
.build(), id);
}
Optional<Recipe> optionalRecipe = recipeService.getById(id);
if (optionalRecipe.isEmpty()) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, id)
.build(), id);
}
Recipe recipe = optionalRecipe.get();
ImageHandler imageHandler = new ImageHandler(recipe, recipeService);
if (!imageHandler.createFile()) {
return getPage(modelResponseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE)
.build(), id);
}
modelResponseBuilder
.addResponseData(ResponseDataType.RECIPE_CODE, recipe.getName())
.addResponseData(ResponseDataType.RECIPE_ID, recipe.getId());
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder().withRedirect(EDITOR_RECIPE_SPECIFIC, id);
try {
// Si je n'utilise pas le path, il cherche un fichier dans les tmp ?
image.transferTo(new File(imageHandler.getFile().getAbsolutePath()));
if (imagesService.isImage(image.getInputStream())) {
modelResponseBuilder.addResponseCode(ResponseCode.FILE_NOT_IMAGE);
} else {
Recipe recipe = recipeService.getById(id);
return modelResponseBuilder.build();
} catch (IOException e) {
ColorRecipesExplorerApplication.LOGGER.error("Erreur inconnue lors de la création d'une image", e);
modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE);
return getPage(modelResponseBuilder.build(), id);
if (imagesService.writeMultipartImage(image, recipeService.getImageFileNameWithIndex(recipe)))
return modelResponseBuilder.build();
else modelResponseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE);
}
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, id);
}
return getPage(modelResponseBuilder.build(), id);
}
/**
* Permet à l'utilisateur de supprimer une image.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Une erreur est survenue lors de la suppression du fichier
* <p>
* Réponse de la méthode:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param form Le formulaire contenant les informations sur l'opération
* @return La réponse sous forme de Map (pour y accéder en Json)
*/
@PostMapping(value = DELETE_IMAGE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Object> deleteImage(@RequestBody Map<String, String> form) {
JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
public Map<String, Object> deleteImage(String image) {
imagesService.deleteImage(image);
ImageHandler imageHandler = new ImageHandler(form.get("image"), recipeService);
if (!imageHandler.deleteFile()) {
responseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_IMAGE);
}
return responseBuilder.build();
return new JSONResponseBuilder().build();
}
}

View File

@ -1,7 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.FileHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Material;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.SimdutService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.CLOSE_TAB;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.SIMDUT_FILES;
@ -22,41 +21,38 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.SIMDUT_FILES
@Controller
public class SIMDUTFilesController {
private MaterialService materialService;
private SimdutService simdutService;
@Autowired
public SIMDUTFilesController(MaterialService materialService) {
this.materialService = materialService;
public SIMDUTFilesController(SimdutService simdutService) {
this.simdutService = simdutService;
}
@GetMapping(SIMDUT_FILES)
public ResponseEntity<byte[]> getFile(HttpServletRequest request, @PathVariable Long id) {
Optional<Material> optionalMaterial = materialService.getById(id);
HttpHeaders headers = new HttpHeaders();
if (optionalMaterial.isEmpty()) {
headers.add("Location", request.getHeader("referer"));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
FileHandler fileHandler = new FileHandler(String.format("%s_%s", id, optionalMaterial.get().getName()), FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
if (!fileHandler.isValid()) {
try {
if (simdutService.simdutExistsForMaterialId(id)) {
byte[] simdutContent = simdutService.readSimdutForMaterialId(id);
headers.setContentType(MediaType.APPLICATION_PDF);
return new ResponseEntity<>(simdutContent, headers, HttpStatus.OK);
} else {
headers.add("Location", request.getHeader("referer"));
}
} catch (EntityNotFoundException ex) {
headers.add("Location", "/" + CLOSE_TAB);
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
byte[] fileContent = fileHandler.readFile();
headers.setContentType(MediaType.APPLICATION_PDF);
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
@PostMapping(SIMDUT_FILES)
public ResponseEntity<Void> getFile(@PathVariable Long id) {
Optional<Material> optionalMaterial = materialService.getById(id);
if (optionalMaterial.isEmpty()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
FileHandler fileHandler = new FileHandler(String.format("%s_%s", id, optionalMaterial.get().getName()), FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
if (!fileHandler.isValid()) return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.status(HttpStatus.FOUND).build();
try {
return ResponseEntity.status(simdutService.simdutExistsForMaterialId(id) ? HttpStatus.FOUND : HttpStatus.NOT_FOUND).build();
} catch (EntityNotFoundException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
}

View File

@ -1,9 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.xlsx.XlsxExporter;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.services.files.XlsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -14,76 +12,50 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.ALL_RECIPES_XLS;
import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.RECIPE_XLS;
// TODO Une grande partie du code de ce controlleur devrait se trouver dans un service
@Controller
public class XlsExporterController {
private RecipeService recipeService;
private XlsService xlsService;
@Autowired
public XlsExporterController(RecipeService recipeService) {
this.recipeService = recipeService;
public XlsExporterController(XlsService xlsService) {
this.xlsService = xlsService;
}
@GetMapping(RECIPE_XLS)
public ResponseEntity<byte[]> getXlsForRecipe(HttpServletRequest request, @PathVariable Long id) {
HttpHeaders headers = new HttpHeaders();
Optional<Recipe> optionalRecipe = recipeService.getById(id);
if (optionalRecipe.isEmpty()) {
try {
byte[] xlsContent = xlsService.generateXlsForRecipeId(id);
return ResponseEntity.ok()
.headers(headers)
.contentLength(xlsContent.length)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(xlsContent);
} catch (EntityNotFoundException ex) {
headers.add(HttpHeaders.LOCATION, request.getHeader("referer"));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
byte[] recipeXLS = new XlsxExporter().generate(optionalRecipe.get());
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.ok()
.headers(headers)
.contentLength(recipeXLS.length)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(recipeXLS);
}
@GetMapping(value = ALL_RECIPES_XLS, produces = "application/zip")
public ResponseEntity<byte[]> getAllXls() throws IOException {
HttpHeaders headers = new HttpHeaders();
ColorRecipesExplorerApplication.LOGGER.info("Exportation de toutes les couleurs en XLS");
ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(byteOS);
Collection<Recipe> recipes = recipeService.getAll();
for (Recipe recipe : recipes) {
byte[] recipeXLS = new XlsxExporter().generate(recipe);
if (recipeXLS.length <= 0) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
out.putNextEntry(new ZipEntry(String.format("%s_%s.xlsx", recipe.getCompany().getName(), recipe.getName())));
out.write(recipeXLS, 0, recipeXLS.length);
out.closeEntry();
}
out.close();
byte[] zipContent = byteOS.toByteArray();
byteOS.close();
byte[] allXlsContent = xlsService.generateXlsForAllRecipes();
return ResponseEntity.ok()
.headers(headers)
.contentLength(zipContent.length)
.contentLength(allXlsContent.length)
.contentType(MediaType.parseMediaType("application/zip"))
.body(zipContent);
.body(allXlsContent);
}
}

View File

@ -41,9 +41,9 @@ public class CompanyRemoverController {
try {
companyService.deleteById(id);
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_COMPANY, companyService.getById(id));
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_COMPANY, companyService.getById(id).getName());
} catch (EntityLinkedException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.COMPANY_LINKED, companyService.getById(id));
modelResponseBuilder.addResponseCode(ResponseCode.COMPANY_LINKED, companyService.getById(id).getName());
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.COMPANY_NOT_FOUND, id);
}

View File

@ -1,5 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.removers;
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;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
@ -28,12 +30,6 @@ public class MaterialRemoverController {
this.materialService = materialService;
}
/**
* Affiche la page de suppression des produits
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(REMOVER_MATERIAL)
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
@ -42,45 +38,20 @@ public class MaterialRemoverController {
.build();
}
/**
* Permet à l'utilisateur de supprimer un produit.
* Cette méthode requiert l'identifiant du produit dans l'URL.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Le produit n'existe pas
* - Le produit est lié à d'autres mélanges
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - materialCode: Contient le code de produit du produit supprimé
* <p>
* REQUIERT UNE AUTORISATION
*
* @param id L'identifiant du produit à supprimer
* @return La page à afficher.
*/
@PostMapping(REMOVER_MATERIAL_SPECIFIC)
public ModelAndView removeMaterial(@PathVariable Long id) {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder("");
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Optional<Material> optionalMaterial = materialService.getById(id);
if (optionalMaterial.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND, id);
} else {
Material material = optionalMaterial.get();
try {
materialService.deleteById(id);
if (materialService.deleteIfNotLinked(material)) {
responseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL, material.getName());
if (!materialService.removeSimdut(material)) {
responseBuilder.addResponseCode(ResponseCode.ERROR_SAVING_SIMDUT);
}
} else {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_LINKED, material.getName());
}
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL, materialService.getById(id).getName());
} catch (EntityLinkedException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_LINKED, materialService.getById(id).getName());
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_NOT_FOUND, id);
}
return getPage(responseBuilder.build());
return getPage(modelResponseBuilder.build());
}
}

View File

@ -1,5 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.removers;
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;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
@ -38,21 +40,18 @@ public class MaterialTypeRemoverController {
@PostMapping(REMOVER_MATERIAL_TYPE_SPECIFIC)
public ModelAndView removeMaterialType(@PathVariable Long id) {
ModelResponseBuilder responseBuilder = new ModelResponseBuilder("");
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Optional<MaterialType> optionalMaterialType = materialTypeService.getById(id);
if (optionalMaterialType.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, id);
} else {
MaterialType materialType = optionalMaterialType.get();
try {
materialTypeService.deleteById(id);
if (materialTypeService.deleteIfNotLinked(materialType)) {
responseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL_TYPE, materialType.getName());
} else {
responseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_LINKED, materialType.getName());
}
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_MATERIAL_TYPE, materialTypeService.getById(id).getName());
} catch (EntityLinkedException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_LINKED, materialTypeService.getById(id).getName());
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.MATERIAL_TYPE_NOT_FOUND, id);
}
return getPage(responseBuilder.build());
return getPage(modelResponseBuilder.build());
}
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.removers;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
@ -25,59 +26,26 @@ public class RecipeRemoverController {
this.recipeService = recipeService;
}
/**
* Affiche la liste de toutes les recettes.
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(REMOVER_RECIPE)
public ModelAndView listRecipes(ModelAndView model) {
public ModelAndView getPage(ModelAndView model) {
return new ModelResponseBuilder(model)
.withView(REMOVER_RECIPE)
.addResponseData(ResponseDataType.RECIPE_MAP, recipeService.getRecipesByCompany())
.build();
}
/**
* Permet à l'utilisateur de supprimer une recette.
* Cette méthode requiert l'identifiant de la recette dans l'URL.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - La recette n'existe pas
* - Une erreur est survenue lors de la suppression dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipeCode: Contient la couleur de la recette
* <p>
* REQUIERT UNE AUTORISATION
*
* @param id L'identifiant de la recette
* @return La page à afficher.
*/
@PostMapping(REMOVER_RECIPE_SPECIFIC)
public ModelAndView removeRecipe(@PathVariable Long id) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder("");
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder();
Optional<Recipe> optionalRecipe = recipeService.getById(id);
if (optionalRecipe.isEmpty()) {
return listRecipes(
modelResponseBuilder
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, id)
.build());
try {
recipeService.deleteById(id);
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_RECIPE, recipeService.getById(id).getName());
} catch (EntityNotFoundException ex) {
modelResponseBuilder.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, id);
}
Recipe recipe = optionalRecipe.get();
if (!recipeService.deleteRecipe(recipe)) {
return listRecipes(
modelResponseBuilder
.addResponseCode(ResponseCode.ERROR_SAVING)
.build());
}
modelResponseBuilder.addResponseCode(ResponseCode.SUCCESS_DELETING_RECIPE, recipe.getName());
return listRecipes(modelResponseBuilder.build());
return getPage(modelResponseBuilder.build());
}
}