Author: bleny Date: 2013-04-24 14:48:43 +0200 (Wed, 24 Apr 2013) New Revision: 123 Url: http://forge.codelutin.com/projects/franciaflex-magalie/repository/revisions... Log: refs #2165 introduce UI for reception Added: trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/PrepareArticleReceptionAction.java trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/ReceiveArticleAction.java trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception-input.jsp trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception.jsp trunk/magalie-web/src/main/webapp/WEB-INF/content/receive-article-input.jsp trunk/magalie-web/src/main/webapp/js/magalie.js trunk/magalie-web/src/main/webapp/js/receive-article-input.js Modified: trunk/magalie-web/src/main/webapp/WEB-INF/content/choose-activity.jsp trunk/magalie-web/src/main/webapp/WEB-INF/content/withdraw-item-input.jsp trunk/magalie-web/src/main/webapp/css/magalie.css trunk/magalie-web/src/main/webapp/js/withdraw-item-input.js Added: trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/PrepareArticleReceptionAction.java =================================================================== --- trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/PrepareArticleReceptionAction.java (rev 0) +++ trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/PrepareArticleReceptionAction.java 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,64 @@ +package com.franciaflex.magalie.web.action; + +import com.franciaflex.magalie.persistence.entity.Building; +import com.franciaflex.magalie.persistence.entity.StoredArticle; +import com.franciaflex.magalie.persistence.entity.Supplier; +import com.franciaflex.magalie.services.service.ReceptionService; +import com.franciaflex.magalie.web.MagalieActionSupport; +import com.franciaflex.magalie.web.MagalieSession; + +import java.util.List; + +public class PrepareArticleReceptionAction extends MagalieActionSupport { + + protected String supplierId; + + protected ReceptionService service; + + protected MagalieSession session; + + protected List<Supplier> receivedSuppliers; + + protected List<StoredArticle> receivedArticles; + + public void setSupplierId(String supplierId) { + this.supplierId = supplierId; + } + + public void setService(ReceptionService service) { + this.service = service; + } + + public void setSession(MagalieSession session) { + this.session = session; + } + + @Override + public String input() { + + Building building = session.getBuilding(); + + receivedSuppliers = service.getReceivedSuppliers(building); + + return INPUT; + + } + + public List<Supplier> getReceivedSuppliers() { + return receivedSuppliers; + } + + @Override + public String execute() { + + receivedArticles = service.getReceivedArticles(supplierId); + + return SUCCESS; + + } + + public List<StoredArticle> getReceivedArticles() { + return receivedArticles; + } + +} Added: trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/ReceiveArticleAction.java =================================================================== --- trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/ReceiveArticleAction.java (rev 0) +++ trunk/magalie-web/src/main/java/com/franciaflex/magalie/web/action/ReceiveArticleAction.java 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,58 @@ +package com.franciaflex.magalie.web.action; + +import com.franciaflex.magalie.services.service.ReceptionService; +import com.franciaflex.magalie.services.service.ReceptionTask; +import com.franciaflex.magalie.web.MagalieActionSupport; +import org.apache.struts2.convention.annotation.Result; +import org.apache.struts2.convention.annotation.Results; +import org.apache.struts2.json.JSONException; +import org.apache.struts2.json.JSONUtil; + +@Results({ + @Result(name="success", type="redirectAction", params = { "actionName", "prepare-article-reception!input" }) +}) +public class ReceiveArticleAction extends MagalieActionSupport { + + protected ReceptionService service; + + protected String storedArticleId; + + protected ReceptionTask receptionTask; + + public void setService(ReceptionService service) { + this.service = service; + } + + public void setStoredArticleId(String storedArticleId) { + this.storedArticleId = storedArticleId; + } + + @Override + public String input() { + + receptionTask = service.getReceptionTask(storedArticleId); + + return INPUT; + + } + + public ReceptionTask getReceptionTask() { + return receptionTask; + } + + public String getModelAsJson() throws JSONException { + + String json = JSONUtil.serialize(getReceptionTask()); + + return json; + + } + + @Override + public String execute() { + + return SUCCESS; + + } + +} Modified: trunk/magalie-web/src/main/webapp/WEB-INF/content/choose-activity.jsp =================================================================== --- trunk/magalie-web/src/main/webapp/WEB-INF/content/choose-activity.jsp 2013-04-24 09:45:30 UTC (rev 122) +++ trunk/magalie-web/src/main/webapp/WEB-INF/content/choose-activity.jsp 2013-04-24 12:48:43 UTC (rev 123) @@ -28,6 +28,7 @@ <s:url namespace="/" action="fulfil-kanban!input" id="fulfilKanbanUrl"/> <s:url namespace="/" action="deliver-requested-article!input" id="deliverRequestedArticleUrl"/> +<s:url namespace="/" action="prepare-article-reception!input" id="prepareArticleReceptionUrl"/> <s:url namespace="/" action="logout" id="logoutUrl"/> <header> @@ -41,5 +42,5 @@ <s:a href="%{fulfilKanbanUrl}" cssClass="btn btn-block">Traiter kanbans</s:a> <s:a href="%{deliverRequestedArticleUrl}" cssClass="btn btn-block">Traiter listes à servir</s:a> -<s:a href="%{}" cssClass="btn btn-block">Traiter les réceptions fournisseurs</s:a> +<s:a href="%{prepareArticleReceptionUrl}" cssClass="btn btn-block">Traiter les réceptions fournisseurs</s:a> <s:a href="%{logoutUrl}" cssClass="btn btn-block">Déconnexion</s:a> Added: trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception-input.jsp =================================================================== --- trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception-input.jsp (rev 0) +++ trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception-input.jsp 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,16 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %> +<%@ taglib prefix="s" uri="/struts-tags" %> + +<ul> + <s:iterator value="receivedSuppliers"> + <li> + <s:url namespace="/" action="prepare-article-reception" id="prepareArticleReceptionUrl"> + <s:param name="supplierId" value="%{id}" /> + </s:url> + <s:a href="%{prepareArticleReceptionUrl}"><s:property value="name" /></s:a> + </li> + </s:iterator> +</ul> + +<s:url namespace="/" action="choose-activity" id="chooseActivityUrl"/> +<s:a href="%{chooseActivityUrl}" cssClass="btn">Changer d'activité</s:a> Added: trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception.jsp =================================================================== --- trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception.jsp (rev 0) +++ trunk/magalie-web/src/main/webapp/WEB-INF/content/prepare-article-reception.jsp 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,17 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %> +<%@ taglib prefix="s" uri="/struts-tags" %> + +<ul> + <s:iterator value="receivedArticles"> + <li> + <s:url namespace="/" action="receive-article!input" id="receiveArticleUrl"> + <s:param name="storedArticleId" value="%{id}" /> + </s:url> + <s:a href="%{receiveArticleUrl}"> + <s:property value="quantity" /> + <s:property value="article.unit" /> + <s:property value="article.description" /> + </s:a> + </li> + </s:iterator> +</ul> Added: trunk/magalie-web/src/main/webapp/WEB-INF/content/receive-article-input.jsp =================================================================== --- trunk/magalie-web/src/main/webapp/WEB-INF/content/receive-article-input.jsp (rev 0) +++ trunk/magalie-web/src/main/webapp/WEB-INF/content/receive-article-input.jsp 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,42 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %> +<%@ taglib prefix="s" uri="/struts-tags" %> + +<head> + <script src="<s:url value='/js/magalie.js' />"></script> + <script> + var model = <s:property value="modelAsJson" escapeHtml="false" />; + </script> + <script src="<s:url value='/js/receive-article-input.js' />"></script> + <title>Réception d'un article</title> +</head> + +<header> + <dl class="dl-horizontal"> + <dt>Réf.</dt> + <dd><s:property value="receptionTask.storedArticle.article.code" /></dd> + <dt>Desc.</dt> + <dd><s:property value="receptionTask.storedArticle.article.description" /></dd> + <dt>Stocké</dt> + <dd><span id="stored">0</span> / <s:property value="receptionTask.storedArticle.quantity" /> <s:property value="receptionTask.storedArticle.article.unit" /></dd> + </dl> +</header> +<section id="locations"> +</section> +<s:form cssClass="form-horizontal"> + <s:textfield name="locationBarcode" label="Empl." inputAppendIcon="barcode" cssClass="input-small" /> + <s:textfield name="quantity" label="Qté" inputAppend="%{receptionTask.storedArticle.article.unit}" cssClass="input-mini" /> + <s:hidden name="confirmation" /> + + <div class="btn-group"> + <s:submit name="full" value="Plein" cssClass="btn btn-danger btn-small" /> + <s:submit name="next" value="Suivant" cssClass="btn btn-primary btn-small" /> + </div> +</s:form> +<div id="locationTemplate" class="template"> + <dl class="dl-horizontal"> + <dt>Empl.</dt> + <dd><span data="warehouse.code"></span> <span data="code"></span></dd> + <dt>Prélev.</dt> + <dd><span data="stored">0</span> <s:property value="receptionTask.storedArticle.article.unit" /></dd> + </dl> +</div> Modified: trunk/magalie-web/src/main/webapp/WEB-INF/content/withdraw-item-input.jsp =================================================================== --- trunk/magalie-web/src/main/webapp/WEB-INF/content/withdraw-item-input.jsp 2013-04-24 09:45:30 UTC (rev 122) +++ trunk/magalie-web/src/main/webapp/WEB-INF/content/withdraw-item-input.jsp 2013-04-24 12:48:43 UTC (rev 123) @@ -24,6 +24,7 @@ <%@ taglib prefix="s" uri="/struts-tags" %> <head> + <script src="<s:url value='/js/magalie.js' />"></script> <script> var model = <s:property value="modelAsJson" escapeHtml="false" />; </script> Modified: trunk/magalie-web/src/main/webapp/css/magalie.css =================================================================== --- trunk/magalie-web/src/main/webapp/css/magalie.css 2013-04-24 09:45:30 UTC (rev 122) +++ trunk/magalie-web/src/main/webapp/css/magalie.css 2013-04-24 12:48:43 UTC (rev 123) @@ -30,15 +30,15 @@ left: 180px; } -#storageMovements .success { +#storageMovements .success, #locations .success { background-color: #77ee77; } -#storageMovements .pending { +#storageMovements .pending, #locations .pending { background-color: #ffff99; } -#storageMovements .defect { +#storageMovements .defect, #locations .defect { background-color: #ff9999; } Added: trunk/magalie-web/src/main/webapp/js/magalie.js =================================================================== --- trunk/magalie-web/src/main/webapp/js/magalie.js (rev 0) +++ trunk/magalie-web/src/main/webapp/js/magalie.js 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,24 @@ +function processTemplate(templateId, model) { + var processing = $('#' + templateId).clone().children(); + var elementsWithData = $(processing).find('*[data]'); + for (var i = 0; i < elementsWithData.length; i++) { + var elementWithData = elementsWithData[i]; + var data = $(elementWithData).attr('data'); + // XXX brendan 27/03/13 eval, seriously ? + var text = eval("model." + data); + $(elementWithData).text(text).removeAttr('data'); + } + return processing; +} + +function ValidationError(message) { + this.message = message; +} + +ValidationError.prototype = new Error(); +ValidationError.prototype.constructor = ValidationError; + +function handleValidationError(validationError) { + alert(validationError.message); + console.debug(validationError); +} Added: trunk/magalie-web/src/main/webapp/js/receive-article-input.js =================================================================== --- trunk/magalie-web/src/main/webapp/js/receive-article-input.js (rev 0) +++ trunk/magalie-web/src/main/webapp/js/receive-article-input.js 2013-04-24 12:48:43 UTC (rev 123) @@ -0,0 +1,226 @@ + +model.locationsIndex = 0; +model.allowSubmit = false; + +model.stored = function() { + var stored = 0; + for (var i = 0; i < this.locations.length; i++) { + stored += this.locations[i].stored || 0; + } + return stored; +}; + +model.isFulfilled = function() { + var isFull = this.stored() === this.storedArticle.quantity; + return isFull; +}; + +model.canTerminate = function() { + var canTerminate = this.isFulfilled(); + return canTerminate; +}; + +model.getLocation = function(barcode) { + var location; + for (var i = 0; i < this.locations.length; i++) { + if (barcode == this.locations[i].barcode) { + location = this.locations[i]; + } + } + if (location == null) { + throw new ValidationError(barcode + " n'est pas le code barre d'un emplacement valide"); + } + return location; +}; + +model.store = function(barcode, quantity) { + if ( ! isFinite(quantity) || quantity < 0) { + throw new ValidationError('Il faut entrer une quantité strictement supérieure à 0'); + } + var location = this.getLocation(barcode); + location.used = true; + location.stored = quantity; +}; + +model.nextLocation = function() { + this.locationsIndex += 1; + if (this.locationsIndex >= this.locations.length) { + this.locationsIndex = null; + } +}; + +model.remainingQuantity = function() { + var remainingQuantity = this.storedArticle.quantity - this.stored(); + return remainingQuantity; +} + +model.getConfirmation = function() { + var locationsIdsToActualQuantities = {}; + var locationInErrorIds = []; + $.each(model.storageMovements, function(key, storageMovement) { + if (storageMovement.used) { + locationsIdsToActualQuantities[storageMovement.originLocation.id] = storageMovement.withdrawn; + if (storageMovement.defect) { + locationInErrorIds.push(storageMovement.originLocation.id); + } + } + }); + var confirmation = { + storageMovementOrderId: this.storageMovementOrderId, + locationsIdsToActualQuantities: locationsIdsToActualQuantities, + locationInErrorIds: locationInErrorIds + }; + return confirmation; +} + +var view = { + + refreshSummary : function() { + $('#stored').text(model.stored()); + if (model.stored() === model.storedArticle.quantity) { + $('#stored').addClass('success'); + } else if (model.stored() > model.storedArticle.quantity) { + $('#stored').addClass('warning'); + } + }, + + refreshLocations : function() { + + var locationsElement = $('#locations'); + + // reset the view + locationsElement.children().remove(); + + // add each storage movement in the model + $.each(model.locations, function(key, location) { + var locationElement = processTemplate('locationTemplate', location); + $(locationElement).attr('id', 'location-' + key); + locationsElement.append(locationElement); + if (location.used) { + if (location.defect) { + $(locationElement).addClass('defect'); + } else { + $(locationElement).addClass('success'); + } + } else if (location.id === model.locations[model.locationsIndex].id && ! model.canTerminate()) { + $(locationElement).addClass('pending'); + locationsElement.scrollTop(locationElement[0].offsetTop); + } else { + $(locationElement).hide(); + } + }); + + }, + + refreshForm : function() { + // reset field to prevent reuse of the value by mistake + $('#receive-article_locationBarcode').val(''); + // set default value of quantity to what should be store on the current location + if (model.locationsIndex != null) { + $('#receive-article_quantity').val(model.remainingQuantity()); + } + }, + + refreshButtons : function() { + if (model.allowSubmit) { + $('#receive-article_next').val('Terminer').removeClass('btn-primary').addClass('btn-success'); + } else { + $('#receive-article_next').val('Suivant').removeClass('btn-success').addClass('btn-primary'); + } + }, + + refresh : function() { + this.refreshSummary(); + this.refreshLocations(); + this.refreshForm(); + this.refreshButtons(); + } + +}; + +var controller = { + + onNext : function() { + + try { + + if (model.allowSubmit) { + + } else { + + var barcode = $('#receive-article_locationBarcode').val(); + var quantity = parseFloat($('#receive-article_quantity').val()); + + model.store(barcode, quantity); + + // if used location focused as current, highlight next location to go + if (barcode == model.locations[model.locationsIndex].barcode) { + model.nextLocation(); + } + + } + + } catch (ex) { + + if (ex instanceof ValidationError) { + handleValidationError(ex); + } else { + throw ex; + } + } + }, + + onReportError : function() { + + try { + + if (model.allowSubmit) { + + } else { + + var barcode = $('#receive-article_locationBarcode').val(); + + model.store(barcode, 0.); + + // if used location focused as current, highlight next location to go + if (barcode == model.locations[model.locationsIndex].barcode) { + model.nextLocation(); + } + + } + + } catch (ex) { + + if (ex instanceof ValidationError) { + handleValidationError(ex); + } else { + throw ex; + } + } + }, + + onSubmit : function(e) { + if (model.allowSubmit) { + // push model as Json in a hidden field + $('#receive-article_confirmation').val(JSON.stringify(model.getConfirmation())); + } else { + e.preventDefault(); + + if (model.canTerminate()) { + model.allowSubmit = true; + } + + view.refresh(); + } + } + +}; + +// bindings +$(document).ready(function() { + $('#receive-article').submit(controller.onSubmit); + $('#receive-article_full').click(controller.onReportError); + $('#receive-article_next').click(controller.onNext); + + view.refresh(); +}); Modified: trunk/magalie-web/src/main/webapp/js/withdraw-item-input.js =================================================================== --- trunk/magalie-web/src/main/webapp/js/withdraw-item-input.js 2013-04-24 09:45:30 UTC (rev 122) +++ trunk/magalie-web/src/main/webapp/js/withdraw-item-input.js 2013-04-24 12:48:43 UTC (rev 123) @@ -20,33 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -function processTemplate(templateId, model) { - var processing = $('#' + templateId).clone().children(); - var elementsWithData = $(processing).find('*[data]'); - for (var i = 0; i < elementsWithData.length; i++) { - var elementWithData = elementsWithData[i]; - var data = $(elementWithData).attr('data'); - // XXX brendan 27/03/13 eval, seriously ? - var text = eval("model." + data); - $(elementWithData).text(text).removeAttr('data'); - } - return processing; -} -function ValidationError(message) { - this.message = message; -} - -ValidationError.prototype = new Error(); -ValidationError.prototype.constructor = ValidationError; - -function handleValidationError(validationError) { - alert(validationError.message); - console.debug(validationError); -} - -"use strict"; - model.storageMovementsIndex = 0; model.allowSubmit = false;