Alteração ValidatorInOut.java


#1

Estou com mais um problema…

Estava tentando não fazer uma expedição do produto que não tivesse em estoque.
Verificando a variavel no system configurator, alterei para “Y”. Com isso não era para entregar produto sem estoque, mas se o produto tiver atributo o sistema entrega memso sem estoque, pois ele faz a contagem do produto versos localizador e eu penso que o correto seria produto&localizador%&atributo

exemplo, tenho a quantidade em estoque de 10 envelopes, sendo 5 com atributo branco e 5 com atributo azul, na hora da entrega se eu tentar entregar 7 azuis ou 7 brancos o sistema permite pois leva em conta o produto&localizador.

ai eu tentando resolver esse problema, fiz o sistema verificar tambem pelo atributo no validador de entrada e saida “ValidatorInOut.java”, apos a alteracao testando o sistema ele fez tudo como esperado levou em consideração o atributo, mas a nivel de codigo não sei se fiz correto, pelos padroes do AD e queria ajuda de voces.

como nao eh permitido anexo vou colar o codigo e as alterações estao abaixo dos comentarios
/**

  • FIXME: M_AttributeSetInstance_ID,
  • com isso verifico quantidade de propduto por instância
  • @author Edilson Duarte, Grupo LCR
  • @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
    /
    —>>.
    /
    *****************************************************************************
  • Product: ADempiereLBR - ADempiere Localization Brazil *
  • This program is free software; you can redistribute it and/or modify it *
  • under the terms version 2 of the GNU General Public License as published *
  • by the Free Software Foundation. This program is distributed in the hope *
  • that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
  • warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
  • See the GNU General Public License for more details. *
  • You should have received a copy of the GNU General Public License along *
  • with this program; if not, write to the Free Software Foundation, Inc., *
  • 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
    *****************************************************************************/
    package org.adempierelbr.validator;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;

import org.compiere.apps.search.Info_Column;
import org.compiere.model.MClient;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInventory;
import org.compiere.model.MInventoryLine;
import org.compiere.model.MMovement;
import org.compiere.model.MMovementLine;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProduct;
import org.compiere.model.MSysConfig;
import org.compiere.model.ModelValidationEngine;
import org.compiere.model.ModelValidator;
import org.compiere.model.PO;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;

/**

  • ValidatorInOut, inclui as validações de outras tabelas que

  • manipulam materiais, como Movimentações, Inventário e Entrada/Saída.

  • @author Ricardo Santana (ralexsander)

  • @version $Id: ValidatorInOut.java, 04/01/2008 15:56:00 ralexsander
    /
    public class ValidatorInOut implements ModelValidator
    {
    /
    *

    • Constructor.
    • The class is instanciated when logging in and client is selected/known
      */
      public ValidatorInOut ()
      {
      super ();
      } //ValidatorInOut

    /** Logger /
    private static CLogger log = CLogger.getCLogger(ValidatorInOut.class);
    /
    * Client */
    private int m_AD_Client_ID = -1;

    /**

    • Initialize Validation

    • @param engine validation engine

    • @param client client
      */
      public void initialize (ModelValidationEngine engine, MClient client)
      {
      m_AD_Client_ID = client.getAD_Client_ID();

      log.info(client.toString());

      // DocValidate
      engine.addDocValidate(“M_InOut”, this);
      engine.addDocValidate(“M_Movement”, this);
      engine.addDocValidate(“M_Inventory”, this);

    } // initialize

    /**

    • Get Client to be monitored
    • @return AD_Client_ID client
      */
      public int getAD_Client_ID()
      {
      return m_AD_Client_ID;
      } // getAD_Client_ID

    /**

    • User Login.
    • Called when preferences are set
    • @param AD_Org_ID org
    • @param AD_Role_ID role
    • @param AD_User_ID user
    • @return error message or null
      */
      public String login (int AD_Org_ID, int AD_Role_ID, int AD_User_ID)
      {
      log.info(“AD_User_ID=” + AD_User_ID);
      return null;
      } // login

    /**

    • Model Change of a monitored Table.
    • Called after PO.beforeSave/PO.beforeDelete
    • when you called addModelChange for the table
    • @param po persistent object
    • @param type TYPE_
    • @return error message or null
    • @exception Exception if the recipient wishes the change to be not accept.
      */
      public String modelChange (PO po, int type) throws Exception
      {
      return null;
      } // modelChange

    /**

    • Validate Document.

    • Called as first step of DocAction.prepareIt

    • when you called addDocValidate for the table.

    • Note that totals, etc. may not be correct.

    • @param po persistent object

    • @param timing see TIMING_ constants

    • @return error message or null
      */
      public String docValidate (PO po, int timing)
      {
      if (po.get_TableName().equalsIgnoreCase(“M_InOut”))
      return docValidate((MInOut) po, timing);

      else if (po.get_TableName().equalsIgnoreCase(“M_Movement”))
      return docValidate((MMovement) po, timing);

      else if (po.get_TableName().equalsIgnoreCase(“M_Inventory”))
      return docValidate((MInventory) po, timing);

      return null;
      } // docValidate

    /**

    • Quantity On Hand.

    • @param M_Product_ID

    • @param M_Locator_ID

    • @param M_AttributeSetInstance_ID

    • @return BigDecimal quantity of product
      */
      private BigDecimal getQtyOnHand(int M_Product_ID, int M_Locator_ID, int M_AttributeSetInstance_ID)
      // private BigDecimal getQtyOnHand(int M_Product_ID, int M_Locator_ID)
      {
      BigDecimal QtyOnHand = Env.ZERO;
      String sql = “SELECT SUM(QtyOnHand) FROM M_Storage "
      + “WHERE M_Product_ID=?”; // 1
      if(M_Locator_ID > 0)
      sql = sql + " AND M_Locator_ID=?”; // 2

      /**

      • FIXME: M_AttributeSetInstance_ID,
      • com isso verifico quantidade de propduto por instância
      • @author Edilson Duarte, Grupo LCR
      • @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
        */

      if(M_AttributeSetInstance_ID > 0)
      sql = sql + " AND M_AttributeSetInstance_ID=?"; // 3
      /***/

      PreparedStatement pstmt = null;
      ResultSet rs = null;
      try
      {
      pstmt = DB.prepareStatement(sql, null);
      pstmt.setInt(1, M_Product_ID);
      if(M_Locator_ID > 0)
      pstmt.setInt(2, M_Locator_ID);

       /**
       *  FIXME: M_AttributeSetInstance_ID,
       *  com isso verifico quantidade de propduto por instância
       *   @author Edilson Duarte, Grupo LCR
       *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
       */
      
      if (M_AttributeSetInstance_ID != 0)
      	pstmt.setInt (3, M_AttributeSetInstance_ID);
      /***/
      
      rs = pstmt.executeQuery();
      if (rs.next())
      {
      	QtyOnHand = rs.getBigDecimal(1);
      }
      

      }
      catch (SQLException e)
      {
      log.log(Level.SEVERE, sql, e);
      return Env.ZERO;
      }
      finally{
      DB.close(rs, pstmt);
      }

      if(QtyOnHand != null)
      return QtyOnHand;

      return Env.ZERO;
      } // QtyOnHand

    /**

    • Validate Movement.

    • @param MMovement movement

    • @param timing see TIMING_ constants

    • @return error message or null
      */
      private String docValidate(MMovement mov, int timing)
      {
      Properties ctx = mov.getCtx();

      if (timing == TIMING_BEFORE_COMPLETE)
      {
      MMovementLine[] lines = mov.getLines(true);
      ArrayList prod = new ArrayList();

      if(lines == null
      		|| lines.length <= 0)
      	return Msg.getMsg(ctx, "NoLines");
      
      for(MMovementLine line : lines)
      {
      	if(line.getM_Product_ID() <=0 
      			|| line.getM_Locator_ID() <=0)
      		return "Produto ou Localizador inválido";
      	
      	
      	 /**
      	 *  FIXME: M_AttributeSetInstance_ID,
      	 *  com isso verifico quantidade de propduto por instância
      	 *   @author Edilson Duarte, Grupo LCR
           *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
      	 */
      
      	if (line.getM_AttributeSetInstance_ID() != 0)
      	{
      	  if(((line.getM_Product_ID() > 0) && (line.getM_Locator_ID() > 0))
      		  && (line.getM_AttributeSetInstance_ID() <=0))
      		    
      		return "Atributo Necessário";
      	}
      	/***/
      	
      					
      	if(line.getMovementQty().equals(Env.ZERO))
      		return "Itens com qtd zero";
      	
      	if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
      		return "Duas linhas usando o mesmo produto na mesma posição";
      	else
      		prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
      
      	/**
      	 *  FIXME: M_AttributeSetInstance_ID,
      	 *  com isso verifico quantidade de propduto por instância
      	 *   @author Edilson Duarte, Grupo LCR
           *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
      	 */
      

    // BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID());
    BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(), line.getM_AttributeSetInstance_ID() );
    /***/

    		if(qtdOnHand.compareTo(line.getMovementQty()) == -1)
    			return "Sem saldo na linha=" + line.getLine();
    	}
    }
    
    return null;
    

    }

    /**

    • Validate Inventory.

    • @param MInventory inventory

    • @param timing see TIMING_ constants

    • @return error message or null
      */
      private String docValidate(MInventory inv, int timing)
      {
      Properties ctx = inv.getCtx();

      if (timing == TIMING_AFTER_COMPLETE)
      {
      MInventoryLine[] lines = inv.getLines(true);
      ArrayList prod = new ArrayList();

      if(lines == null
      		|| lines.length <= 0)
      	return Msg.getMsg(ctx, "NoLines");
      
      Boolean isInternalUse = (Boolean) inv.get_Value("z_IsInternalUse");
      
      if(isInternalUse != null && isInternalUse)
      {
      	for(MInventoryLine line : lines)
      	{
      		if(line.getM_Product_ID() <=0 
      				|| line.getM_Locator_ID() <=0)
      			return "Produto ou Localizador inválido";
      		
      		if(line.getMovementQty().equals(Env.ZERO))
      			return "Itens com qtd zero";
      		
      		if(line.getC_Charge_ID() == 0)
      			return "Sem conta de destino";
      		
      		if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
      			return "Duas linhas usando o mesmo produto na mesma posição";
      		else
      			prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
      		 /**
      		 *  FIXME: M_AttributeSetInstance_ID,
      		 *  com isso verifico quantidade de propduto por instância
      		 *   @author Edilson Duarte, Grupo LCR
               *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
      		 */
      

// BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID());
BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(),line.getM_AttributeSetInstance_ID());
/***/

				if(qtdOnHand.compareTo(line.getQtyInternalUse()) == -1)
					return "Sem saldo na linha=" + line.getLine();
			}
		}
	}
	
	return null;
}

/**
 *	Validate Shipment/Receipt.
 *  
 *	@param MInOut inventory
 *	@param timing see TIMING_ constants
 *	@return error message or null
 */
private String docValidate(MInOut inOut, int timing)
{
	Properties ctx = inOut.getCtx();
	String     trx = inOut.get_TrxName();
	
	if (timing == TIMING_AFTER_COMPLETE)
	{
		String sql = "SELECT C_DocType_ID FROM C_DocType " +
				"WHERE lbr_IsManufactured='Y' AND C_DocType_ID=?";
		
		/** 
		 * Verifica se é industrialização
		 * */
		if(DB.getSQLValue(trx, sql, inOut.getC_DocType_ID()) < 0)
			return null;
		
		MInOutLine[] lines = inOut.getLines();
		
		for(MInOutLine line : lines)
		{
			int C_OrderLine_ID = line.getC_OrderLine_ID();
			
			if(C_OrderLine_ID > 0)
			{
				MOrderLine oLine = new MOrderLine(ctx, C_OrderLine_ID, trx);
				Integer ii = (Integer) oLine.get_Value("M_ProductionLine_ID");
				int M_ProductionLine_ID = 0;
				
				if(ii != null)
					M_ProductionLine_ID = ii.intValue();
				else
					continue;
				
				/**	
				 * Atualiza a quantidade entregue de industrialização
				 * */
				
				DB.executeUpdate("UPDATE M_ProductionLine " +
						"SET QtyDelivered=COALESCE(QtyDelivered,0)+ " +
						"(SELECT QtyEntered FROM M_InOutLine " +
						"WHERE M_InOutLine_ID=" + line.getM_InOutLine_ID() + ") " +
						"WHERE M_ProductionLine_ID=" + M_ProductionLine_ID, trx);
			}
		}	
	}
	else if ((timing == TIMING_BEFORE_COMPLETE || timing == TIMING_BEFORE_REVERSECORRECT))
	{
		MInOutLine[] lines = inOut.getLines();
		ArrayList<Integer> olines = new ArrayList<Integer>();
		
		if (lines.length == 0)
			return "Documento sem linhas";

		for (int i = 0; i < lines.length; i++)
		{
			MInOutLine line = lines[i];
			int M_Product_ID = line.getM_Product_ID();
			int M_Locator_ID = line.getM_Locator_ID();
			 /**
			 *  FIXME: M_AttributeSetInstance_ID,
			 *  com isso verifico quantidade de propduto por instância
			 *   @author Edilson Duarte, Grupo LCR
	         *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
			 */

			int M_AttributeSetInstance_ID = line.getM_AttributeSetInstance_ID();
            
			/***/
			
			
			BigDecimal onHand = Env.ZERO, qtyToShip = Env.ZERO;
			MProduct produto = MProduct.get(ctx, M_Product_ID);
			
			if (!produto.isStocked())
				continue;
			
			if (M_Locator_ID == 0)
				return "Localizador do estoque não definida na linha: #" + line.getLine() + ".";

			if (line.getQtyEntered() == Env.ZERO)
				return "Item com quantidade ZERO na linha: #" + line.getLine() + ".";
			
			if (!MSysConfig.getBooleanValue("LBR_ALLOW_MM_SHIP_RECEIPT_WITHOUT_ORDER", true, inOut.getAD_Client_ID())
					&& line.getC_OrderLine_ID() == 0)
				return "Ordem de Compra não disponível.";
			
			MOrderLine oline = new MOrderLine(ctx, line.getC_OrderLine_ID(), trx);
			
			if (timing == TIMING_BEFORE_REVERSECORRECT 
					&& !MSysConfig.getBooleanValue("LBR_ALLOW_REVERSE_SHIP_RECEIT_WITH_OPEN_INVOICE", true, inOut.getAD_Client_ID())
					&& DB.getSQLValue(null, "SELECT COUNT(*) FROM C_InvoiceLine il, C_Invoice i WHERE i.C_Invoice_ID=il.C_Invoice_ID AND i.DocStatus IN ('CO','CL') AND il.M_InOutLine_ID=?", line.getM_InOutLine_ID()) > 0)
				return "Fatura(s) em aberto. Impossível continuar com o estorno.";
			
			int C_OrderLine_ID = line.getC_OrderLine_ID();
			if (C_OrderLine_ID != 0)
			{
				if(!MSysConfig.getBooleanValue("LBR_ALLOW_DUPLICATED_ORDERLINE_ON_SHIP_RECEIPT", true, inOut.getAD_Client_ID())
						&& olines.contains("" + line.getC_OrderLine_ID()))
					return "Linha #" + line.getLine() + " duplicada.";
				else
					olines.add(line.getC_OrderLine_ID());
			}
			
			/**
			 *  FIXME: QtyDelivered é na UDM padrão, QtyEntered pode ser outra,
			 *  com isso a comparação, pode não funcionar corretamente.
			 *  
			 */
			
			log.info("Delivered: " + oline.getQtyDelivered() + " Entered: " + oline.getQtyEntered() + " Trying: " + line.getQtyEntered());
			if (timing == TIMING_BEFORE_COMPLETE
					&& MSysConfig.getBooleanValue("LBR_MATCH_SHIPMENT_RECEIPT_AND_ORDER_QTY", false, inOut.getAD_Client_ID())
					&& oline.getQtyDelivered().add(line.getQtyEntered()).doubleValue() > oline.getQtyEntered().doubleValue())
				return "Nao e possivel fazer recebimento maior que o pedido. Linha do pedido #" + line.getLine();
			
			if (M_Product_ID == 0)
				continue;
			 /**
			 *  FIXME: M_AttributeSetInstance_ID,
			 *  com isso verifico quantidade de propduto por instância
			 *   @author Edilson Duarte, Grupo LCR
	         *   @version $Id: ValidatorInOut.java,v 1.7 2009/01/12 05:18:39 edilsoneto Exp $
			 */

// onHand = getQtyOnHand(M_Product_ID, M_Locator_ID); //QtyOnHand
onHand = getQtyOnHand(M_Product_ID, M_Locator_ID, M_AttributeSetInstance_ID); //QtyOnHand
/**/

			qtyToShip = Env.ZERO;
			
			for (int j = 0; j < lines.length; j++)
			{
				if(lines[j].getM_Product_ID() == line.getM_Product_ID()
						&& lines[j].getM_Locator_ID() == line.getM_Locator_ID())
				{
					qtyToShip = qtyToShip.add(lines[j].getQtyEntered());
				}
			}
			
			if (timing == TIMING_BEFORE_COMPLETE
					&& !MSysConfig.getBooleanValue("LBR_ALLOW_NEGATIVE_STOCK", true, inOut.getAD_Client_ID())){
								
				String movementType = inOut.getMovementType();
				
				if (movementType.charAt(1) == '-')
				{
					if (timing == TIMING_BEFORE_COMPLETE
							&& onHand.subtract(qtyToShip).doubleValue() < 0)
						return "Sem quantidade disponivel na linha #" + line.getLine() + ".";
				}
				else 
				{
					if (onHand.add(line.getQtyEntered()).doubleValue() < 0)
						return "Sem quantidade disponível na linha #" + line.getLine() + ".";
				}
			
			}
			
		} // for;
	}
	
	return null;
}
	
/**
 * 	Update Info Window Columns.
 * 	- add new Columns
 * 	- remove columns
 * 	- change dispay sequence
 *	@param columns array of columns
 *	@param sqlFrom from clause, can be modified
 *	@param sqlOrder order by clause, can me modified
 *	@return true if you updated columns, sequence or sql From clause
 */
public boolean updateInfoColumns (ArrayList<Info_Column> columns, 
	StringBuffer sqlFrom, StringBuffer sqlOrder)
{
	/**		*
	int AD_Role_ID = Env.getAD_Role_ID (Env.getCtx());	// Can be Role/User specific 
	String from = sqlFrom.toString();
	if (from.startsWith ("M_Product"))
	{
		columns.add (new Info_Column("Header", "'sql'", String.class).seq(35));
		return true;
	}/**	*/
	return false;
}	//	updateInfoColumns

/**
 * 	String Representation
 *	@return info
 */
public String toString ()
{
	StringBuffer sb = new StringBuffer ("AdempiereLBR - Powered by Kenos & Faire");
	return sb.toString ();
}	//	toString

} //ValidatorInOut


#2

Dê uma olhada nessa versão do código…

Um abraço,
Eduardo.


/******************************************************************************
 * Product: ADempiereLBR - ADempiere Localization Brazil                      *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program is distributed in the hope   *
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 *****************************************************************************/
package org.adempierelbr.validator;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;

import org.compiere.apps.search.Info_Column;
import org.compiere.model.MClient;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInventory;
import org.compiere.model.MInventoryLine;
import org.compiere.model.MMovement;
import org.compiere.model.MMovementLine;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProduct;
import org.compiere.model.MSysConfig;
import org.compiere.model.ModelValidationEngine;
import org.compiere.model.ModelValidator;
import org.compiere.model.PO;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;

/**
 *	ValidatorInOut, inclui as validações de outras tabelas que 
 *	manipulam materiais, como Movimentações, Inventário e Entrada/Saída.
 *  	
 *	@author Ricardo Santana (ralexsander)
 *	@version $Id: ValidatorInOut.java, 04/01/2008 15:56:00 ralexsander
 */
public class ValidatorInOut implements ModelValidator
{
	/**
	 *	Constructor.
	 *	The class is instanciated when logging in and client is selected/known
	 */
	public ValidatorInOut ()
	{
		super ();
	}	//ValidatorInOut
	
	/**	Logger			*/
	private static CLogger log = CLogger.getCLogger(ValidatorInOut.class);
	/** Client			*/
	private int		m_AD_Client_ID = -1;
	
	
	/**
	 *	Initialize Validation
	 *	@param engine validation engine 
	 *	@param client client
	 */
	public void initialize (ModelValidationEngine engine, MClient client)
	{
		//client = null for global validator
        if (client != null) {
            m_AD_Client_ID = client.getAD_Client_ID();
            log.info(client.toString());
        }
        else  {
            log.info("Initializing global validator: "+this.toString());
        }

		//	DocValidate
		engine.addDocValidate("M_InOut", this);
		engine.addDocValidate("M_Movement", this);
		engine.addDocValidate("M_Inventory", this);

	}	//	initialize

	/**
	 *	Get Client to be monitored
	 *	@return AD_Client_ID client
	 */
	public int getAD_Client_ID()
	{
		return m_AD_Client_ID;
	}	//	getAD_Client_ID

	/**
	 *	User Login.
	 *	Called when preferences are set
	 *	@param AD_Org_ID org
	 *	@param AD_Role_ID role
	 *	@param AD_User_ID user
	 *	@return error message or null
	 */
	public String login (int AD_Org_ID, int AD_Role_ID, int AD_User_ID)
	{
		log.info("AD_User_ID=" + AD_User_ID);
		return null;
	}	//	login
	
    /**
     *	Model Change of a monitored Table.
     *	Called after PO.beforeSave/PO.beforeDelete
     *	when you called addModelChange for the table
     *	@param po persistent object
     *	@param type TYPE_
     *	@return error message or null
     *	@exception Exception if the recipient wishes the change to be not accept.
     */
	public String modelChange (PO po, int type) throws Exception
	{
		return null;
	}	//	modelChange

	/**
	 *	Validate Document.
	 *	Called as first step of DocAction.prepareIt 
     *	when you called addDocValidate for the table.
     *	Note that totals, etc. may not be correct.
	 *	@param po persistent object
	 *	@param timing see TIMING_ constants
     *	@return error message or null
	 */
	public String docValidate (PO po, int timing)
	{
		if (po.get_TableName().equalsIgnoreCase("M_InOut"))
			return docValidate((MInOut) po, timing);
		
		else if (po.get_TableName().equalsIgnoreCase("M_Movement"))
			return docValidate((MMovement) po, timing);
		
		else if (po.get_TableName().equalsIgnoreCase("M_Inventory"))
			return docValidate((MInventory) po, timing);

		
		return null;
	}	//	docValidate
	
	/**
	 *	Quantity On Hand.
	 *	
	 *	@param M_Product_ID
	 *	@param M_Locator_ID
	 *	@param M_AttributeSetInstance_ID
     *	@return BigDecimal quantity of product
	 */
	private BigDecimal getQtyOnHand(int M_Product_ID, int M_Locator_ID, int M_AttributeSetInstance_ID)
	{
		BigDecimal QtyOnHand = Env.ZERO;
		String sql = "SELECT SUM(QtyOnHand) FROM M_Storage "
			+ "WHERE M_Product_ID=?";	//	1
		if(M_Locator_ID > 0)
			sql = sql + " AND M_Locator_ID=?";	//	2
		if(M_AttributeSetInstance_ID >0)
			sql = sql + " AND M_AttributeSetInstance_ID=?";	//	3
		
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try
		{
			int i=1;
			pstmt = DB.prepareStatement(sql, null);
			pstmt.setInt(i++, M_Product_ID);
			if(M_Locator_ID > 0)
				pstmt.setInt(i++, M_Locator_ID);
			if(M_AttributeSetInstance_ID > 0)
				pstmt.setInt(i++, M_AttributeSetInstance_ID);
			
			rs = pstmt.executeQuery();
			if (rs.next())
			{
				QtyOnHand = rs.getBigDecimal(1);
			}
		}
		catch (SQLException e)
		{
			log.log(Level.SEVERE, sql, e);
			return Env.ZERO;
		}
		finally{
		       DB.close(rs, pstmt);
		}
		
		if(QtyOnHand != null)
			return QtyOnHand;
		
		return Env.ZERO;
	}	//	QtyOnHand
	
	/**
	 *	Validate Movement.
	 *  
	 *	@param MMovement movement
	 *	@param timing see TIMING_ constants
     *	@return error message or null
	 */
	private String docValidate(MMovement mov, int timing)
	{
		Properties ctx = mov.getCtx();
		
		if (timing == TIMING_BEFORE_COMPLETE)
		{
			MMovementLine[] lines = mov.getLines(true);
			ArrayList<String> prod = new ArrayList<String>();
			
			if(lines == null
					|| lines.length <= 0)
				return Msg.getMsg(ctx, "NoLines");
			
			for(MMovementLine line : lines)
			{
				if(line.getM_Product_ID() <=0 
						|| line.getM_Locator_ID() <=0)
					return "Producto o Ubicación inválidos";
				
				if(line.getMovementQty().equals(Env.ZERO))
					return "Artículos con cantidad cero";
				
				if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
					return "Existen dos o más líneas repetidas";
				else
					prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
				
				BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(), line.getM_AttributeSetInstance_ID());
				
				if(qtdOnHand.compareTo(line.getMovementQty()) == -1)
					return "No hay stock del artículo de la línea=" + line.getLine();
			}
		}
		
		return null;
	}
	
	/**
	 *	Validate Inventory.
	 *  
	 *	@param MInventory inventory
	 *	@param timing see TIMING_ constants
     *	@return error message or null
	 */
	private String docValidate(MInventory inv, int timing)
	{
		Properties ctx = inv.getCtx();
		
		if (timing == TIMING_AFTER_COMPLETE)
		{
			MInventoryLine[] lines = inv.getLines(true);
			ArrayList<String> prod = new ArrayList<String>();
			
			if(lines == null
					|| lines.length <= 0)
				return Msg.getMsg(ctx, "NoLines");
			
			Boolean isInternalUse = (Boolean) inv.get_Value("z_IsInternalUse");
			
			if(isInternalUse != null && isInternalUse)
			{
				for(MInventoryLine line : lines)
				{
					if(line.getM_Product_ID() <=0 
							|| line.getM_Locator_ID() <=0)
						return "Producto o Ubicación invalidos";
					
					if(line.getMovementQty().equals(Env.ZERO))
						return "Artículos con cantidad zero";
					
					if(line.getC_Charge_ID() == 0)
						return "Sin cuenta para cargar costos";
					
					if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
						return "Existen dos o más líneas repetidas";
					else
						prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
					
					BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(), line.getM_AttributeSetInstance_ID());
					
					if(qtdOnHand.compareTo(line.getQtyInternalUse()) == -1)
						return "No hay stock del artículo de la línea=" + line.getLine();
				}
			}
		}
		
		return null;
	}
	
	/**
	 *	Validate Shipment/Receipt.
	 *  
	 *	@param MInOut inventory
	 *	@param timing see TIMING_ constants
     *	@return error message or null
	 */
	private String docValidate(MInOut inOut, int timing)
	{
		Properties ctx = inOut.getCtx();
		String     trx = inOut.get_TrxName();
		
		if (timing == TIMING_AFTER_COMPLETE)
		{
			String sql = "SELECT C_DocType_ID FROM C_DocType " +
					"WHERE lbr_IsManufactured='Y' AND C_DocType_ID=?";
			
			/** 
			 * Verifica se é industrialização
			 * */
			if(DB.getSQLValue(trx, sql, inOut.getC_DocType_ID()) < 0)
				return null;
			
			MInOutLine[] lines = inOut.getLines();
			
			for(MInOutLine line : lines)
			{
				int C_OrderLine_ID = line.getC_OrderLine_ID();
				
				if(C_OrderLine_ID > 0)
				{
					MOrderLine oLine = new MOrderLine(ctx, C_OrderLine_ID, trx);
					Integer ii = (Integer) oLine.get_Value("M_ProductionLine_ID");
					int M_ProductionLine_ID = 0;
					
					if(ii != null)
						M_ProductionLine_ID = ii.intValue();
					else
						continue;
					
					/**	
					 * Atualiza a quantidade entregue de industrialização
					 * */
					
					DB.executeUpdate("UPDATE M_ProductionLine " +
							"SET QtyDelivered=COALESCE(QtyDelivered,0)+ " +
							"(SELECT QtyEntered FROM M_InOutLine " +
							"WHERE M_InOutLine_ID=" + line.getM_InOutLine_ID() + ") " +
							"WHERE M_ProductionLine_ID=" + M_ProductionLine_ID, trx);
				}
			}	
		}
		else if ((timing == TIMING_BEFORE_COMPLETE || timing == TIMING_BEFORE_REVERSECORRECT))
		{
			MInOutLine[] lines = inOut.getLines();
			ArrayList<Integer> olines = new ArrayList<Integer>();
			
			if (lines.length == 0)
				return "El Documento no contiene ninguna línea";
	
			for (int i = 0; i < lines.length; i++)
			{
				MInOutLine line = lines[i];
				int M_Product_ID = line.getM_Product_ID();
				int M_Locator_ID = line.getM_Locator_ID();
				int M_AttributeSetInstance_ID = line.getM_AttributeSetInstance_ID();
				BigDecimal onHand = Env.ZERO, qtyToShip = Env.ZERO;
				MProduct produto = MProduct.get(ctx, M_Product_ID);
				
				if (!produto.isStocked())
					continue;
				
				if (M_Locator_ID == 0)
					return "Ubicación no especificada en la línea: #" + line.getLine() + ".";
	
				if (line.getQtyEntered() == Env.ZERO)
					return "Articulo con cantidad CERO en la línea: #" + line.getLine() + ".";
				
				if (!MSysConfig.getBooleanValue("LBR_ALLOW_MM_SHIP_RECEIPT_WITHOUT_ORDER", true, inOut.getAD_Client_ID())
						&& line.getC_OrderLine_ID() == 0)
					return "Orden de Compra no disponible";

				if (!MSysConfig.getBooleanValue("LBR_ALLOW_MM_SHIP_RECEIPT_WITHOUT_INVOICE", true, inOut.getAD_Client_ID())
						&& line.isInvoiced())
					return "Factura no disponible";
				
				MOrderLine oline = new MOrderLine(ctx, line.getC_OrderLine_ID(), trx);
				
				if (timing == TIMING_BEFORE_REVERSECORRECT 
						&& !MSysConfig.getBooleanValue("LBR_ALLOW_REVERSE_SHIP_RECEIT_WITH_OPEN_INVOICE", true, inOut.getAD_Client_ID())
						&& oline.getQtyDelivered().subtract(oline.getQtyInvoiced()).doubleValue() == 0)
					return "Facturas pendientes. No es posible revertir";
				
				int C_OrderLine_ID = line.getC_OrderLine_ID();
				if (C_OrderLine_ID != 0)
				{
					if(!MSysConfig.getBooleanValue("LBR_ALLOW_DUPLICATED_ORDERLINE_ON_SHIP_RECEIPT", true, inOut.getAD_Client_ID())
							&& olines.contains("" + line.getC_OrderLine_ID()))
						return "Línea #" + line.getLine() + " duplicada.";
					else
						olines.add(line.getC_OrderLine_ID());
				}
				
				/**
				 *  FIXME: QtyDelivered é na UDM padrão, QtyEntered pode ser outra,
				 *  com isso a comparação, pode não funcionar corretamente.
				 *  
				 */
				
				log.info("Delivered: " + oline.getQtyDelivered() + " Entered: " + oline.getQtyEntered() + " Trying: " + line.getQtyEntered());
				if (timing == TIMING_BEFORE_COMPLETE
						&& MSysConfig.getBooleanValue("LBR_MATCH_SHIPMENT_RECEIPT_AND_ORDER_QTY", false, inOut.getAD_Client_ID())
						&& oline.getQtyDelivered().add(line.getQtyEntered()).doubleValue() > oline.getQtyEntered().doubleValue())
					return "La Cantidad recibida es mayor que la cantidad de la Orden de Compra. Linea de la Orden #" + line.getLine();
				
				onHand = getQtyOnHand(M_Product_ID, M_Locator_ID, M_AttributeSetInstance_ID); //QtyOnHand
				
				qtyToShip = Env.ZERO;
				
				for (int j = 0; j < lines.length; j++)
				{
					if(lines[j].getM_Product_ID() == line.getM_Product_ID()
							&& lines[j].getM_Locator_ID() == line.getM_Locator_ID())
					{
						qtyToShip = qtyToShip.add(lines[j].getQtyEntered());
					}
				}
				
				if (timing == TIMING_BEFORE_COMPLETE){
									
					//Checks if negative stock is allowed
					boolean negativeStockAllowed = true;
					if(!MSysConfig.getBooleanValue("LBR_ALLOW_NEGATIVE_STOCK", true, inOut.getAD_Client_ID())){
						//Before returning, check if the product can be shipped without qty
						String allow = line.getProduct().get_ValueAsString("LBR_AllowSaleWithoutStock");
						if(allow == null || allow.equals("false")){ // era &&
							negativeStockAllowed = false;
							//return null;
						}		
					}
					String movementType = inOut.getMovementType();
					
					if (movementType.charAt(1) == '-')
					{
						if (timing == TIMING_BEFORE_COMPLETE
								&& (onHand.subtract(qtyToShip).doubleValue() < 0 && !negativeStockAllowed))
							return "No hay stock del artículo de la línea #" + line.getLine() + ".";
					}
					else 
					{
						if (onHand.add(line.getQtyEntered()).doubleValue() < 0 && !negativeStockAllowed)
							return "No hay stock del artículo de la línea #" + line.getLine() + ".";
					}
				
				}
				
			} // for;
		}
		
		return null;
	}
		
	/**
	 * 	Update Info Window Columns.
	 * 	- add new Columns
	 * 	- remove columns
	 * 	- change dispay sequence
	 *	@param columns array of columns
	 *	@param sqlFrom from clause, can be modified
	 *	@param sqlOrder order by clause, can me modified
	 *	@return true if you updated columns, sequence or sql From clause
	 */
	public boolean updateInfoColumns (ArrayList<Info_Column> columns, 
		StringBuffer sqlFrom, StringBuffer sqlOrder)
	{
		/**		*
		int AD_Role_ID = Env.getAD_Role_ID (Env.getCtx());	// Can be Role/User specific 
		String from = sqlFrom.toString();
		if (from.startsWith ("M_Product"))
		{
			columns.add (new Info_Column("Header", "'sql'", String.class).seq(35));
			return true;
		}/**	*/
		return false;
	}	//	updateInfoColumns
	
	/**
	 * 	String Representation
	 *	@return info
	 */
	public String toString ()
	{
		StringBuffer sb = new StringBuffer ("AdempiereLBR - Powered by Kenos & Faire");
		return sb.toString ();
	}	//	toString

} //ValidatorInOut

#3

Obrigado Eduardo,
eh isso mesmo, mas teria como fazer o upload para Trunk.
Achei outro erro e estou tentendo resolver. No ADEMPIERE mesmo com esse validator ao completar um recebimento ou envio com um produto com mesmo localizador em duas linhas o sistema nao faz nenhuma critica, você ja testou isso? Estava olhando que tem uma restrição nesse validator mas não fuciona…


#4

Edilson,

Você chegou a fazer os testes habilitando as validações no System Configurator?

Ricardo Santana


#5

Ricardo, ja verifiquei todas as variaveis aqui no nesse validator. Ele só controla o mesmo produto em duas linhas diferentes na movimentação e no inventario “estoque fisico”. No envio e no recebimento não faz esse controle, mas ja resolvi criei uma variavel System Configurator e codifique o validator para o controlar o pedido, apenas estou fazendo uns testes e ate o final da semana faço o post aqui.

Obrigado desde entao…


#6

Alterei novamente essa “classe” e fiz alguns testes e deu certo!

Criei tambem uma variavel no system configuration LBR_ALLOW_TWOLINE_INOUT, para não deixar receber ou entregar um mesmo produtudo em duas linhas diferentes. aqui o codigo

[code]/******************************************************************************

  • Product: ADempiereLBR - ADempiere Localization Brazil *
  • This program is free software; you can redistribute it and/or modify it *
  • under the terms version 2 of the GNU General Public License as published *
  • by the Free Software Foundation. This program is distributed in the hope *
  • that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
  • warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
  • See the GNU General Public License for more details. *
  • You should have received a copy of the GNU General Public License along *
  • with this program; if not, write to the Free Software Foundation, Inc., *
  • 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
    *****************************************************************************/
    package org.adempierelbr.validator;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;

import org.compiere.apps.search.Info_Column;
import org.compiere.model.MClient;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInventory;
import org.compiere.model.MInventoryLine;
import org.compiere.model.MMovement;
import org.compiere.model.MMovementLine;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProduct;
import org.compiere.model.MSysConfig;
import org.compiere.model.ModelValidationEngine;
import org.compiere.model.ModelValidator;
import org.compiere.model.PO;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;

/**

  • ValidatorInOut, inclui as validações de outras tabelas que

  • manipulam materiais, como Movimentações, Inventário e Entrada/Saída.

  • @author Ricardo Santana (ralexsander)

  • @contributor Edilson Duarte (Grupo LCR)

  • @version $Id: ValidatorInOut.java, 14/12/2009 10:00:00 edilsondneto
    /
    public class ValidatorInOut implements ModelValidator
    {
    /
    *

    • Constructor.
    • The class is instanciated when logging in and client is selected/known
      */
      public ValidatorInOut ()
      {
      super ();
      } //ValidatorInOut

    /** Logger /
    private static CLogger log = CLogger.getCLogger(ValidatorInOut.class);
    /
    * Client */
    private int m_AD_Client_ID = -1;

    /**

    • Initialize Validation

    • @param engine validation engine

    • @param client client
      */
      public void initialize (ModelValidationEngine engine, MClient client)
      {
      m_AD_Client_ID = client.getAD_Client_ID();

      log.info(client.toString());

      // DocValidate
      engine.addDocValidate(“M_InOut”, this);
      engine.addDocValidate(“M_Movement”, this);
      engine.addDocValidate(“M_Inventory”, this);

    } // initialize

    /**

    • Get Client to be monitored
    • @return AD_Client_ID client
      */
      public int getAD_Client_ID()
      {
      return m_AD_Client_ID;
      } // getAD_Client_ID

    /**

    • User Login.
    • Called when preferences are set
    • @param AD_Org_ID org
    • @param AD_Role_ID role
    • @param AD_User_ID user
    • @return error message or null
      */
      public String login (int AD_Org_ID, int AD_Role_ID, int AD_User_ID)
      {
      log.info(“AD_User_ID=” + AD_User_ID);
      return null;
      } // login

    /**

    • Model Change of a monitored Table.
    • Called after PO.beforeSave/PO.beforeDelete
    • when you called addModelChange for the table
    • @param po persistent object
    • @param type TYPE_
    • @return error message or null
    • @exception Exception if the recipient wishes the change to be not accept.
      */
      public String modelChange (PO po, int type) throws Exception
      {
      return null;
      } // modelChange

    /**

    • Validate Document.

    • Called as first step of DocAction.prepareIt

    • when you called addDocValidate for the table.

    • Note that totals, etc. may not be correct.

    • @param po persistent object

    • @param timing see TIMING_ constants

    • @return error message or null
      */
      public String docValidate (PO po, int timing)
      {
      if (po.get_TableName().equalsIgnoreCase(“M_InOut”))
      return docValidate((MInOut) po, timing);

      else if (po.get_TableName().equalsIgnoreCase(“M_Movement”))
      return docValidate((MMovement) po, timing);

      else if (po.get_TableName().equalsIgnoreCase(“M_Inventory”))
      return docValidate((MInventory) po, timing);

      return null;
      } // docValidate

    /**

    • Quantity On Hand.
    • @param M_Product_ID
    • @param M_Locator_ID
    • @param M_AttributeSetInstance_ID
    • @return BigDecimal quantity of product
      */
      private BigDecimal getQtyOnHand(int M_Product_ID, int M_Locator_ID, int M_AttributeSetInstance_ID)

    {
    BigDecimal QtyOnHand = Env.ZERO;
    String sql = “SELECT SUM(QtyOnHand) FROM M_Storage "
    + “WHERE M_Product_ID=?”; // 1
    if(M_Locator_ID > 0)
    sql = sql + " AND M_Locator_ID=?”; // 2

    if(M_AttributeSetInstance_ID > 0)
    	sql = sql + " AND M_AttributeSetInstance_ID=?";	//	3
    /***/
    
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try
    {
    	pstmt = DB.prepareStatement(sql, null);
    	pstmt.setInt(1, M_Product_ID);
    	if(M_Locator_ID > 0)
    		pstmt.setInt(2, M_Locator_ID);
    	
    	if (M_AttributeSetInstance_ID != 0)
    		pstmt.setInt (3, M_AttributeSetInstance_ID);
    	
    	rs = pstmt.executeQuery();
    	if (rs.next())
    	{
    		QtyOnHand = rs.getBigDecimal(1);
    	}
    }
    catch (SQLException e)
    {
    	log.log(Level.SEVERE, sql, e);
    	return Env.ZERO;
    }
    finally{
           DB.close(rs, pstmt);
    }
    
    if(QtyOnHand != null)
    	return QtyOnHand;
    
    return Env.ZERO;
    

    } // QtyOnHand

    /**

    • Validate Movement.

    • @param MMovement movement

    • @param timing see TIMING_ constants

    • @return error message or null movimentação de material
      */
      private String docValidate(MMovement mov, int timing)
      {
      Properties ctx = mov.getCtx();

      if (timing == TIMING_BEFORE_COMPLETE)
      {
      MMovementLine[] lines = mov.getLines(true);
      ArrayList prod = new ArrayList();

      if(lines == null
      		|| lines.length <= 0)
      	return Msg.getMsg(ctx, "NoLines");
      
      for(MMovementLine line : lines)
      {
      	if(line.getM_Product_ID() <=0 
      			|| line.getM_Locator_ID() <=0)
      		return "Produto ou Localizador inválido";
      	
      	if (line.getM_AttributeSetInstance_ID() != 0)
      	{
      	  if(((line.getM_Product_ID() > 0) && (line.getM_Locator_ID() > 0))
      		  && (line.getM_AttributeSetInstance_ID() <=0))
      		    
      		return "Atributo Necessário";
      	}
      
      					
      	if(line.getMovementQty().equals(Env.ZERO))
      		return "Itens com qtd zero";
      	
      	
      	if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
      		return "Duas linhas usando o mesmo produto na mesma posição";
      	else
      		prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
      				
      
      	if (line.getM_AttributeSetInstance_ID() != 0)
      	{
      		if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID()))
      			return "Duas linhas usando o mesmo produto na mesma posição";
      		else
      			prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID());
      	}	
      	
      	BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(), line.getM_AttributeSetInstance_ID() );
      
      	
      	if(qtdOnHand.compareTo(line.getMovementQty()) == -1)
      		return "Sem saldo na linha=" + line.getLine();
      }
      

      }

      return null;
      }

    /**

    • Quantity On line in Shipment/Receipt

    • @param M_InOut_id

    • @param M_Product_ID

    • @param M_Locator_ID

    • @param M_AttributeSetInstance_ID

    • @return BigDecimal quantity of product
      */
      private BigDecimal getQtyinLine(int M_InOut_id, int M_Product_ID, int M_Locator_ID, int M_AttributeSetInstance_ID)
      {
      BigDecimal QtyinLine = Env.ZERO;

      String sql = "SELECT count(*) FROM M_InOutLine il, M_InOut i where "
      + "i.M_InOut_id=il.M_InOut_id " //AND “i.DocStatus IN (‘CO’,‘CL’)”
      + "AND il.M_InOut_id =? "
      + "AND il.M_Product_ID =? ";

               if(M_Locator_ID > 0)
              	 sql = sql + "AND il.M_Locator_ID =? "; //2
               
               if(M_AttributeSetInstance_ID > 0)
              	 sql = sql + "AND il.M_attributesetinstance_ID =? ";//3
               
               sql =  sql +  "group by   i.M_InOut_id,  il.M_Product_ID,  il.M_Locator_ID,"
                          +  "il.M_AttributeSetInstance_ID";
      

      PreparedStatement pstmt = null;
      ResultSet rs = null;
      try
      {
      pstmt = DB.prepareStatement(sql, null);

      pstmt.setInt(1, M_InOut_id);
      
      pstmt.setInt(2, M_Product_ID);
      
      if(M_Locator_ID > 0)
      	pstmt.setInt(3, M_Locator_ID);
      
       
      if (M_AttributeSetInstance_ID > 0 )
      	pstmt.setInt (4, M_AttributeSetInstance_ID);
      
      
      rs = pstmt.executeQuery();
      if (rs.next())
      {
      	QtyinLine = rs.getBigDecimal(1);
      }
      

      }
      catch (SQLException e)
      {
      log.log(Level.SEVERE, sql, e);
      return Env.ZERO;
      }
      finally{
      DB.close(rs, pstmt);
      }

      if(QtyinLine != null)
      return QtyinLine;

      return Env.ZERO;
      } // QtyinLine

    /**

    • Validate Inventory.

    • @param MInventory inventory

    • @param timing see TIMING_ constants

    • @return error message or null 2 inventario
      */
      private String docValidate(MInventory inv, int timing)
      {
      Properties ctx = inv.getCtx();

      if (timing == TIMING_AFTER_COMPLETE)
      {
      MInventoryLine[] lines = inv.getLines(true);
      ArrayList prod = new ArrayList();

      if(lines == null
      		|| lines.length <= 0)
      	return Msg.getMsg(ctx, "NoLines");
      
      Boolean isInternalUse = (Boolean) inv.get_Value("z_IsInternalUse");
      
      if(isInternalUse != null && isInternalUse)
      {
      	for(MInventoryLine line : lines)
      	{
      		if(line.getM_Product_ID() <=0 
      				|| line.getM_Locator_ID() <=0)
      			return "Produto ou Localizador inválido";
      		
      		if(line.getMovementQty().equals(Env.ZERO))
      			return "Itens com qtd zero";
      		
      		if(line.getC_Charge_ID() == 0)
      			return "Sem conta de destino";
      
      		
      		
      		if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))
      			return "Duas linhas usando o mesmo produto na mesma posição";
      		else
      			prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
      
      		if (line.getM_AttributeSetInstance_ID() != 0)
      		{										
      			if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID()))
      				return "Duas linhas usando o mesmo produto na mesma posição";
      			else
      				prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID());
      		}
      
      		BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(),line.getM_AttributeSetInstance_ID());
      
      		
      		if(qtdOnHand.compareTo(line.getQtyInternalUse()) == -1)
      			return "Sem saldo na linha=" + line.getLine();
      	}
      }
      

      }

      return null;
      }

    /**

    • Validate Shipment/Receipt.

    • @param MInOut inventory

    • @param timing see TIMING_ constants

    • @return error message or null expedição e recebimento 3
      */
      private String docValidate(MInOut inOut, int timing)
      {
      Properties ctx = inOut.getCtx();
      String trx = inOut.get_TrxName();

      if (timing == TIMING_AFTER_COMPLETE)
      {
      String sql = "SELECT C_DocType_ID FROM C_DocType " +
      “WHERE lbr_IsManufactured=‘Y’ AND C_DocType_ID=?”;

      /** 
       * Verifica se é industrialização
       * */
      if(DB.getSQLValue(trx, sql, inOut.getC_DocType_ID()) < 0)
      	return null;
      
      MInOutLine[] lines = inOut.getLines();
      
      for(MInOutLine line : lines)
      {
      	int C_OrderLine_ID = line.getC_OrderLine_ID();
      	
      	if(C_OrderLine_ID > 0)
      	{
      		MOrderLine oLine = new MOrderLine(ctx, C_OrderLine_ID, trx);
      		Integer ii = (Integer) oLine.get_Value("M_ProductionLine_ID");
      		int M_ProductionLine_ID = 0;
      		
      		if(ii != null)
      			M_ProductionLine_ID = ii.intValue();
      		else
      			continue;
      		
      		/**	
      		 * Atualiza a quantidade entregue de industrialização
      		 * */
      		
      		DB.executeUpdate("UPDATE M_ProductionLine " +
      				"SET QtyDelivered=COALESCE(QtyDelivered,0)+ " +
      				"(SELECT QtyEntered FROM M_InOutLine " +
      				"WHERE M_InOutLine_ID=" + line.getM_InOutLine_ID() + ") " +
      				"WHERE M_ProductionLine_ID=" + M_ProductionLine_ID, trx);
      	}
      }	
      

      }
      else if ((timing == TIMING_BEFORE_COMPLETE || timing == TIMING_BEFORE_REVERSECORRECT))
      {
      MInOutLine[] lines = inOut.getLines();
      ArrayList olines = new ArrayList();

      if (lines.length == 0)
      	return "Documento sem linhas";
      
      for (int i = 0; i < lines.length; i++)
      {
      	MInOutLine line = lines[i];
      	int M_Product_ID = line.getM_Product_ID();
      	int M_Locator_ID = line.getM_Locator_ID();
      	int M_AttributeSetInstance_ID = line.getM_AttributeSetInstance_ID();
          
      	
      	
      	BigDecimal onHand = Env.ZERO, qtyToShip = Env.ZERO;
      	MProduct produto = MProduct.get(ctx, M_Product_ID);
      	
      	if (!produto.isStocked())
      		continue;
      	
      	
      
      	if (M_Locator_ID == 0)
      		return "Localizador do estoque não definida na linha: #" + line.getLine() + ".";
      
      	if (line.getQtyEntered() == Env.ZERO)
      		return "Item com quantidade ZERO na linha: #" + line.getLine() + ".";
      	
      	if (!MSysConfig.getBooleanValue("LBR_ALLOW_MM_SHIP_RECEIPT_WITHOUT_ORDER", true, inOut.getAD_Client_ID())
      			&& line.getC_OrderLine_ID() == 0)
      		return "Ordem de Compra não disponível.";
      	
      	MOrderLine oline = new MOrderLine(ctx, line.getC_OrderLine_ID(), trx);
      	
      	if (timing == TIMING_BEFORE_REVERSECORRECT 
      			&& !MSysConfig.getBooleanValue("LBR_ALLOW_REVERSE_SHIP_RECEIT_WITH_OPEN_INVOICE", true, inOut.getAD_Client_ID())
      			&& DB.getSQLValue(null, "SELECT COUNT(*) FROM C_InvoiceLine il, C_Invoice i WHERE i.C_Invoice_ID=il.C_Invoice_ID AND i.DocStatus IN ('CO','CL') AND il.M_InOutLine_ID=?", line.getM_InOutLine_ID()) > 0)
      		return "Fatura(s) em aberto. Impossível continuar com o estorno.";
      
      
      					
      					
      	
      	int C_OrderLine_ID = line.getC_OrderLine_ID();
      	if (C_OrderLine_ID != 0)
      	{
      		if(!MSysConfig.getBooleanValue("LBR_ALLOW_DUPLICATED_ORDERLINE_ON_SHIP_RECEIPT", true, inOut.getAD_Client_ID())
      				&& olines.contains("" + line.getC_OrderLine_ID()))
      			return "Linha #" + line.getLine() + " duplicada da mesma ordem.";
      		else
      			olines.add(line.getC_OrderLine_ID());
      	}
      	
      	/**
      	 *  FIXME: QtyDelivered é na UDM padrão, QtyEntered pode ser outra,
      	 *  com isso a comparação, pode não funcionar corretamente.
      	 *  
      	 */
      	
      	log.info("Delivered: " + oline.getQtyDelivered() + " Entered: " + oline.getQtyEntered() + " Trying: " + line.getQtyEntered());
      	if (timing == TIMING_BEFORE_COMPLETE
      			&& MSysConfig.getBooleanValue("LBR_MATCH_SHIPMENT_RECEIPT_AND_ORDER_QTY", false, inOut.getAD_Client_ID())
      			&& oline.getQtyDelivered().add(line.getQtyEntered()).doubleValue() > oline.getQtyEntered().doubleValue())
      		return "Nao e possivel fazer recebimento maior que o pedido. Linha do pedido #" + line.getLine();
      	
      	if (M_Product_ID == 0)
      		continue;
      
      	onHand = getQtyOnHand(M_Product_ID, M_Locator_ID, M_AttributeSetInstance_ID); //QtyOnHand
      	
      	
      	
      	qtyToShip = Env.ZERO;
      	
      	for (int j = 0; j < lines.length; j++)
      	{
      		if(lines[j].getM_Product_ID() == line.getM_Product_ID()
      				&& lines[j].getM_Locator_ID() == line.getM_Locator_ID()
      				&& lines[j].getM_AttributeSetInstance_ID() == line.getM_AttributeSetInstance_ID())
      		{
      			qtyToShip = qtyToShip.add(lines[j].getQtyEntered());
      		}
      	}
      	
      
      	BigDecimal qtdInLine = getQtyinLine(line.getM_InOut_ID(), line.getM_Product_ID(), line.getM_Locator_ID(),line.getM_AttributeSetInstance_ID());
      
      	if (!MSysConfig.getBooleanValue("LBR_ALLOW_TWOLINE_INOUT", true, inOut.getAD_Client_ID())
      			&& (qtdInLine.intValue() > 1))
      		return "Linha #" + line.getLine() + " duplicada.";
      	
      
      	
      	
      	if (timing == TIMING_BEFORE_COMPLETE
      			&& !MSysConfig.getBooleanValue("LBR_ALLOW_NEGATIVE_STOCK", true, inOut.getAD_Client_ID())){
      						
      		String movementType = inOut.getMovementType();
      		
      		if (movementType.charAt(1) == '-')
      		{
      			if (timing == TIMING_BEFORE_COMPLETE
      					&& onHand.subtract(qtyToShip).doubleValue() < 0)
      				return "Sem quantidade disponivel na linha #" + line.getLine() + ".";
      		}
      		else 
      		{
      			if (onHand.add(line.getQtyEntered()).doubleValue() < 0)
      				return "Sem quantidade disponível na linha #" + line.getLine() + ".";
      		}
      		
      		
      		
      		
      	
      	}
      	
      	
      } // for;
      

      }

      return null;
      }

    /**

    • Update Info Window Columns.
      • add new Columns
      • remove columns
      • change dispay sequence
    • @param columns array of columns
    • @param sqlFrom from clause, can be modified
    • @param sqlOrder order by clause, can me modified
    • @return true if you updated columns, sequence or sql From clause
      /
      public boolean updateInfoColumns (ArrayList<Info_Column> columns,
      StringBuffer sqlFrom, StringBuffer sqlOrder)
      {
      /
      * *
      int AD_Role_ID = Env.getAD_Role_ID (Env.getCtx()); // Can be Role/User specific
      String from = sqlFrom.toString();
      if (from.startsWith (“M_Product”))
      {
      columns.add (new Info_Column(“Header”, “‘sql’”, String.class).seq(35));
      return true;
      }/** */
      return false;
      } // updateInfoColumns

    /**

    • String Representation
    • @return info
      */
      public String toString ()
      {
      StringBuffer sb = new StringBuffer (“AdempiereLBR - Powered by Kenos & Faire”);
      return sb.toString ();
      } // toString

} //ValidatorInOut[/code]


Processo de rastreabilidade
#7

Bom dia à todos,

alterei o código do Edílson, pois verifiquei que ao realizar uma movimentação de estoque possuíndo o mesmo produto em mais de uma linha, não era completado.

Ao debugar o código, realizei a mudança no método Validate Movement na classe ValidatorInOut:

/**
* Validate Movement.
*
* @param MMovement movement
* @param timing see TIMING_ constants
* @return error message or null movimentação de material
*/
private String docValidate(MMovement mov, int timing)
{
Properties ctx = mov.getCtx();

  if (timing == TIMING_BEFORE_COMPLETE)
  {
     MMovementLine[] lines = mov.getLines(true);
     ArrayList<String> prod = new ArrayList<String>();
     
     if(lines == null
           || lines.length <= 0)
        return Msg.getMsg(ctx, "NoLines");
     
     for(MMovementLine line : lines)
     {
        if(line.getM_Product_ID() <=0 
              || line.getM_Locator_ID() <=0)
           return "Produto ou Localizador inválido";
        
        if (line.getM_AttributeSetInstance_ID() != 0)
        {
          if(((line.getM_Product_ID() > 0) && (line.getM_Locator_ID() > 0))
             && (line.getM_AttributeSetInstance_ID() <=0))
               
           return "Atributo Necessário";
        }
     
                    
        if(line.getMovementQty().equals(Env.ZERO))
           return "Itens com qtd zero";
                                    
     
        if (line.getM_AttributeSetInstance_ID() != 0)
        {
           if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID()))
              return "Duas linhas usando o mesmo produto na mesma posição";
           else
              prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID() + "|" +  line.getM_AttributeSetInstance_ID());
        } else {
        	if(prod.contains("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID()))//Código que localizava-se antes do if (line.getM_AttributeSetInstance_ID() != 0)
                return "Duas linhas usando o mesmo produto na mesma posição";
             else
                prod.add("" + line.getM_Product_ID() + "|" + line.getM_Locator_ID());
        	
        }
        
        BigDecimal qtdOnHand = getQtyOnHand(line.getM_Product_ID(), line.getM_Locator_ID(), line.getM_AttributeSetInstance_ID() );

        
        if(qtdOnHand.compareTo(line.getMovementQty()) == -1)
           return "Sem saldo na linha=" + line.getLine();
     }
  }
  
  return null;

}

Ele estava comparando duas vezes a mesma coisa, porém, quando coloquei um else no attributesetinstance !=0 com o código antes deste if, deu certo.

Alguma observação no código?

Grato pela atenção.