Améliorations InventoryController et InventoryService
This commit is contained in:
parent
7ea34423bc
commit
747d593c40
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue