/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* 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. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.acct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MCostDetail;
import org.compiere.model.MProduct;
import org.compiere.model.MProduction;
import org.compiere.model.MProductionLineMA;
import org.compiere.model.ProductCost;
import org.compiere.model.X_M_Production;
import org.compiere.model.X_M_ProductionLine;
import org.compiere.util.DB;
import org.compiere.util.Env;
/**
* Post {@link MProduction} Documents.
*
* Table: M_Production (325)
* Document Types: MMP
*
* @author Jorg Janke
* @version $Id: Doc_Production.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $
*/
public class Doc_Production extends Doc
{
/**
* Constructor
* @param as accounting schema
* @param rs record
* @param trxName trx
*/
public Doc_Production (MAcctSchema as, ResultSet rs, String trxName)
{
super (as, X_M_Production.class, rs, null, trxName);
} // Doc_Production
/**
* Load Document Details
* @return error message or null
*/
@Override
protected String loadDocumentDetails()
{
setC_Currency_ID (NO_CURRENCY);
X_M_Production prod = (X_M_Production)getPO();
setDateDoc (prod.getMovementDate());
setDateAcct(prod.getMovementDate());
// Contained Objects
p_lines = loadLines(prod);
if (log.isLoggable(Level.FINE)) log.fine("Lines=" + p_lines.length);
return null;
} // loadDocumentDetails
private Map mQtyProduced;
/**
* IDEMPIERE-3082
* @param mQtyProduced
* @param line
* @param isUsePlan
* @param addMoreQty when you want get value, just pass null
* @return qty produce for line (include addMoreQty if addMoreQty is not null)
*/
private BigDecimal manipulateQtyProduced (Map mQtyProduced, X_M_ProductionLine line, Boolean isUsePlan, BigDecimal addMoreQty){
BigDecimal qtyProduced = null;
Integer key = isUsePlan?line.getM_ProductionPlan_ID():line.getM_Production_ID();
if (mQtyProduced.containsKey(key)){
qtyProduced = mQtyProduced.get(key);
}else{
qtyProduced = BigDecimal.ZERO;
mQtyProduced.put(key, qtyProduced);
}
if (addMoreQty != null){
qtyProduced = qtyProduced.add(addMoreQty);
mQtyProduced.put(key, qtyProduced);
}
return qtyProduced;
}
/**
* Load production lines
* @param prod production
* @return DoaLine Array
*/
private DocLine[] loadLines(X_M_Production prod)
{
ArrayList list = new ArrayList();
mQtyProduced = new HashMap<>();
String sqlPL = null;
if (prod.isUseProductionPlan()){
// Production Plan
// -- ProductionLine - the real level
sqlPL = "SELECT * FROM "
+ " M_ProductionLine pro_line INNER JOIN M_ProductionPlan plan ON pro_line.M_ProductionPlan_id = plan.M_ProductionPlan_id "
+ " INNER JOIN M_Production pro ON pro.M_Production_id = plan.M_Production_id "
+ " WHERE pro.M_Production_ID=? "
+ " ORDER BY plan.M_ProductionPlan_id, pro_line.Line";
}else{
// Production
// -- ProductionLine - the real level
sqlPL = "SELECT * FROM M_ProductionLine pl "
+ "WHERE pl.M_Production_ID=? "
+ "ORDER BY pl.Line";
}
PreparedStatement pstmtPL = null;
ResultSet rsPL = null;
try
{
pstmtPL = DB.prepareStatement(sqlPL, getTrxName());
pstmtPL.setInt(1,get_ID());
rsPL = pstmtPL.executeQuery();
while (rsPL.next())
{
X_M_ProductionLine line = new X_M_ProductionLine(getCtx(), rsPL, getTrxName());
if (line.getMovementQty().signum() == 0)
{
if (log.isLoggable(Level.INFO)) log.info("LineQty=0 - " + line);
continue;
}
DocLine docLine = new DocLine (line, this);
docLine.setQty (line.getMovementQty(), false);
// Identify finished BOM Product
if (prod.isUseProductionPlan())
docLine.setProductionBOM(line.getM_Product_ID() == line.getM_ProductionPlan().getM_Product_ID());
else
docLine.setProductionBOM(line.getM_Product_ID() == prod.getM_Product_ID());
if (docLine.isProductionBOM()){
manipulateQtyProduced (mQtyProduced, line, prod.isUseProductionPlan(), line.getMovementQty());
}
//
if (log.isLoggable(Level.FINE)) log.fine(docLine.toString());
list.add (docLine);
}
}
catch (Exception ee)
{
log.log(Level.SEVERE, sqlPL, ee);
}
finally
{
DB.close(rsPL, pstmtPL);
rsPL = null;
pstmtPL = null;
}
DocLine[] dl = new DocLine[list.size()];
list.toArray(dl);
return dl;
} // loadLines
/**
* Get Balance
* @return Zero (always balanced)
*/
@Override
public BigDecimal getBalance()
{
BigDecimal retValue = Env.ZERO;
return retValue;
} // getBalance
/**
* Create Facts (the accounting logic) for
* MMP.
*
* Production
* Inventory DR CR
*
* @param as account schema
* @return Fact
*/
@Override
public ArrayList createFacts (MAcctSchema as)
{
// create Fact Header
Fact fact = new Fact(this, as, Fact.POST_Actual);
setC_Currency_ID (as.getC_Currency_ID());
// Line pointer
FactLine fl = null;
X_M_Production prod = (X_M_Production)getPO();
HashMap costMap = new HashMap();
for (int i = 0; i < p_lines.length; i++)
{
DocLine line = p_lines[i];
// Calculate Costs
BigDecimal costs = BigDecimal.ZERO;
X_M_ProductionLine prodline = (X_M_ProductionLine)line.getPO();
MProductionLineMA mas[] = MProductionLineMA.get(getCtx(), prodline.get_ID(), getTrxName());
MProduct product = (MProduct) prodline.getM_Product();
String CostingLevel = product.getCostingLevel(as);
String costingMethod = product.getCostingMethod(as);
if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel) )
{
if (line.getM_AttributeSetInstance_ID() == 0 && (mas!=null && mas.length> 0 ))
{
for (int j = 0; j < mas.length; j++)
{
MProductionLineMA ma = mas[j];
MCostDetail cd = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line.get_ID(), ma.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
if (cd != null)
costs = costs.add(cd.getAmt());
else
{
ProductCost pc = line.getProductCost();
pc.setQty(ma.getMovementQty());
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());
costs = costs.add(line.getProductCosts(as, line.getAD_Org_ID(), false));
}
costMap.put(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID(), costs);
}
}
else
{
MCostDetail cd = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line.get_ID(), line.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
if (cd != null)
{
costs = cd.getAmt();
}
else
{
costs = line.getProductCosts(as, line.getAD_Org_ID(), false);
}
costMap.put(line.get_ID()+ "_"+ line.getM_AttributeSetInstance_ID(), costs);
}
}
else
{
// MZ Goodwill
// if Production CostDetail exist then get Cost from Cost Detail
MCostDetail cd = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line.get_ID(), line.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
if (cd != null)
{
costs = cd.getAmt();
}
else
{
costs = line.getProductCosts(as, line.getAD_Org_ID(), false);
}
costMap.put(line.get_ID()+ "_"+ line.getM_AttributeSetInstance_ID(), costs);
}
int stdPrecision = as.getStdPrecision();
BigDecimal bomCost = Env.ZERO;
BigDecimal qtyProduced = null;
if (line.isProductionBOM())
{
X_M_ProductionLine endProLine = (X_M_ProductionLine)line.getPO();
int parentEndPro = prod.isUseProductionPlan()?endProLine.getM_ProductionPlan_ID():endProLine.getM_Production_ID();
// Get BOM Cost - Sum of individual lines
for (int ii = 0; ii < p_lines.length; ii++)
{
DocLine line0 = p_lines[ii];
X_M_ProductionLine bomProLine = (X_M_ProductionLine)line0.getPO();
int parentBomPro = prod.isUseProductionPlan()?bomProLine.getM_ProductionPlan_ID():bomProLine.getM_Production_ID();
if (parentBomPro != parentEndPro)
continue;
if (!line0.isProductionBOM()) {
MProduct product0 = (MProduct) bomProLine.getM_Product();
String CostingLevel0 = product0.getCostingLevel(as);
if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel0) )
{
if (bomProLine.getM_AttributeSetInstance_ID() == 0 )
{
MProductionLineMA bomLineMA[] = MProductionLineMA.get(getCtx(), line0.get_ID(), getTrxName());
if (bomLineMA!=null && bomLineMA.length> 0 )
{
// get cost of children for batch costing level (auto generate)
BigDecimal costs0 = BigDecimal.ZERO ;
for (int j = 0; j < bomLineMA.length; j++)
{
BigDecimal maCost = BigDecimal.ZERO ;
MProductionLineMA ma = bomLineMA[j];
// get cost of children
MCostDetail cd0 = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line0.get_ID(), ma.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
if (cd0 != null)
maCost = cd0.getAmt();
else
{
ProductCost pc = line0.getProductCost();
pc.setQty(ma.getMovementQty());
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());
maCost = line0.getProductCosts(as, line0.getAD_Org_ID(), false);
}
costMap.put(line0.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID(),maCost);
costs0 = costs0.add(maCost);
}
bomCost = bomCost.add(costs0);
}
else
p_Error = "Failed to post - No Attribute Set for line";
}
else
{
// get cost of children for batch costing level
MCostDetail cd0 = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line0.get_ID(), line0.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
BigDecimal costs0;
if (cd0 != null)
{
costs0 = cd0.getAmt();
}
else
{
costs0 = line0.getProductCosts(as, line0.getAD_Org_ID(), false);
}
costMap.put(line0.get_ID()+ "_"+ line0.getM_AttributeSetInstance_ID(),costs0);
bomCost = bomCost.add(costs0);
}
}
else
{
// get cost of children
MCostDetail cd0 = MCostDetail.get (as.getCtx(), "M_ProductionLine_ID=?",
line0.get_ID(), line0.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName());
BigDecimal costs0;
if (cd0 != null)
{
costs0 = cd0.getAmt();
}
else
{
costs0 = line0.getProductCosts(as, line0.getAD_Org_ID(), false);
}
costMap.put(line0.get_ID()+ "_"+ line0.getM_AttributeSetInstance_ID(),costs0);
bomCost = bomCost.add(costs0);
}
}
}
qtyProduced = manipulateQtyProduced (mQtyProduced, endProLine, prod.isUseProductionPlan(), null);
if (line.getQty().compareTo(qtyProduced) != 0)
{
BigDecimal factor = line.getQty().divide(qtyProduced, 12, RoundingMode.HALF_UP);
bomCost = bomCost.multiply(factor);
}
if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel))
{
//post roll-up
fl = fact.createLine(line,
line.getAccount(ProductCost.ACCTTYPE_P_Asset, as),
as.getC_Currency_ID(), bomCost.negate().setScale(stdPrecision, RoundingMode.HALF_UP));
if (fl == null)
{
p_Error = "Couldn't post roll-up " + line.getLine() + " - " + line;
return null;
}
fl.setQty(qtyProduced);
}
else if (MAcctSchema.COSTINGMETHOD_StandardCosting.equals(costingMethod))
{
BigDecimal variance = costs.subtract(bomCost.negate()).setScale(stdPrecision, RoundingMode.HALF_UP);
// only post variance if it's not zero
if (variance.signum() != 0)
{
//post variance
fl = fact.createLine(line,
line.getAccount(ProductCost.ACCTTYPE_P_RateVariance, as),
as.getC_Currency_ID(), variance.negate());
if (fl == null)
{
p_Error = "Couldn't post variance " + line.getLine() + " - " + line;
return null;
}
fl.setQty(Env.ZERO);
}
}
}
// end MZ
// Inventory DR CR
if (!(line.isProductionBOM() && MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel)))
{
BigDecimal factLineAmt = costs;
if (line.isProductionBOM() && !(MAcctSchema.COSTINGMETHOD_StandardCosting.equals(costingMethod)))
{
factLineAmt = bomCost.negate();
}
fl = fact.createLine(line,
line.getAccount(ProductCost.ACCTTYPE_P_Asset, as),
as.getC_Currency_ID(), factLineAmt.setScale(stdPrecision, RoundingMode.HALF_UP));
if (fl == null)
{
p_Error = "No Costs for Line " + line.getLine() + " - " + line;
return null;
}
fl.setM_Locator_ID(line.getM_Locator_ID());
fl.setQty(line.getQty());
}
// Cost Detail
String description = line.getDescription();
if (description == null)
description = "";
if (line.isProductionBOM())
description += "(*)";
if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(CostingLevel))
{
if (line.isProductionBOM())
{
if (!MCostDetail.createProduction(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0,
bomCost.negate(), qtyProduced,
description, getTrxName()))
{
p_Error = "Failed to create cost detail record";
return null;
}
}
else if (line.getM_AttributeSetInstance_ID() == 0 && (mas!=null && mas.length> 0 ))
{
for (int j = 0; j < mas.length; j++)
{
MProductionLineMA ma = mas[j];
BigDecimal maCost = costMap.get(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID());
if (!MCostDetail.createProduction(as, line.getAD_Org_ID(),
line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(),
line.get_ID(), 0,
maCost, ma.getMovementQty(),
description, getTrxName()))
{
p_Error = "Failed to create cost detail record";
return null;
}
}
}
else
{
if (!MCostDetail.createProduction(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0,
costs, line.getQty(),
description, getTrxName()))
{
p_Error = "Failed to create cost detail record";
return null;
}
}
}
else
{
if (line.isProductionBOM() && !(MAcctSchema.COSTINGMETHOD_StandardCosting.equals(costingMethod)))
{
if (!MCostDetail.createProduction(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0,
bomCost.negate(), line.getQty(),
description, getTrxName()))
{
p_Error = "Failed to create cost detail record";
return null;
}
}
else
{
if (!MCostDetail.createProduction(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0,
costs, line.getQty(),
description, getTrxName()))
{
p_Error = "Failed to create cost detail record";
return null;
}
}
}
}
//
ArrayList facts = new ArrayList();
facts.add(fact);
return facts;
} // createFact
} // Doc_Production