Améliorations InventoryController et InventoryService

This commit is contained in:
FyloZ 2020-02-21 11:05:00 -05:00
parent 7ea34423bc
commit 747d593c40
7 changed files with 162 additions and 143 deletions

View File

@ -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());
}
}

View File

@ -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<Long> materials;
private List<Float> quantities;
}

View File

@ -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<Material, Float> 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<InventoryDto> mixes) throws TooLowQuantityException {
for (InventoryDto mix : mixes) {
List<Material> 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<Material, Float> quantities) {
for (Map.Entry<Material, Float> 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<Material> convertMaterialIdsToMaterials(List<Long> ids) {
return ids.stream()
.map(i -> materialService.getById(i))
.collect(Collectors.toList());
}
}

View File

@ -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<String, Object> consumeMaterials(@RequestBody Map<String, Map<String, Float>> form) {
throw new UnsupportedOperationException("TODO");
public Map<String, Object> consumeMaterials(@RequestBody List<InventoryDto> mixes) {
JSONResponseBuilder jsonResponseBuilder = new JSONResponseBuilder();
// 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();
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();
}
}

View File

@ -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

View File

@ -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

View File

@ -98,7 +98,7 @@
<div>
<table class="mix"
th:id="'mix-' + ${mix.id}"
th:mixId="${mix.id}">
th:data-mixId="${mix.id}">
<tr>
<th th:text="#{keyword.material}"></th>
<th th:text="#{keyword.type}"></th>
@ -132,17 +132,18 @@
th:text="${mixQuantity.quantity}"></p>
</td>
<td class="quantityColumn">
<input th:if="${!material.isMixType()}"
class="quantityCustomizer noStyle"
min="0.001" step="0.001"
th:data-materialId="${material.id}"
th:data-mixId="${mix.id}"
th:data-quantityML="${mixQuantity.quantity}"
th:data-usePercentages="${material.materialType.usePercentages}"
th:data-defaultvalue="${mixQuantity.quantity}"
th:value="${mixQuantity.quantity}"
th:disabled="${material.materialType.usePercentages}"
type="number"/></td>
<input
class="quantityCustomizer noStyle"
min="0.001" step="0.001"
th:data-materialId="${material.id}"
th:data-mixId="${mix.id}"
th:data-quantityML="${mixQuantity.quantity}"
th:data-usePercentages="${material.materialType.usePercentages}"
th:data-isMixType="${material.isMixType}"
th:data-defaultvalue="${mixQuantity.quantity}"
th:value="${mixQuantity.quantity}"
th:disabled="${material.materialType.usePercentages}"
type="number"/></td>
<td class="unitsColumn">
<p class="inventoryQuantityUnits"
th:unless="${material.materialType.usePercentages}">mL</p>
@ -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");