IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has incon… (#1598)

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- refinement and added unit tests

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- add unit test for lot level costing

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- fix unit test error.

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- add unit test for cost adjustment

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- add unit test for physical inventory

* IDEMPIERE-5503 Product Costs > Cost Movement > Field AMOUNT has inconsistent +/- signs

- add unit test for PO landed cost
This commit is contained in:
hengsin 2022-12-20 21:29:07 +08:00 committed by GitHub
parent 7c0055f797
commit 06b02889f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1153 additions and 34 deletions

View File

@ -0,0 +1,10 @@
SELECT register_migration_script('202212021150_IDEMPIERE-5503.sql') FROM dual;
SET SQLBLANKLINES ON
SET DEFINE OFF
UPDATE M_CostDetail SET Amt = Amt * -1 WHERE Qty < 0 AND Amt > 0 AND M_InOutLine_ID IS NOT NULL
;
UPDATE M_CostDetail SET Amt = Amt * -1 WHERE Qty > 0 AND Amt < 0 AND M_InOutLine_ID IS NOT NULL
;

View File

@ -0,0 +1,8 @@
SELECT register_migration_script('202212021150_IDEMPIERE-5503.sql') FROM dual;
UPDATE M_CostDetail SET Amt = Amt * -1 WHERE Qty < 0 AND Amt > 0 AND M_InOutLine_ID IS NOT NULL
;
UPDATE M_CostDetail SET Amt = Amt * -1 WHERE Qty > 0 AND Amt < 0 AND M_InOutLine_ID IS NOT NULL
;

View File

@ -1481,7 +1481,7 @@ public abstract class Doc
ResultSet rs = null; ResultSet rs = null;
try try
{ {
pstmt = DB.prepareStatement(sql, null); pstmt = DB.prepareStatement(sql, getTrxName());
if (para_1 == -1) // GL Accounts if (para_1 == -1) // GL Accounts
pstmt.setInt (1, as.getC_AcctSchema_ID()); pstmt.setInt (1, as.getC_AcctSchema_ID());
else else

View File

@ -20,7 +20,9 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import org.compiere.model.I_C_OrderLine; import org.compiere.model.I_C_OrderLine;
@ -194,6 +196,7 @@ public class Doc_InOut extends Doc
{ {
for (int i = 0; i < p_lines.length; i++) for (int i = 0; i < p_lines.length; i++)
{ {
Map<String, BigDecimal> batchLotCostMap = null;
DocLine_InOut line = (DocLine_InOut) p_lines[i]; DocLine_InOut line = (DocLine_InOut) p_lines[i];
MProduct product = line.getProduct(); MProduct product = line.getProduct();
BigDecimal costs = null; BigDecimal costs = null;
@ -207,16 +210,19 @@ public class Doc_InOut extends Doc
MInOutLineMA mas[] = MInOutLineMA.get(getCtx(), ioLine.get_ID(), getTrxName()); MInOutLineMA mas[] = MInOutLineMA.get(getCtx(), ioLine.get_ID(), getTrxName());
if (mas != null && mas.length > 0 ) if (mas != null && mas.length > 0 )
{ {
batchLotCostMap = new HashMap<>();
costs = BigDecimal.ZERO; costs = BigDecimal.ZERO;
for (int j = 0; j < mas.length; j++) for (int j = 0; j < mas.length; j++)
{ {
MInOutLineMA ma = mas[j]; MInOutLineMA ma = mas[j];
BigDecimal QtyMA = ma.getMovementQty(); BigDecimal QtyMA = ma.getMovementQty();
if (QtyMA.signum() != line.getQty().signum())
QtyMA = QtyMA.negate();
ProductCost pc = line.getProductCost(); ProductCost pc = line.getProductCost();
pc.setQty(QtyMA); pc.setQty(QtyMA);
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());
BigDecimal maCosts = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InOutLine_ID=?"); BigDecimal maCosts = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InOutLine_ID=?");
batchLotCostMap.put(ma.getM_InOutLineMA_UU(), maCosts);
costs = costs.add(maCosts); costs = costs.add(maCosts);
} }
} }
@ -329,10 +335,31 @@ public class Doc_InOut extends Doc
for (int j = 0; j < mas.length; j++) for (int j = 0; j < mas.length; j++)
{ {
MInOutLineMA ma = mas[j]; MInOutLineMA ma = mas[j];
BigDecimal qty = ma.getMovementQty();
if (qty.signum() != line.getQty().signum())
qty = qty.negate();
BigDecimal amt = batchLotCostMap != null ? batchLotCostMap.get(ma.getM_InOutLineMA_UU()) : null;
if (amt == null && isReversal(line))
{
amt = findReversalCostDetailAmt(as, line.getM_Product_ID(), ma, line.getReversalLine_ID());
if (amt != null)
amt = amt.negate();
}
if (amt == null)
{
amt = costs.divide(line.getProductCost().getQty(), RoundingMode.HALF_UP);
amt = amt.multiply(qty);
}
else if (!isReversal(line) && line.getProductCost().getQty().signum() != line.getQty().signum())
{
if (amt.signum() != (costs.signum() * -1)) {
amt = amt.negate();
}
}
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, ma.getMovementQty().negate(), amt, qty,
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -346,10 +373,13 @@ public class Doc_InOut extends Doc
// //
if (line.getM_Product_ID() != 0) if (line.getM_Product_ID() != 0)
{ {
BigDecimal amt = costs;
if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line))
amt = amt.negate();
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, line.getQty(), amt, line.getQty(),
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -363,10 +393,13 @@ public class Doc_InOut extends Doc
// //
if (line.getM_Product_ID() != 0) if (line.getM_Product_ID() != 0)
{ {
BigDecimal amt = costs;
if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line))
amt = amt.negate();
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, line.getQty(), amt, line.getQty(),
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -398,6 +431,7 @@ public class Doc_InOut extends Doc
DocLine_InOut line = (DocLine_InOut) p_lines[i]; DocLine_InOut line = (DocLine_InOut) p_lines[i];
MProduct product = line.getProduct(); MProduct product = line.getProduct();
BigDecimal costs = null; BigDecimal costs = null;
Map<String, BigDecimal> batchLotCostMap = null;
if (!isReversal(line)) if (!isReversal(line))
{ {
if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(product.getCostingLevel(as)) ) if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(product.getCostingLevel(as)) )
@ -409,15 +443,18 @@ public class Doc_InOut extends Doc
costs = BigDecimal.ZERO; costs = BigDecimal.ZERO;
if (mas != null && mas.length > 0 ) if (mas != null && mas.length > 0 )
{ {
batchLotCostMap = new HashMap<>();
for (int j = 0; j < mas.length; j++) for (int j = 0; j < mas.length; j++)
{ {
MInOutLineMA ma = mas[j]; MInOutLineMA ma = mas[j];
BigDecimal QtyMA = ma.getMovementQty(); BigDecimal QtyMA = ma.getMovementQty();
if (QtyMA.signum() != line.getQty().signum())
QtyMA = QtyMA.negate();
ProductCost pc = line.getProductCost(); ProductCost pc = line.getProductCost();
pc.setQty(QtyMA); pc.setQty(QtyMA);
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());
BigDecimal maCosts = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InOutLine_ID=?"); BigDecimal maCosts = line.getProductCosts(as, line.getAD_Org_ID(), true, "M_InOutLine_ID=?");
batchLotCostMap.put(ma.getM_InOutLineMA_UU(), maCosts);
costs = costs.add(maCosts); costs = costs.add(maCosts);
} }
} }
@ -490,10 +527,31 @@ public class Doc_InOut extends Doc
for (int j = 0; j < mas.length; j++) for (int j = 0; j < mas.length; j++)
{ {
MInOutLineMA ma = mas[j]; MInOutLineMA ma = mas[j];
BigDecimal qty = ma.getMovementQty();
if (qty.signum() != line.getQty().signum())
qty = qty.negate();
BigDecimal amt = batchLotCostMap != null ? batchLotCostMap.get(ma.getM_InOutLineMA_UU()) : null;
if (amt == null && isReversal(line))
{
amt = findReversalCostDetailAmt(as, line.getM_Product_ID(), ma, line.getReversalLine_ID());
if (amt != null)
amt = amt.negate();
}
if (amt == null)
{
amt = costs.divide(line.getProductCost().getQty(), RoundingMode.HALF_UP);
amt = amt.multiply(qty);
}
else if (!isReversal(line) && line.getProductCost().getQty().signum() != line.getQty().signum())
{
if (amt.signum() != (costs.signum() * -1)) {
amt = amt.negate();
}
}
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, ma.getMovementQty(), amt, qty,
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -505,10 +563,13 @@ public class Doc_InOut extends Doc
{ {
if (line.getM_Product_ID() != 0) if (line.getM_Product_ID() != 0)
{ {
BigDecimal amt = costs;
if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line))
amt = amt.negate();
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, line.getQty(), amt, line.getQty(),
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -521,10 +582,13 @@ public class Doc_InOut extends Doc
// //
if (line.getM_Product_ID() != 0) if (line.getM_Product_ID() != 0)
{ {
BigDecimal amt = costs;
if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line))
amt = amt.negate();
if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), if (!MCostDetail.createShipment(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costs, line.getQty(), amt, line.getQty(),
line.getDescription(), true, getTrxName())) line.getDescription(), true, getTrxName()))
{ {
p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record"); p_Error = Msg.getMsg(getCtx(),"Failed to create cost detail record");
@ -899,6 +963,8 @@ public class Doc_InOut extends Doc
{ {
MInOutLineMA ma = mas[j]; MInOutLineMA ma = mas[j];
BigDecimal QtyMA = ma.getMovementQty(); BigDecimal QtyMA = ma.getMovementQty();
if (QtyMA.signum() != line.getQty().signum())
QtyMA = QtyMA.negate();
ProductCost pc = line.getProductCost(); ProductCost pc = line.getProductCost();
pc.setQty(QtyMA); pc.setQty(QtyMA);
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());
@ -1009,6 +1075,17 @@ public class Doc_InOut extends Doc
return facts; return facts;
} // createFact } // createFact
private BigDecimal findReversalCostDetailAmt(MAcctSchema as, int M_Product_ID, MInOutLineMA ma, int reversalLine_ID) {
StringBuilder select = new StringBuilder("SELECT ").append(MCostDetail.COLUMNNAME_Amt)
.append(" FROM ").append(MCostDetail.Table_Name).append(" WHERE ")
.append(MCostDetail.COLUMNNAME_C_AcctSchema_ID).append("=? ")
.append("AND ").append(MCostDetail.COLUMNNAME_M_AttributeSetInstance_ID).append("=? ")
.append("AND ").append(MCostDetail.COLUMNNAME_M_InOutLine_ID).append("=? ")
.append("AND ").append(MCostDetail.COLUMNNAME_M_Product_ID).append("=? ");
BigDecimal amt = DB.getSQLValueBDEx(getTrxName(), select.toString(), as.getC_AcctSchema_ID(), ma.getM_AttributeSetInstance_ID(), reversalLine_ID, M_Product_ID);
return amt;
}
private boolean isReversal(DocLine line) { private boolean isReversal(DocLine line) {
return m_Reversal_ID !=0 && line.getReversalLine_ID() != 0; return m_Reversal_ID !=0 && line.getReversalLine_ID() != 0;
} }

View File

@ -316,6 +316,7 @@ public class Doc_Inventory extends Doc
p_Error = "Original Physical Inventory not posted yet"; p_Error = "Original Physical Inventory not posted yet";
return null; return null;
} }
costs = dr.getAcctBalance(); //get original cost
} }
// InventoryDiff DR CR // InventoryDiff DR CR
@ -357,7 +358,6 @@ public class Doc_Inventory extends Doc
p_Error = "Original Physical Inventory not posted yet"; p_Error = "Original Physical Inventory not posted yet";
return null; return null;
} }
costs = cr.getAcctBalance(); //get original cost
} }
} }
} }
@ -386,11 +386,15 @@ public class Doc_Inventory extends Doc
{ {
MInventoryLineMA ma = mas[j]; MInventoryLineMA ma = mas[j];
BigDecimal maCost = costMap.get(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID()); BigDecimal maCost = costMap.get(line.get_ID()+ "_"+ ma.getM_AttributeSetInstance_ID());
BigDecimal qty = ma.getMovementQty();
if (qty.signum() != line.getQty().signum())
qty = qty.negate();
if (maCost.signum() != costDetailAmt.signum())
maCost = maCost.negate();
if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), if (!MCostDetail.createInventory(as, line.getAD_Org_ID(),
line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
maCost, ma.getMovementQty().negate(), maCost, qty,
line.getDescription(), getTrxName())) line.getDescription(), getTrxName()))
{ {
p_Error = "Failed to create cost detail record"; p_Error = "Failed to create cost detail record";
@ -401,10 +405,11 @@ public class Doc_Inventory extends Doc
} }
else else
{ {
BigDecimal amt = costDetailAmt;
if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), if (!MCostDetail.createInventory(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costDetailAmt, line.getQty(), amt, line.getQty(),
line.getDescription(), getTrxName())) line.getDescription(), getTrxName()))
{ {
p_Error = "Failed to create cost detail record"; p_Error = "Failed to create cost detail record";
@ -415,10 +420,11 @@ public class Doc_Inventory extends Doc
else else
{ {
// Cost Detail // Cost Detail
BigDecimal amt = costDetailAmt;
if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), if (!MCostDetail.createInventory(as, line.getAD_Org_ID(),
line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(),
line.get_ID(), 0, line.get_ID(), 0,
costDetailAmt, line.getQty(), amt, line.getQty(),
line.getDescription(), getTrxName())) line.getDescription(), getTrxName()))
{ {
p_Error = "Failed to create cost detail record"; p_Error = "Failed to create cost detail record";

View File

@ -154,6 +154,8 @@ public class Doc_Movement extends Doc
{ {
MMovementLineMA ma = mas[j]; MMovementLineMA ma = mas[j];
BigDecimal QtyMA = ma.getMovementQty(); BigDecimal QtyMA = ma.getMovementQty();
if (QtyMA.signum() != line.getQty().signum())
QtyMA = QtyMA.negate();
ProductCost pc = line.getProductCost(); ProductCost pc = line.getProductCost();
pc.setQty(QtyMA); pc.setQty(QtyMA);
pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID());

View File

@ -161,6 +161,8 @@ public class Doc_ProjectIssue extends Doc
if (product != null && product.get_ID() > 0 && !product.isService() && product.isStocked()) { if (product != null && product.get_ID() > 0 && !product.isService() && product.isStocked()) {
BigDecimal costDetailQty = m_line.getQty(); BigDecimal costDetailQty = m_line.getQty();
BigDecimal costDetailAmt = cost; BigDecimal costDetailAmt = cost;
if (m_line.getQty().signum() != m_line.getProductCost().getQty().signum())
costDetailAmt = costDetailAmt.negate();
if (!MCostDetail.createProjectIssue(as, m_line.getAD_Org_ID(), if (!MCostDetail.createProjectIssue(as, m_line.getAD_Org_ID(),
m_line.getM_Product_ID(), m_line.getM_AttributeSetInstance_ID(), m_line.getM_Product_ID(), m_line.getM_AttributeSetInstance_ID(),
m_line.get_ID(), 0, m_line.get_ID(), 0,

View File

@ -663,6 +663,27 @@ public class MCostDetail extends X_M_CostDetail
return retValue; return retValue;
} // get } // get
/**************************************************************************
* Get Cost Detail Records
* @param ctx context
* @param whereClause where clause
* @param ID 1st parameter
* @param M_AttributeSetInstance_ID ASI
* @param trxName trx
* @return list of cost detail record
*/
public static List<MCostDetail> list (Properties ctx, String whereClause,
int ID, int M_AttributeSetInstance_ID, int C_AcctSchema_ID, String trxName)
{
StringBuilder localWhereClause = new StringBuilder(whereClause)
.append(" AND M_AttributeSetInstance_ID=?")
.append(" AND C_AcctSchema_ID=?");
List<MCostDetail> retValue = new Query(ctx,I_M_CostDetail.Table_Name,localWhereClause.toString(),trxName)
.setParameters(ID,M_AttributeSetInstance_ID,C_AcctSchema_ID)
.list();
return retValue;
} // get
/** /**
* Process Cost Details for product * Process Cost Details for product
* @param product product * @param product product
@ -732,15 +753,15 @@ public class MCostDetail extends X_M_CostDetail
* @param M_Product_ID product * @param M_Product_ID product
* @param M_AttributeSetInstance_ID asi * @param M_AttributeSetInstance_ID asi
* @param M_CostElement_ID optional cost element for Freight * @param M_CostElement_ID optional cost element for Freight
* @param Amt amt * @param amt Amount
* @param Qty qty * @param qty Quantity
* @param Description optional description * @param description optional description
* @param trxName transaction * @param trxName transaction
*/ */
public MCostDetail (MAcctSchema as, int AD_Org_ID, public MCostDetail (MAcctSchema as, int AD_Org_ID,
int M_Product_ID, int M_AttributeSetInstance_ID, int M_Product_ID, int M_AttributeSetInstance_ID,
int M_CostElement_ID, BigDecimal Amt, BigDecimal Qty, int M_CostElement_ID, BigDecimal amt, BigDecimal qty,
String Description, String trxName) String description, String trxName)
{ {
this (as.getCtx(), 0, trxName); this (as.getCtx(), 0, trxName);
setClientOrg(as.getAD_Client_ID(), AD_Org_ID); setClientOrg(as.getAD_Client_ID(), AD_Org_ID);
@ -749,10 +770,9 @@ public class MCostDetail extends X_M_CostDetail
setM_AttributeSetInstance_ID (M_AttributeSetInstance_ID); setM_AttributeSetInstance_ID (M_AttributeSetInstance_ID);
// //
setM_CostElement_ID(M_CostElement_ID); setM_CostElement_ID(M_CostElement_ID);
// setAmt (amt);
setAmt (Amt); setQty (qty);
setQty (Qty); setDescription(description);
setDescription(Description);
} // MCostDetail } // MCostDetail
/** /**

View File

@ -121,7 +121,14 @@ public class ProductCost
m_C_UOM_ID = C_UOM_ID; m_C_UOM_ID = C_UOM_ID;
} // setQty } // setQty
/**
*
* @return qty
*/
public BigDecimal getQty()
{
return m_qty;
}
/** Product Revenue Acct */ /** Product Revenue Acct */

View File

@ -381,6 +381,24 @@ public final class DictionaryIDs {
} }
} }
public enum M_CostElement {
MATERIAL(100),
FREIGHT(101),
FIFO(102),
AVERAGE_PO(103),
AVERAGE_INVOICE(104),
LABOR(105),
BURDEN(50000),
OVERHEAD(50001),
OUTSIDE_PROCESSING(50002);
public final int id;
private M_CostElement(int id) {
this.id = id;
}
}
public enum M_DiscountSchema { public enum M_DiscountSchema {
SALES_2001(100), SALES_2001(100),
PURCHASE_2001(101), PURCHASE_2001(101),

View File

@ -0,0 +1,972 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* 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., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.idempiere.test.costing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.List;
import org.compiere.model.MAcctSchema;
import org.compiere.model.MAttributeSet;
import org.compiere.model.MAttributeSetExclude;
import org.compiere.model.MAttributeSetInstance;
import org.compiere.model.MBPartner;
import org.compiere.model.MClient;
import org.compiere.model.MCost;
import org.compiere.model.MCostDetail;
import org.compiere.model.MCostElement;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MInOutLineMA;
import org.compiere.model.MInventory;
import org.compiere.model.MInventoryLine;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLandedCost;
import org.compiere.model.MOrderLine;
import org.compiere.model.MPriceList;
import org.compiere.model.MPriceListVersion;
import org.compiere.model.MProcess;
import org.compiere.model.MProduct;
import org.compiere.model.MProductCategory;
import org.compiere.model.MProductCategoryAcct;
import org.compiere.model.MProductPrice;
import org.compiere.model.MProduction;
import org.compiere.model.MProductionLine;
import org.compiere.model.MProject;
import org.compiere.model.MProjectIssue;
import org.compiere.model.MStorageOnHand;
import org.compiere.process.DocAction;
import org.compiere.process.DocumentEngine;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ServerProcessCtl;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.compiere.wf.MWorkflow;
import org.eevolution.model.MPPProductBOM;
import org.eevolution.model.MPPProductBOMLine;
import org.idempiere.test.AbstractTestCase;
import org.idempiere.test.DictionaryIDs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Isolated;
@Isolated
public class AveragePOCostingTest extends AbstractTestCase {
public AveragePOCostingTest() {
}
@Test
public void testMaterialReceipt() {
MProduct product = null;
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
try {
product = new MProduct(Env.getCtx(), 0, null);
product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.CHEMICALS.id);
product.setName("testMaterialReceipt");
product.setValue("testMaterialReceipt");
product.setProductType(MProduct.PRODUCTTYPE_Item);
product.setIsStocked(true);
product.setIsSold(true);
product.setIsPurchased(true);
product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id);
product.saveEx();
MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null);
MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName());
pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID());
pp.setM_Product_ID(product.get_ID());
pp.setPriceStd(new BigDecimal("2"));
pp.setPriceList(new BigDecimal("2"));
pp.saveEx();
MInOutLine receiptLine = createPOAndMRForProduct(product.get_ID(), null, null);
product.set_TrxName(getTrxName());
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertNotNull(cost, "No MCost record found");
assertEquals(new BigDecimal("2.00"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
MCostDetail cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", receiptLine.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for material receipt line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("2.00"), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
receiptLine = createPOAndMRForProduct(product.get_ID(), null, new BigDecimal("3.00"));
cost.load(getTrxName());
//(2+3)/2
assertEquals(new BigDecimal("2.50"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", receiptLine.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for material receipt line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("3.00"), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
//reverse MR2
MInOut receipt = new MInOut(Env.getCtx(), receiptLine.getM_InOut_ID(), getTrxName());
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
receipt.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, receipt.getDocStatus(), "Unexpected Document Status");
MInOut reverse = new MInOut(Env.getCtx(), receipt.getReversal_ID(), getTrxName());
if (!reverse.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), reverse.getAD_Client_ID(), reverse.get_Table_ID(), reverse.get_ID(), false, getTrxName());
assertNull(error, error);
}
//back to cost=2 after reversal
cost.load(getTrxName());
assertEquals(new BigDecimal("2.00"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", receiptLine.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName());
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("0.00"), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
} finally {
rollback();
if (product != null) {
product.set_TrxName(null);
product.deleteEx(true);
}
}
}
@Test
public void testShipment() {
MProduct product = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, getTrxName());
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.AZALEA_BUSH.id, null, new BigDecimal("5.00"));
cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
BigDecimal currentCost = cost.getCurrentCostPrice();
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
line1.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
order.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus(), "Unexpected Document Status");
MInOut shipment = new MInOut(order, DictionaryIDs.C_DocType.MM_SHIPMENT.id, order.getDateOrdered());
shipment.setDocStatus(DocAction.STATUS_Drafted);
shipment.setDocAction(DocAction.ACTION_Complete);
shipment.saveEx();
MInOutLine shipmentLine = new MInOutLine(shipment);
shipmentLine.setOrderLine(line1, 0, new BigDecimal("1"));
shipmentLine.setQty(new BigDecimal("1"));
shipmentLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus(), "Unexpected Document Status");
//cost shouldn't change after shipment
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
MCostDetail cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", shipmentLine.get_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for shipment line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
//reverse shipment
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, shipment.getDocStatus(), "Unexpected Document Status");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
MInOut reversal = new MInOut(Env.getCtx(), shipment.getReversal_ID(), getTrxName());
MInOutLine[] reversalLines = reversal.getLines();
cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", reversalLines[0].get_ID(), 0, as.get_ID(), getTrxName());
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
}
@Test
public void testInternalUse() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MProduct product = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id, getTrxName());
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0 || cost.getCurrentQty().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id, null, null);
cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
assertNotNull(cost, "No MCost Record");
BigDecimal currentCost = cost.getCurrentCostPrice();
MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName());
inventory.setC_DocType_ID(DictionaryIDs.C_DocType.INTERNAL_USE_INVENTORY.id);
inventory.saveEx();
MInventoryLine line = new MInventoryLine(Env.getCtx(), 0, getTrxName());
line.setM_Inventory_ID(inventory.get_ID());
line.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
line.setQtyInternalUse(new BigDecimal("1.00"));
line.setC_Charge_ID(DictionaryIDs.C_Charge.COMMISSIONS.id);
line.setM_Locator_ID(DictionaryIDs.M_Locator.HQ.id);
line.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus(), "Unexpected Document Status");
MCostDetail cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", line.getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
//reverse internal use
info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Reverse_Accrual);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Reversed, inventory.getDocStatus(), "Unexpected Document Status");
MInventory reversal = new MInventory(Env.getCtx(), inventory.getReversal_ID(), getTrxName());
cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", reversal.getLines(false)[0].getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
}
@Test
public void testProjectIssue() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MProduct product = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id, getTrxName());
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0 || cost.getCurrentQty().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id, null, null);
cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
assertNotNull(cost, "No MCost Record");
BigDecimal currentCost = cost.getCurrentCostPrice();
MProject project = new MProject(Env.getCtx(), 0, getTrxName());
project.setName("testProjectIssue");
project.setC_Currency_ID(DictionaryIDs.C_Currency.USD.id);
project.saveEx();
MProjectIssue issue = new MProjectIssue(project);
issue.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
issue.setLine(10);
issue.setM_Locator_ID(DictionaryIDs.M_Locator.HQ.id);
issue.setMovementQty(new BigDecimal("1.00"));
issue.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(issue, DocAction.ACTION_Complete);
issue.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, issue.getDocStatus(), "Unexpected Document Status");
if (!issue.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), issue.getAD_Client_ID(), issue.get_Table_ID(), issue.get_ID(), false, getTrxName());
assertNull(error, error);
}
MCostDetail cd = MCostDetail.get(Env.getCtx(), "C_ProjectIssue_ID=?", issue.getC_ProjectIssue_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
//reverse issue
info = MWorkflow.runDocumentActionWorkflow(issue, DocAction.ACTION_Reverse_Accrual);
issue.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Reversed, issue.getDocStatus(), "Unexpected Document Status");
cd = MCostDetail.get(Env.getCtx(), "C_ProjectIssue_ID=?", issue.getReversal_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
}
@Test
public void testProduction() {
MProduct mulch = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id, getTrxName());
MProduct azb = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, getTrxName());
MProduct mulchX = new MProduct(Env.getCtx(), 0, null);
mulchX.setName("MulchX2");
mulchX.setIsBOM(true);
mulchX.setIsStocked(true);
mulchX.setC_UOM_ID(mulch.getC_UOM_ID());
mulchX.setM_Product_Category_ID(mulch.getM_Product_Category_ID());
mulchX.setProductType(mulch.getProductType());
mulchX.setM_AttributeSet_ID(mulch.getM_AttributeSet_ID());
mulchX.setC_TaxCategory_ID(mulch.getC_TaxCategory_ID());
mulchX.saveEx();
try {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MCost cost = mulch.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0 || cost.getCurrentQty().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id, null, null);
cost = mulch.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
BigDecimal mulchCost = cost.getCurrentCostPrice();
cost = azb.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0 || cost.getCurrentQty().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.AZALEA_BUSH.id, null, null);
cost = azb.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
BigDecimal azbCost = cost.getCurrentCostPrice();
MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName());
bom.setM_Product_ID(mulchX.get_ID());
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive);
bom.setBOMUse(MPPProductBOM.BOMUSE_Master);
bom.setName(mulchX.getName());
bom.saveEx();
MPPProductBOMLine line1 = new MPPProductBOMLine(bom);
line1.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
line1.setQtyBOM(new BigDecimal("1"));
line1.saveEx();
MPPProductBOMLine line2 = new MPPProductBOMLine(bom);
line2.setM_Product_ID(DictionaryIDs.M_Product.AZALEA_BUSH.id);
line2.setQtyBOM(new BigDecimal("1"));
line2.saveEx();
mulchX.load((String)null);
mulchX.setIsVerified(true);
mulchX.saveEx();
MProduction production = new MProduction(Env.getCtx(), 0, getTrxName());
production.setM_Product_ID(mulchX.get_ID());
production.setM_Locator_ID(DictionaryIDs.M_Locator.HQ.id);
production.setIsUseProductionPlan(false);
production.setMovementDate(getLoginDate());
production.setDocAction(DocAction.ACTION_Complete);
production.setDocStatus(DocAction.STATUS_Drafted);
production.setIsComplete(false);
production.setProductionQty(new BigDecimal("1"));
production.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID());
production.saveEx();
int productionCreate = 53226;
MProcess process = MProcess.get(Env.getCtx(), productionCreate);
ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_User_ID(getAD_User_ID());
pi.setRecord_ID(production.get_ID());
pi.setTransactionName(getTrxName());
ServerProcessCtl.process(pi, getTrx(), false);
assertFalse(pi.isError(), pi.getSummary());
production.load(getTrxName());
MProductionLine[] plines = production.getLines();
assertEquals("Y", production.getIsCreated(), "MProduction.IsCreated != Y");
assertTrue(plines.length > 0, "No Production Lines");
assertEquals(3, plines.length, "Unexpected number of production lines");
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(production, DocAction.ACTION_Complete);
production.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, production.getDocStatus(), "Production Status="+production.getDocStatus());
BigDecimal rollup = mulchCost.add(azbCost);
MCostDetail cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", plines[0].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(rollup.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
mulchX.set_TrxName(getTrxName());
cost = mulchX.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertEquals(rollup.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", plines[1].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(mulchCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost = mulch.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertEquals(mulchCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", plines[2].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(azbCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost = azb.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertEquals(azbCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
//reverse production
info = MWorkflow.runDocumentActionWorkflow(production, DocAction.ACTION_Reverse_Accrual);
production.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Reversed, production.getDocStatus(), "Production Status="+production.getDocStatus());
MProduction reversal = new MProduction(Env.getCtx(), production.getReversal_ID(), getTrxName());
MProductionLine[] reversalLines = reversal.getLines();
cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", reversalLines[0].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(rollup.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", reversalLines[1].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(mulchCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost = mulch.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertEquals(mulchCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
cd = MCostDetail.get(Env.getCtx(), "M_ProductionLine_ID=?", reversalLines[2].getM_ProductionLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for project issue line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(azbCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost = azb.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
} finally {
rollback();
DB.executeUpdateEx("delete from m_cost where m_product_id=?", new Object[] {mulchX.get_ID()}, null);
mulchX.set_TrxName(null);
mulchX.deleteEx(true);
}
}
@Test
public void testMRAndShipmentByLot() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
MProductCategory lotLevel = new MProductCategory(Env.getCtx(), 0, null);
lotLevel.setName("testMaterialReceiptLot");
lotLevel.saveEx();
MProduct product = null;
MAttributeSetExclude exclude = null;
MAttributeSetExclude exclude1 = null;
try {
MAttributeSet mas = new MAttributeSet(Env.getCtx(), DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id, getTrxName());
mas.setMandatoryType(MAttributeSet.MANDATORYTYPE_NotMandatory);
mas.saveEx();
exclude = new MAttributeSetExclude(Env.getCtx(), 0, null);
exclude.setM_AttributeSet_ID(mas.get_ID());
exclude.setAD_Table_ID(MOrderLine.Table_ID);
exclude.setIsSOTrx(true);
exclude.saveEx();
exclude1 = new MAttributeSetExclude(Env.getCtx(), 0, null);
exclude1.setM_AttributeSet_ID(mas.get_ID());
exclude1.setAD_Table_ID(MInOutLine.Table_ID);
exclude1.setIsSOTrx(true);
exclude1.saveEx();
MProductCategoryAcct lotLevelAcct = MProductCategoryAcct.get(lotLevel.get_ID(), as.get_ID());
lotLevelAcct = new MProductCategoryAcct(Env.getCtx(), lotLevelAcct);
lotLevelAcct.setCostingLevel(MAcctSchema.COSTINGLEVEL_BatchLot);
lotLevelAcct.saveEx();
product = new MProduct(Env.getCtx(), 0, null);
product.setM_Product_Category_ID(lotLevel.get_ID());
product.setName("testMaterialReceiptLot");
product.setProductType(MProduct.PRODUCTTYPE_Item);
product.setIsStocked(true);
product.setIsSold(true);
product.setIsPurchased(true);
product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id);
product.setM_AttributeSet_ID(DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id);
product.saveEx();
MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null);
MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName());
pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID());
pp.setM_Product_ID(product.get_ID());
pp.setPriceStd(new BigDecimal("2"));
pp.setPriceList(new BigDecimal("2"));
pp.saveEx();
MAttributeSetInstance asi1 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi1.setM_AttributeSet_ID(DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id);
asi1.setLot("Lot1");
asi1.saveEx();
MInOutLine line1 = createPOAndMRForProduct(product.get_ID(), asi1, new BigDecimal("2.00"));
MAttributeSetInstance asi2 = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName());
asi2.setM_AttributeSet_ID(DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id);
asi2.setLot("Lot2");
asi2.saveEx();
MInOutLine line2 = createPOAndMRForProduct(product.get_ID(), asi2, new BigDecimal("3.00"));
MCostDetail cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", line1.getC_OrderLine_ID(), asi1.get_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("2").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", line2.getC_OrderLine_ID(), asi2.get_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("3").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
product.set_TrxName(getTrxName());
MCost cost1 = product.getCostingRecord(as, getAD_Org_ID(), asi1.get_ID(), as.getCostingMethod());
assertNotNull(cost1, "MCost record not found");
assertEquals(new BigDecimal("2.00"), cost1.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP));
MCost cost2 = product.getCostingRecord(as, getAD_Org_ID(), asi2.get_ID(), as.getCostingMethod());
assertNotNull(cost2, "MCost record not found");
assertEquals(new BigDecimal("3.00"), cost2.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP));
//shipment
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDatePromised(today);
order.saveEx();
MOrderLine ol1 = new MOrderLine(order);
ol1.setLine(10);
//Azalea Bush
ol1.setProduct(product);
ol1.setQty(new BigDecimal("2"));
ol1.setDatePromised(today);
ol1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
order.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus(), "Unexpected document status");
MInOut shipment = new MInOut(order, DictionaryIDs.C_DocType.MM_SHIPMENT.id, order.getDateOrdered());
shipment.setDocStatus(DocAction.STATUS_Drafted);
shipment.setDocAction(DocAction.ACTION_Complete);
shipment.saveEx();
MInOutLine shipmentLine = new MInOutLine(shipment);
shipmentLine.setOrderLine(ol1, 0, new BigDecimal("2"));
shipmentLine.setQty(new BigDecimal("2"));
shipmentLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus(), "Unexpected document status");
MInOutLineMA[] linema = MInOutLineMA.get(Env.getCtx(), shipmentLine.get_ID(), getTrxName());
assertEquals(2, linema.length, "Unexpected number of MInOutLineMA records");
assertEquals(linema[0].getM_AttributeSetInstance_ID(), asi1.get_ID(), "Unexpected M_AttributeSetInstance_ID for MInOutLineMA 1");
assertEquals(linema[1].getM_AttributeSetInstance_ID(), asi2.get_ID(), "Unexpected M_AttributeSetInstance_ID for MInOutLineMA 2");
cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", shipmentLine.getM_InOutLine_ID(), linema[0].getM_AttributeSetInstance_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("2").negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", shipmentLine.getM_InOutLine_ID(), linema[1].getM_AttributeSetInstance_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("3").negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
//reverse shipment
info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
shipment.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, shipment.getDocStatus(), "Unexpected document status");
MInOut reversal = new MInOut(Env.getCtx(), shipment.getReversal_ID(), getTrxName());
MInOutLine[] reversalLines = reversal.getLines();
cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", reversalLines[0].getM_InOutLine_ID(), asi1.getM_AttributeSetInstance_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("2").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cd = MCostDetail.get(Env.getCtx(), "M_InOutLine_ID=?", reversalLines[0].getM_InOutLine_ID(), asi2.getM_AttributeSetInstance_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line1");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("3").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost1 = product.getCostingRecord(as, getAD_Org_ID(), asi1.get_ID(), as.getCostingMethod());
assertNotNull(cost1, "MCost record not found");
assertEquals(new BigDecimal("2.00"), cost1.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP));
cost2 = product.getCostingRecord(as, getAD_Org_ID(), asi2.get_ID(), as.getCostingMethod());
assertNotNull(cost2, "MCost record not found");
assertEquals(new BigDecimal("3.00"), cost2.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP));
//reverse MR2
MInOut mr2 = new MInOut(Env.getCtx(), line2.getM_InOut_ID(), getTrxName());
info = MWorkflow.runDocumentActionWorkflow(mr2, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
mr2.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, mr2.getDocStatus(), "Unexpected document status");
cd = MCostDetail.get(Env.getCtx(), "C_OrderLine_ID=?", line2.getC_OrderLine_ID(), asi2.getM_AttributeSetInstance_ID(), as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for order line2");
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("0").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
} finally {
rollback();
if (exclude != null)
exclude.deleteEx(true);
if (exclude1 != null)
exclude1.deleteEx(true);
if (product != null) {
product.set_TrxName(null);
product.deleteEx(true);
}
lotLevel.deleteEx(true);
}
}
@Test
public void testCostAdjustment() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MProduct product = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id, getTrxName());
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
if (cost == null || cost.getCurrentCostPrice().signum() == 0 || cost.getCurrentQty().signum() == 0) {
createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id, null, null);
cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
}
assertNotNull(cost, "No MCost Record");
BigDecimal currentCost = cost.getCurrentCostPrice();
BigDecimal adjustment = new BigDecimal("0.25");
MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName());
inventory.setC_DocType_ID(DictionaryIDs.C_DocType.COST_ADJUSTMENT.id);
inventory.setC_Currency_ID(as.getC_Currency_ID());
inventory.setCostingMethod(as.getCostingMethod());
inventory.saveEx();
MInventoryLine line = new MInventoryLine(Env.getCtx(), 0, getTrxName());
line.setM_Inventory_ID(inventory.get_ID());
line.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
line.setC_Charge_ID(DictionaryIDs.C_Charge.COMMISSIONS.id);
line.setCurrentCostPrice(currentCost);
line.setNewCostPrice(currentCost.add(adjustment));
line.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus(), "Unexpected Document Status");
MCostDetail cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", line.getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(adjustment.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.add(adjustment).setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
//reverse cost adjustment
info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Reverse_Accrual);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Reversed, inventory.getDocStatus(), "Unexpected Document Status");
MInventory reversal = new MInventory(Env.getCtx(), inventory.getReversal_ID(), getTrxName());
MInventoryLine[] reversalLines = reversal.getLines(true);
cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", reversalLines[0].getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(adjustment.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
}
@Test
public void testPhysicalInventory() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MProduct product = new MProduct(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id, getTrxName());
createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id, null, null);
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertNotNull(cost, "No MCost Record");
BigDecimal currentCost = cost.getCurrentCostPrice();
MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName());
inventory.setC_DocType_ID(DictionaryIDs.C_DocType.MATERIAL_PHYSICAL_INVENTORY.id);
inventory.saveEx();
MInventoryLine line = new MInventoryLine(Env.getCtx(), 0, getTrxName());
line.setM_Inventory_ID(inventory.get_ID());
line.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
line.setM_Locator_ID(DictionaryIDs.M_Locator.HQ.id);
BigDecimal qtyOnHand = MStorageOnHand.getQtyOnHandForLocator(line.getM_Product_ID(), line.getM_Locator_ID(), 0, getTrxName());
line.setQtyBook(qtyOnHand);
line.setQtyCount(line.getQtyBook().add(new BigDecimal("1")));
line.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus(), "Unexpected Document Status");
MCostDetail cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", line.getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
//reverse physical inventory
info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Reverse_Accrual);
inventory.load(getTrxName());
assertFalse(info.isError(), info.getSummary());
assertEquals(DocAction.STATUS_Reversed, inventory.getDocStatus(), "Unexpected Document Status");
MInventory reversal = new MInventory(Env.getCtx(), inventory.getReversal_ID(), getTrxName());
MInventoryLine[] reversalLines = reversal.getLines(true);
cd = MCostDetail.get(Env.getCtx(), "M_InventoryLine_ID=?", reversalLines[0].getM_InventoryLine_ID(), 0, as.get_ID(), getTrxName());
assertNotNull(cd, "MCostDetail not found for internal use line");
assertEquals(-1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(currentCost.negate().setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
cost.load(getTrxName());
assertEquals(currentCost.setScale(2, RoundingMode.HALF_UP), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost");
}
@Test
public void testLandedCostForPO() {
MClient client = MClient.get(Env.getCtx());
MAcctSchema as = client.getAcctSchema();
assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO");
MProduct product = null;
try {
product = new MProduct(Env.getCtx(), 0, null);
product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id);
product.setName("testLandedCostForPO");
product.setProductType(MProduct.PRODUCTTYPE_Item);
product.setIsStocked(true);
product.setIsSold(true);
product.setIsPurchased(true);
product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id);
product.saveEx();
MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null);
MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName());
pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID());
pp.setM_Product_ID(product.get_ID());
pp.setPriceStd(new BigDecimal("2"));
pp.setPriceList(new BigDecimal("2"));
pp.saveEx();
//create order
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id));
order.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id);
order.setIsSOTrx(false);
order.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
line1.setProduct(new MProduct(Env.getCtx(), product.get_ID(), getTrxName()));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.setPrice(new BigDecimal("2"));
line1.saveEx();
MOrderLandedCost landedCost = new MOrderLandedCost(Env.getCtx(), 0, getTrxName());
landedCost.setC_Order_ID(order.get_ID());
landedCost.setLandedCostDistribution(MOrderLandedCost.LANDEDCOSTDISTRIBUTION_Costs);
landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id);
landedCost.setAmt(new BigDecimal("0.30"));
landedCost.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
MInOut receipt1 = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered());
receipt1.setDocStatus(DocAction.STATUS_Drafted);
receipt1.setDocAction(DocAction.ACTION_Complete);
receipt1.saveEx();
MInOutLine receiptLine1 = new MInOutLine(receipt1);
receiptLine1.setOrderLine(line1, 0, new BigDecimal("1"));
receiptLine1.setQty(new BigDecimal("1"));
receiptLine1.saveEx();
info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
receipt1.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus());
if (!receipt1.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName());
assertNull(error, error);
}
List<MCostDetail> cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", line1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName());
assertTrue(cds.size() == 2, "MCostDetail not found for order line1");
for(MCostDetail cd : cds) {
if (cd.getM_CostElement_ID() == 0) {
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("2.00").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
} else if (cd.getM_CostElement_ID() == DictionaryIDs.M_CostElement.FREIGHT.id ) {
assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("0.30").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
}
}
product.set_TrxName(getTrxName());
MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod());
assertNotNull(cost, "No MCost record found");
assertEquals(new BigDecimal("2.30"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
//reverse receipt
info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
receipt1.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, receipt1.getDocStatus());
cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", line1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName());
assertTrue(cds.size() == 2, "MCostDetail not found for order line1");
for(MCostDetail cd : cds) {
if (cd.getM_CostElement_ID() == 0) {
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("0").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
} else if (cd.getM_CostElement_ID() == DictionaryIDs.M_CostElement.FREIGHT.id ) {
assertEquals(0, cd.getQty().intValue(), "Unexpected MCostDetail Qty");
assertEquals(new BigDecimal("0").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt");
}
}
} finally {
rollback();
if (product != null) {
product.set_TrxName(null);
product.deleteEx(true);
}
}
}
private MInOutLine createPOAndMRForProduct(int productId, MAttributeSetInstance asi, BigDecimal price) {
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id));
order.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id);
order.setIsSOTrx(false);
order.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
line1.setProduct(new MProduct(Env.getCtx(), productId, getTrxName()));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
if (price != null)
line1.setPrice(price);
if (asi != null)
line1.setM_AttributeSetInstance_ID(asi.getM_AttributeSetInstance_ID());
line1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
MInOut receipt1 = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered());
receipt1.setDocStatus(DocAction.STATUS_Drafted);
receipt1.setDocAction(DocAction.ACTION_Complete);
receipt1.saveEx();
MInOutLine receiptLine1 = new MInOutLine(receipt1);
receiptLine1.setOrderLine(line1, 0, new BigDecimal("1"));
receiptLine1.setQty(new BigDecimal("1"));
if (asi != null)
receiptLine1.setM_AttributeSetInstance_ID(asi.get_ID());
receiptLine1.saveEx();
info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
receipt1.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus());
if (!receipt1.isPosted()) {
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName());
assertNull(error, error);
}
return receiptLine1;
}
}

View File

@ -122,9 +122,7 @@ public class ProductionTest extends AbstractTestCase {
@Test @Test
public void testAutoProduce() { public void testAutoProduce() {
int mulchId = 137; MProduct mulch = MProduct.get(DictionaryIDs.M_Product.MULCH.id);
int joeBlock = 118;
MProduct mulch = MProduct.get(mulchId);
MProduct mulchX = new MProduct(Env.getCtx(), 0, null); MProduct mulchX = new MProduct(Env.getCtx(), 0, null);
mulchX.setName("MulchX2"); mulchX.setName("MulchX2");
@ -139,7 +137,7 @@ public class ProductionTest extends AbstractTestCase {
mulchX.saveEx(); mulchX.saveEx();
try { try {
createPOAndMRForProduct(mulchId); // create some stock to avoid negative qty average cost exception createPOAndMRForProduct(DictionaryIDs.M_Product.MULCH.id); // create some stock to avoid negative qty average cost exception
MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName()); MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName());
bom.setM_Product_ID(mulchX.get_ID()); bom.setM_Product_ID(mulchX.get_ID());
@ -149,7 +147,7 @@ public class ProductionTest extends AbstractTestCase {
bom.saveEx(); bom.saveEx();
MPPProductBOMLine line = new MPPProductBOMLine(bom); MPPProductBOMLine line = new MPPProductBOMLine(bom);
line.setM_Product_ID(mulchId); line.setM_Product_ID(DictionaryIDs.M_Product.MULCH.id);
line.setQtyBOM(new BigDecimal("2")); line.setQtyBOM(new BigDecimal("2"));
line.saveEx(); line.saveEx();
@ -158,8 +156,7 @@ public class ProductionTest extends AbstractTestCase {
mulchX.saveEx(); mulchX.saveEx();
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id));
order.setBPartner(MBPartner.get(Env.getCtx(), joeBlock));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted); order.setDocStatus(DocAction.STATUS_Drafted);
@ -218,7 +215,7 @@ public class ProductionTest extends AbstractTestCase {
assertTrue(productionLines.length==2,"Number of production line is not 2 as expected ("+productionLines.length+")"); assertTrue(productionLines.length==2,"Number of production line is not 2 as expected ("+productionLines.length+")");
assertTrue(productionLines[0].getM_Product_ID()==shipmentLine.getM_Product_ID(), "Production Line Production <> Shipment Line Product"); assertTrue(productionLines[0].getM_Product_ID()==shipmentLine.getM_Product_ID(), "Production Line Production <> Shipment Line Product");
assertTrue(productionLines[0].getMovementQty().equals(shipmentLine.getMovementQty()), "Production Line Qty <> Shipment Line Qty"); assertTrue(productionLines[0].getMovementQty().equals(shipmentLine.getMovementQty()), "Production Line Qty <> Shipment Line Qty");
assertTrue(productionLines[1].getM_Product_ID()==mulchId,"Production Line 2 Product is not the expected component product"); assertTrue(productionLines[1].getM_Product_ID()==DictionaryIDs.M_Product.MULCH.id,"Production Line 2 Product is not the expected component product");
assertTrue(productionLines[1].getMovementQty().intValue()==-2,"Production Line 2 Qty is not the expected component qty"); assertTrue(productionLines[1].getMovementQty().intValue()==-2,"Production Line 2 Qty is not the expected component qty");
} finally { } finally {
rollback(); rollback();