diff --git a/migration/iD10/oracle/202212021150_IDEMPIERE-5503.sql b/migration/iD10/oracle/202212021150_IDEMPIERE-5503.sql new file mode 100644 index 0000000000..7773dcc957 --- /dev/null +++ b/migration/iD10/oracle/202212021150_IDEMPIERE-5503.sql @@ -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 +; diff --git a/migration/iD10/postgresql/202212021150_IDEMPIERE-5503.sql b/migration/iD10/postgresql/202212021150_IDEMPIERE-5503.sql new file mode 100644 index 0000000000..da4cad02d0 --- /dev/null +++ b/migration/iD10/postgresql/202212021150_IDEMPIERE-5503.sql @@ -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 +; + diff --git a/org.adempiere.base/src/org/compiere/acct/Doc.java b/org.adempiere.base/src/org/compiere/acct/Doc.java index 8f8a1f0ed9..945835419f 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc.java @@ -1481,7 +1481,7 @@ public abstract class Doc ResultSet rs = null; try { - pstmt = DB.prepareStatement(sql, null); + pstmt = DB.prepareStatement(sql, getTrxName()); if (para_1 == -1) // GL Accounts pstmt.setInt (1, as.getC_AcctSchema_ID()); else diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java b/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java index 2ff8e0df80..317c8e6892 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java @@ -20,7 +20,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.ResultSet; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; 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++) { + Map batchLotCostMap = null; DocLine_InOut line = (DocLine_InOut) p_lines[i]; MProduct product = line.getProduct(); BigDecimal costs = null; @@ -207,16 +210,19 @@ public class Doc_InOut extends Doc MInOutLineMA mas[] = MInOutLineMA.get(getCtx(), ioLine.get_ID(), getTrxName()); if (mas != null && mas.length > 0 ) { + batchLotCostMap = new HashMap<>(); costs = BigDecimal.ZERO; for (int j = 0; j < mas.length; j++) { MInOutLineMA ma = mas[j]; BigDecimal QtyMA = ma.getMovementQty(); + if (QtyMA.signum() != line.getQty().signum()) + QtyMA = QtyMA.negate(); ProductCost pc = line.getProductCost(); pc.setQty(QtyMA); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_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); } } @@ -329,10 +335,31 @@ public class Doc_InOut extends Doc for (int j = 0; j < mas.length; 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(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, ma.getMovementQty().negate(), + amt, qty, line.getDescription(), true, getTrxName())) { 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) { + BigDecimal amt = costs; + if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line)) + amt = amt.negate(); if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, line.getQty(), + amt, line.getQty(), line.getDescription(), true, getTrxName())) { 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) { + BigDecimal amt = costs; + if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line)) + amt = amt.negate(); if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, line.getQty(), + amt, line.getQty(), line.getDescription(), true, getTrxName())) { 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]; MProduct product = line.getProduct(); BigDecimal costs = null; + Map batchLotCostMap = null; if (!isReversal(line)) { if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(product.getCostingLevel(as)) ) @@ -409,15 +443,18 @@ public class Doc_InOut extends Doc costs = BigDecimal.ZERO; if (mas != null && mas.length > 0 ) { + batchLotCostMap = new HashMap<>(); for (int j = 0; j < mas.length; j++) { MInOutLineMA ma = mas[j]; BigDecimal QtyMA = ma.getMovementQty(); + if (QtyMA.signum() != line.getQty().signum()) + QtyMA = QtyMA.negate(); ProductCost pc = line.getProductCost(); pc.setQty(QtyMA); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_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); } } @@ -490,10 +527,31 @@ public class Doc_InOut extends Doc for (int j = 0; j < mas.length; 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(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, ma.getMovementQty(), + amt, qty, line.getDescription(), true, getTrxName())) { 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) { + BigDecimal amt = costs; + if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line)) + amt = amt.negate(); if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, line.getQty(), + amt, line.getQty(), line.getDescription(), true, getTrxName())) { 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) { + BigDecimal amt = costs; + if (line.getProductCost().getQty().signum() != line.getQty().signum() && !isReversal(line)) + amt = amt.negate(); if (!MCostDetail.createShipment(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costs, line.getQty(), + amt, line.getQty(), line.getDescription(), true, getTrxName())) { 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]; BigDecimal QtyMA = ma.getMovementQty(); + if (QtyMA.signum() != line.getQty().signum()) + QtyMA = QtyMA.negate(); ProductCost pc = line.getProductCost(); pc.setQty(QtyMA); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); @@ -1009,6 +1075,17 @@ public class Doc_InOut extends Doc return facts; } // 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) { return m_Reversal_ID !=0 && line.getReversalLine_ID() != 0; } diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_Inventory.java b/org.adempiere.base/src/org/compiere/acct/Doc_Inventory.java index 9a41416bda..953cf280c1 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Inventory.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Inventory.java @@ -316,6 +316,7 @@ public class Doc_Inventory extends Doc p_Error = "Original Physical Inventory not posted yet"; return null; } + costs = dr.getAcctBalance(); //get original cost } // InventoryDiff DR CR @@ -356,8 +357,7 @@ public class Doc_Inventory extends Doc { p_Error = "Original Physical Inventory not posted yet"; return null; - } - costs = cr.getAcctBalance(); //get original cost + } } } } @@ -386,11 +386,15 @@ public class Doc_Inventory extends Doc { MInventoryLineMA ma = mas[j]; 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(), line.getM_Product_ID(), ma.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - maCost, ma.getMovementQty().negate(), + maCost, qty, line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; @@ -401,10 +405,11 @@ public class Doc_Inventory extends Doc } else { + BigDecimal amt = costDetailAmt; if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costDetailAmt, line.getQty(), + amt, line.getQty(), line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; @@ -415,10 +420,11 @@ public class Doc_Inventory extends Doc else { // Cost Detail + BigDecimal amt = costDetailAmt; if (!MCostDetail.createInventory(as, line.getAD_Org_ID(), line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), line.get_ID(), 0, - costDetailAmt, line.getQty(), + amt, line.getQty(), line.getDescription(), getTrxName())) { p_Error = "Failed to create cost detail record"; diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_Movement.java b/org.adempiere.base/src/org/compiere/acct/Doc_Movement.java index 1d26b0096e..8e39260642 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Movement.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Movement.java @@ -154,6 +154,8 @@ public class Doc_Movement extends Doc { MMovementLineMA ma = mas[j]; BigDecimal QtyMA = ma.getMovementQty(); + if (QtyMA.signum() != line.getQty().signum()) + QtyMA = QtyMA.negate(); ProductCost pc = line.getProductCost(); pc.setQty(QtyMA); pc.setM_M_AttributeSetInstance_ID(ma.getM_AttributeSetInstance_ID()); diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_ProjectIssue.java b/org.adempiere.base/src/org/compiere/acct/Doc_ProjectIssue.java index 512a8d120e..740ec46697 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_ProjectIssue.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_ProjectIssue.java @@ -161,6 +161,8 @@ public class Doc_ProjectIssue extends Doc if (product != null && product.get_ID() > 0 && !product.isService() && product.isStocked()) { BigDecimal costDetailQty = m_line.getQty(); 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(), m_line.getM_Product_ID(), m_line.getM_AttributeSetInstance_ID(), m_line.get_ID(), 0, diff --git a/org.adempiere.base/src/org/compiere/model/MCostDetail.java b/org.adempiere.base/src/org/compiere/model/MCostDetail.java index a1c3ed4ed2..1b98eb2f73 100644 --- a/org.adempiere.base/src/org/compiere/model/MCostDetail.java +++ b/org.adempiere.base/src/org/compiere/model/MCostDetail.java @@ -663,6 +663,27 @@ public class MCostDetail extends X_M_CostDetail return retValue; } // 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 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 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 * @param product product @@ -732,15 +753,15 @@ public class MCostDetail extends X_M_CostDetail * @param M_Product_ID product * @param M_AttributeSetInstance_ID asi * @param M_CostElement_ID optional cost element for Freight - * @param Amt amt - * @param Qty qty - * @param Description optional description + * @param amt Amount + * @param qty Quantity + * @param description optional description * @param trxName transaction */ public MCostDetail (MAcctSchema as, int AD_Org_ID, int M_Product_ID, int M_AttributeSetInstance_ID, - int M_CostElement_ID, BigDecimal Amt, BigDecimal Qty, - String Description, String trxName) + int M_CostElement_ID, BigDecimal amt, BigDecimal qty, + String description, String trxName) { this (as.getCtx(), 0, trxName); setClientOrg(as.getAD_Client_ID(), AD_Org_ID); @@ -748,11 +769,10 @@ public class MCostDetail extends X_M_CostDetail setM_Product_ID (M_Product_ID); setM_AttributeSetInstance_ID (M_AttributeSetInstance_ID); // - setM_CostElement_ID(M_CostElement_ID); - // - setAmt (Amt); - setQty (Qty); - setDescription(Description); + setM_CostElement_ID(M_CostElement_ID); + setAmt (amt); + setQty (qty); + setDescription(description); } // MCostDetail /** diff --git a/org.adempiere.base/src/org/compiere/model/ProductCost.java b/org.adempiere.base/src/org/compiere/model/ProductCost.java index 9ab36203d5..1e9b91918c 100644 --- a/org.adempiere.base/src/org/compiere/model/ProductCost.java +++ b/org.adempiere.base/src/org/compiere/model/ProductCost.java @@ -121,7 +121,14 @@ public class ProductCost m_C_UOM_ID = C_UOM_ID; } // setQty - + /** + * + * @return qty + */ + public BigDecimal getQty() + { + return m_qty; + } /** Product Revenue Acct */ diff --git a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java index 1194feb690..3f45b72da5 100644 --- a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java +++ b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java @@ -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 { SALES_2001(100), PURCHASE_2001(101), diff --git a/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java b/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java new file mode 100644 index 0000000000..266a5008db --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java @@ -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 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; + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java b/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java index d41ca5d947..38219dea3f 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/ProductionTest.java @@ -122,9 +122,7 @@ public class ProductionTest extends AbstractTestCase { @Test public void testAutoProduce() { - int mulchId = 137; - int joeBlock = 118; - MProduct mulch = MProduct.get(mulchId); + MProduct mulch = MProduct.get(DictionaryIDs.M_Product.MULCH.id); MProduct mulchX = new MProduct(Env.getCtx(), 0, null); mulchX.setName("MulchX2"); @@ -139,7 +137,7 @@ public class ProductionTest extends AbstractTestCase { mulchX.saveEx(); 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()); bom.setM_Product_ID(mulchX.get_ID()); @@ -149,7 +147,7 @@ public class ProductionTest extends AbstractTestCase { bom.saveEx(); 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.saveEx(); @@ -158,8 +156,7 @@ public class ProductionTest extends AbstractTestCase { mulchX.saveEx(); MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); - //Joe Block - order.setBPartner(MBPartner.get(Env.getCtx(), joeBlock)); + 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); @@ -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[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[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"); } finally { rollback();