diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/TooLowQuantityException.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/TooLowQuantityException.java new file mode 100644 index 0000000..6aadcab --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/exception/TooLowQuantityException.java @@ -0,0 +1,25 @@ +package dev.fyloz.trial.colorrecipesexplorer.core.exception; + +import dev.fyloz.trial.colorrecipesexplorer.core.model.Material; +import lombok.Getter; + +@Getter +public class TooLowQuantityException extends Exception { + + private Long mixId; + private Material material; + private Float storedQuantity; + private Float requestQuantity; + + private String response; + + public TooLowQuantityException(Long mixId, Material material, Float storedQuantity, Float requestQuantity) { + this.mixId = mixId; + this.material = material; + this.storedQuantity = storedQuantity; + this.requestQuantity = requestQuantity; + + response = String.format("%s_%s", mixId, material.getId()); + } + +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/dto/InventoryDto.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/dto/InventoryDto.java new file mode 100644 index 0000000..1c62974 --- /dev/null +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/dto/InventoryDto.java @@ -0,0 +1,14 @@ +package dev.fyloz.trial.colorrecipesexplorer.core.model.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class InventoryDto { + + private Long mixId; + private List materials; + private List quantities; + +} diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/InventoryService.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/InventoryService.java index b8d4575..e42fc74 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/InventoryService.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/InventoryService.java @@ -1,13 +1,13 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services; -import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException; +import dev.fyloz.trial.colorrecipesexplorer.core.exception.TooLowQuantityException; import dev.fyloz.trial.colorrecipesexplorer.core.model.Material; -import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix; -import dev.fyloz.trial.colorrecipesexplorer.core.model.MixQuantity; +import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.InventoryDto; import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.Map; +import java.util.List; import java.util.stream.Collectors; @Service @@ -19,35 +19,60 @@ public class InventoryService { this.materialService = materialService; } - public String checkQuantities(Mix mix, Map quantities) { - for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) { - if (!material.isMixType()) { - Float quantity = quantities.get(material); + /** + * Utilise tous les produits demandés. + * + * @param mixes Tous les produits à utiliser + * @throws TooLowQuantityException Lorsqu'il n'y a pas assez d'un produit dans l'inventaire + */ + @Transactional + public void use(List mixes) throws TooLowQuantityException { + for (InventoryDto mix : mixes) { + List materials = convertMaterialIdsToMaterials(mix.getMaterials()); - if (quantity > material.getInventoryQuantity()) { - return String.format("%s-%s", mix.getId(), material.getId()); - } + for (int i = 0; i < materials.size(); i++) { + Material material = materials.get(i); + Float quantity = mix.getQuantities().get(i); + + if (!hasEnoughStock(material, quantity)) + throw new TooLowQuantityException(mix.getMixId(), material, material.getInventoryQuantity(), quantity); + + useMaterial(material, quantity); } } - - return null; } - public boolean useMix(Map quantities) { - for (Map.Entry entry : quantities.entrySet()) { - Material material = entry.getKey(); + /** + * Utilise un produit. + * + * @param material Le produit à utiliser + * @param quantity La quantité à utiliser + */ + private void useMaterial(Material material, Float quantity) { + material.setInventoryQuantity(material.getInventoryQuantity() - quantity); + materialService.update(material); + } - if (!material.isMixType()) { - material.setInventoryQuantity(material.getInventoryQuantity() - entry.getValue()); + /** + * Vérifie s'il y a assez d'un produit dans l'inventaire pour l'utiliser. + * + * @param material Le produit à vérifier + * @param quantity La quantité à utiliser + * @return S'il y a assez de produit dans l'inventaire pour l'utiliser + */ + private boolean hasEnoughStock(Material material, Float quantity) { + return material.getInventoryQuantity() >= quantity; + } - try { - materialService.update(material); - } catch (EntityNotFoundException ex) { - return false; - } - } - } - - return true; + /** + * Convertit une liste d'identifiant de produit en produits. + * + * @param ids Les identifiant à convertir + * @return Les produits correspondant aux identifiants + */ + private List convertMaterialIdsToMaterials(List ids) { + return ids.stream() + .map(i -> materialService.getById(i)) + .collect(Collectors.toList()); } } diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/InventoryController.java b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/InventoryController.java index baf93f9..a169bab 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/InventoryController.java +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/web/controller/InventoryController.java @@ -1,24 +1,26 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller; +import dev.fyloz.trial.colorrecipesexplorer.core.exception.TooLowQuantityException; 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; import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType; -import dev.fyloz.trial.colorrecipesexplorer.core.model.Material; -import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix; -import dev.fyloz.trial.colorrecipesexplorer.core.model.MixQuantity; +import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.InventoryDto; import dev.fyloz.trial.colorrecipesexplorer.core.services.InventoryService; import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialService; import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MaterialTypeService; -import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MixService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static dev.fyloz.trial.colorrecipesexplorer.web.StringBank.RESPONSE_REASON; @@ -30,14 +32,12 @@ public class InventoryController { private InventoryService inventoryService; private MaterialService materialService; - private MixService mixService; private MaterialTypeService materialTypeService; @Autowired - public InventoryController(InventoryService inventoryService, MaterialService materialService, MixService mixService, MaterialTypeService materialTypeService) { + public InventoryController(InventoryService inventoryService, MaterialService materialService, MaterialTypeService materialTypeService) { this.inventoryService = inventoryService; this.materialService = materialService; - this.mixService = mixService; this.materialTypeService = materialTypeService; } @@ -45,7 +45,7 @@ public class InventoryController { public ModelAndView getPage(ModelAndView model) { return new ModelResponseBuilder(model) .withView(INVENTORY) - .addResponseData(ResponseDataType.MATERIALS, materialService.getAllOrdered().stream().filter(m -> !m.isMixType()).collect(Collectors.toList())) + .addResponseData(ResponseDataType.MATERIALS, materialService.getAllNotMixType()) .addResponseData(ResponseDataType.MATERIAL_TYPES, materialTypeService.getAll()) .build(); } @@ -53,66 +53,18 @@ public class InventoryController { @PostMapping(value = USE_INVENTORY, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @Transactional - // TODO vers DTO - // TODO + vers service - public Map consumeMaterials(@RequestBody Map> form) { - throw new UnsupportedOperationException("TODO"); + public Map consumeMaterials(@RequestBody List mixes) { + JSONResponseBuilder jsonResponseBuilder = new JSONResponseBuilder(); -// JSONResponseBuilder responseBuilder = new JSONResponseBuilder(); -// -// List mixes = new ArrayList<>(); -// Map> quantities = new HashMap<>(); -// -// for (String mixIDStr : form.keySet()) { -// Long mixID = Long.parseLong(mixIDStr); -// -// Optional optionalMix = mixService.getById(mixID); -// if (optionalMix.isEmpty()) { -// return responseBuilder -// .addResponseCode(ResponseCode.MIX_NOT_FOUND, mixID) -// .build(); -// } -// -// Mix mix = optionalMix.get(); -// mixes.add(mix); -// -// Map formMaterials = form.get(mixIDStr); -// Map 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(); + try { + inventoryService.use(mixes); + + jsonResponseBuilder.addResponseCode(ResponseCode.SUCCESS_USING_MATERIALS); + } catch (TooLowQuantityException ex) { + jsonResponseBuilder.addResponseCode(ResponseCode.NOT_ENOUGH_MATERIAL, ex.getMaterial().getName()) + .addAttribute(RESPONSE_REASON, ex.getResponse()); + } + + return jsonResponseBuilder.build(); } } diff --git a/src/main/resources/lang/responses_en.properties b/src/main/resources/lang/responses_en.properties index 7f310ee..ddac395 100644 --- a/src/main/resources/lang/responses_en.properties +++ b/src/main/resources/lang/responses_en.properties @@ -6,7 +6,7 @@ response.13=There is already a banner named {0} response.14=The material {0} is linked to one or more recipes, edit them first response.15=The banner {0} is linked to one or more recipes, delete them first response.16=The mix with the ID {0} is not linked to the recipe with the ID {1} -response.17=There is not enough {0} in inventory for this recipe +response.17=There is not enough {0} in inventory response.18=This recipe already contains a mix of the type {0} response.19=No material type with the name {0} has been found response.2=The recipe's informations have been saved diff --git a/src/main/resources/lang/responses_fr.properties b/src/main/resources/lang/responses_fr.properties index 73065b1..4852b15 100644 --- a/src/main/resources/lang/responses_fr.properties +++ b/src/main/resources/lang/responses_fr.properties @@ -6,7 +6,7 @@ response.13=Il y a déjà une bannière s''appellant {0} response.14=Le produit {0} est lié à une ou plusieurs recettes, veuillez les modifier d'abord response.15=La bannière {0} est liée à une ou plusieurs recettes, veuillez les supprimer d'abord response.16=Le mélange ayant l''identifiant {0} n''est pas associé à la recette ayant l''identifiant {1} -response.17=Il n''y a pas assez de {0} en inventaire pour cette recette +response.17=Il n''y a pas assez de {0} en inventaire response.18=Cette recette contient déjà un mélange du type {0} response.19=Aucun type de produit ayant le nom {0} n''a été trouvée response.2=Les informations de la recette ont été sauvegardées diff --git a/src/main/resources/templates/recipe/explore.html b/src/main/resources/templates/recipe/explore.html index f0e2192..866503d 100644 --- a/src/main/resources/templates/recipe/explore.html +++ b/src/main/resources/templates/recipe/explore.html @@ -98,7 +98,7 @@
+ th:data-mixId="${mix.id}"> @@ -132,17 +132,18 @@ th:text="${mixQuantity.quantity}">

+
-

mL

@@ -265,46 +266,24 @@ $("#useSubmit").on({ click: function () { - let formData = []; + showConfirm(formatMessage("[[#{inventory.askUseRecipe}]]"), false, () => { + let formData = []; - $(".mix").each(function() { - const mixId = $(this).data("mixid"); - const materials = []; + let i = 0; + $(".mixContainer").each(function () { + formData[i++] = getMixQuantities($(this)); + }); - let i = 0; - $(this).find(".quantityCustomizer").each(function() { - materials[i] = $(this).data("materialid") - }); + clearNotEnoughClasses(); + sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r)); }); - - $(".quantityCustomizer").each(function () { - const materialId = $(this).data("materialid"); - const mixId = $(this).data("mixid"); - - if (formData[mixId] === undefined) { - formData[mixId] = {}; - } - - formData[mixId][materialId] = e.dataset.quantityml; - }); - - clearNotEnoughClasses(); - sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r)); } }); $(".useMixSubmit").on({ click: function () { showConfirm(formatMessage("[[#{inventory.askUseMix}]]"), false, () => { - let formData = {}; - - $(this).parents(".mixContainer").find(".quantityCustomizer").each(function () { - const materialId = $(this).data("materialid"); - const mixId = $(this).data("mixid"); - - if (formData[mixId] === undefined) formData[mixId] = {}; - formData[mixId][materialId] = $(this).data("quantityml"); - }); + let formData = [getMixQuantities($(this).parents(".mixContainer"))]; clearNotEnoughClasses(); sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r)); @@ -329,7 +308,7 @@ } function displayNotEnoughReason(reason) { - const splitReason = reason.split("-"); + const splitReason = reason.split("_"); $(`#mix-${splitReason[0]}`).find(`#material-${splitReason[1]}`).addClass("notEnough"); } @@ -390,6 +369,30 @@ }); } + function getMixQuantities(mixContainer) { + mix = $(mixContainer).find(".mix"); + + const mixId = mix.data("mixid"); + const materials = []; + const quantities = []; + + let j = 0; + mix.find(".quantityCustomizer").each(function () { + if (!$(this).data("ismixtype")) { + materials[j] = $(this).data("materialid"); + quantities[j] = $(this).data("quantityml"); + + j++; + } + }); + + return { + mixId: mixId, + materials: materials, + quantities: quantities + }; + } + function computeQuantities(customizer, computeTotal) { const mix = customizer.parents(".mixContainer"); const mixCustomizers = mix.find(".quantityCustomizer");