IDEMPIERE-5836: Adding unit test cases for Non stocked expense curren… (#2024)
* IDEMPIERE-5836: Adding unit test cases for Non stocked expense current bhavior * IDEMPIERE-5836: Refactoring as per code review comments. * IDEMPIERE-5836: Corrected code comments as per peer review
This commit is contained in:
parent
34485f97f8
commit
cb6c8b636d
|
@ -0,0 +1,770 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Copyright (C) 2016 Logilite Technologies LLP *
|
||||||
|
* This program is free software; you can redistribute it and/or modify it *
|
||||||
|
* under the terms version 2 of the GNU General Public License as published *
|
||||||
|
* by the Free Software Foundation. This program is distributed in the hope *
|
||||||
|
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
||||||
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
||||||
|
* See the GNU General Public License for more details. *
|
||||||
|
* You should have received a copy of the GNU General Public License along *
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||||
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
||||||
|
*****************************************************************************/
|
||||||
|
package org.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.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.compiere.acct.Doc;
|
||||||
|
import org.compiere.acct.DocManager;
|
||||||
|
import org.compiere.model.MAccount;
|
||||||
|
import org.compiere.model.MAcctSchema;
|
||||||
|
import org.compiere.model.MBPartner;
|
||||||
|
import org.compiere.model.MClient;
|
||||||
|
import org.compiere.model.MCost;
|
||||||
|
import org.compiere.model.MFactAcct;
|
||||||
|
import org.compiere.model.MInOut;
|
||||||
|
import org.compiere.model.MInOutConfirm;
|
||||||
|
import org.compiere.model.MInOutLine;
|
||||||
|
import org.compiere.model.MInvoice;
|
||||||
|
import org.compiere.model.MInvoiceLine;
|
||||||
|
import org.compiere.model.MMatchInv;
|
||||||
|
import org.compiere.model.MMatchPO;
|
||||||
|
import org.compiere.model.MOrder;
|
||||||
|
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.MRMA;
|
||||||
|
import org.compiere.model.MRMALine;
|
||||||
|
import org.compiere.model.MStorageOnHand;
|
||||||
|
import org.compiere.model.ProductCost;
|
||||||
|
import org.compiere.model.Query;
|
||||||
|
import org.compiere.model.SystemIDs;
|
||||||
|
import org.compiere.process.DocAction;
|
||||||
|
import org.compiere.process.DocumentEngine;
|
||||||
|
import org.compiere.process.ProcessInfo;
|
||||||
|
import org.compiere.process.ServerProcessCtl;
|
||||||
|
import org.compiere.util.Env;
|
||||||
|
import org.compiere.util.TimeUtil;
|
||||||
|
import org.compiere.util.Util;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases Stocked Expense type product with Average PO Costing Method.
|
||||||
|
*
|
||||||
|
* @author Logilite Technologies
|
||||||
|
* @since September 15, 2023
|
||||||
|
*/
|
||||||
|
@Isolated
|
||||||
|
public class NonStockedExpTypeAvgPOCostingTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
private static final BigDecimal BD_2 = new BigDecimal("2.00");
|
||||||
|
private static final BigDecimal BD_5 = new BigDecimal("5.00");
|
||||||
|
private static final BigDecimal BD_20 = new BigDecimal("20.00");
|
||||||
|
|
||||||
|
public NonStockedExpTypeAvgPOCostingTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaterialReceipt()
|
||||||
|
{
|
||||||
|
//need to create category and product for the every test case because after the first test completed it's rollback
|
||||||
|
//and also need to delete category and product at the end of the each test case otherwise next test case failed
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
|
||||||
|
// Create Material Receipt
|
||||||
|
MInOutLine rLine = createPOAndMRForProduct(product);
|
||||||
|
|
||||||
|
MMatchPO[] matchPOs = MMatchPO.get(Env.getCtx(), rLine.getM_InOutLine_ID(), getTrxName());
|
||||||
|
assertEquals(1, matchPOs.length);
|
||||||
|
assertEquals(rLine.getC_OrderLine_ID(), matchPOs[0].getC_OrderLine_ID());
|
||||||
|
assertEquals(rLine.getM_InOutLine_ID(), matchPOs[0].getM_InOutLine_ID());
|
||||||
|
assertTrue(matchPOs[0].getQty().compareTo(BigDecimal.TEN) == 0);
|
||||||
|
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
//Validate that averagePO costing method should not be created in the cost for the expense type product
|
||||||
|
assertNoAveragePOCost(product.get_ID());
|
||||||
|
|
||||||
|
|
||||||
|
// Testing cost and stock of the product after completing MR
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current Qty");
|
||||||
|
//Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
// Testing Accounting For MR
|
||||||
|
Doc doc = DocManager.getDocument(as, MInOut.Table_ID, rLine.getM_InOut_ID(), getTrxName());
|
||||||
|
doc.setC_BPartner_ID(rLine.getM_InOut().getC_BPartner_ID());
|
||||||
|
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
|
||||||
|
|
||||||
|
// get ProductExpense Of the created MR
|
||||||
|
ProductCost pc = new ProductCost(Env.getCtx(), rLine.getM_Product_ID(), 0, getTrxName());
|
||||||
|
MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
|
||||||
|
StringBuilder whereClause = new StringBuilder();
|
||||||
|
whereClause
|
||||||
|
.append(MFactAcct.COLUMNNAME_AD_Table_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(MInOut.Table_ID)
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_Record_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(rLine.getM_InOut_ID())
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_C_AcctSchema_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(as.getC_AcctSchema_ID());
|
||||||
|
|
||||||
|
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : ids)
|
||||||
|
{
|
||||||
|
// Test Accounting for MR
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSOAndShipment()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
assertNoAveragePOCost(product.get_ID());
|
||||||
|
|
||||||
|
createSOAndShipmentForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// testing cost and stock of the product after completing Shipment
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current Qty");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No storage records should be created for expense type product");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomerRMA()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
assertNoAveragePOCost(product.get_ID());
|
||||||
|
|
||||||
|
|
||||||
|
// Create SO And Shipment
|
||||||
|
MInOutLine sLine = createSOAndShipmentForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Create MRA
|
||||||
|
MRMA rma = new MRMA(Env.getCtx(), 0, getTrxName());
|
||||||
|
rma.setName("testCustomerReturnMaterial");
|
||||||
|
rma.setC_DocType_ID(DictionaryIDs.C_DocType.CUSTOMER_RETURN_MATERIAL.id);
|
||||||
|
rma.setM_RMAType_ID(DictionaryIDs.M_RMAType.DAMAGE_ON_ARRIVAL.id);
|
||||||
|
rma.setM_InOut_ID(sLine.getM_InOut_ID());
|
||||||
|
rma.setIsSOTrx(true);
|
||||||
|
rma.setSalesRep_ID(SystemIDs.USER_SUPERUSER); // SuperUser
|
||||||
|
rma.saveEx();
|
||||||
|
|
||||||
|
// Create MRALine
|
||||||
|
MRMALine rmaLine = new MRMALine(Env.getCtx(), 0, getTrxName());
|
||||||
|
rmaLine.setLine(10);
|
||||||
|
rmaLine.setM_RMA_ID(rma.get_ID());
|
||||||
|
rmaLine.setQty(new BigDecimal("3"));
|
||||||
|
rmaLine.setM_InOutLine_ID(sLine.get_ID());
|
||||||
|
rmaLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(rma, DocAction.ACTION_Complete);
|
||||||
|
rma.load(getTrxName());
|
||||||
|
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, rma.getDocStatus());
|
||||||
|
|
||||||
|
// Create delivery for Customer Return Material
|
||||||
|
MInOut delivery = new MInOut(Env.getCtx(), 0, getTrxName());
|
||||||
|
delivery.setM_RMA_ID(rma.get_ID());
|
||||||
|
delivery.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id));
|
||||||
|
delivery.setC_DocType_ID(DictionaryIDs.C_DocType.MM_CUSTOMER_RETURN.id);
|
||||||
|
delivery.setMovementType(MInOut.MOVEMENTTYPE_CustomerReturns);
|
||||||
|
delivery.setIsSOTrx(true);
|
||||||
|
delivery.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
delivery.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
delivery.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id);
|
||||||
|
delivery.saveEx();
|
||||||
|
|
||||||
|
MInOutLine dLine = new MInOutLine(delivery);
|
||||||
|
dLine.setM_RMALine_ID(rmaLine.get_ID());
|
||||||
|
dLine.setLine(10);
|
||||||
|
dLine.setProduct(new MProduct(Env.getCtx(), product.getM_Product_ID(), getTrxName()));
|
||||||
|
dLine.setM_Locator_ID(sLine.getM_Locator_ID());
|
||||||
|
dLine.setQty(new BigDecimal("3"));
|
||||||
|
dLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(delivery, DocAction.ACTION_Complete);
|
||||||
|
delivery.load(getTrxName());
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
|
||||||
|
// Complete the MInOutConfirm to complete the delivery
|
||||||
|
MInOutConfirm[] confirmations = delivery.getConfirmations(true);
|
||||||
|
assertEquals(1, confirmations.length, "Expected 1 Shipment Confirmation Document");
|
||||||
|
|
||||||
|
assertTrue(confirmations[0].getM_InOutConfirm_ID() != delivery.getM_InOut_ID(), "Test should run with M_InOut_ID != M_InOutConfirm_ID");
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(confirmations[0], DocAction.ACTION_Complete);
|
||||||
|
if (info.isError())
|
||||||
|
{
|
||||||
|
fail("Failed to complete shipment confirmation" + (Util.isEmpty(info.getSummary()) ? "" : " : " + info.getSummary()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmations[0].load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, confirmations[0].getDocStatus(), "Expected Completed Status for Shipment Confirmation");
|
||||||
|
|
||||||
|
// Complete the delivery
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(delivery, DocAction.ACTION_Complete);
|
||||||
|
delivery.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, delivery.getDocStatus(), "Expected Completed Status for Shipment");
|
||||||
|
|
||||||
|
if (!delivery.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate( Env.getCtx(), delivery.getAD_Client_ID(), MInOut.Table_ID,
|
||||||
|
delivery.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// testing cost and stock of the product after Customer return
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost Qty");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvoice()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
|
||||||
|
// Create PO And MR
|
||||||
|
MInOutLine rLine = createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
assertNoAveragePOCost(product.get_ID());
|
||||||
|
|
||||||
|
// Create Invoice from the MR
|
||||||
|
MInOut mInOut = (MInOut) rLine.getM_InOut();
|
||||||
|
MInvoice invoice = new MInvoice(mInOut, rLine.getM_InOut().getMovementDate());
|
||||||
|
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
invoice.saveEx();
|
||||||
|
|
||||||
|
MOrderLine oLine = (MOrderLine) rLine.getC_OrderLine();
|
||||||
|
MInvoiceLine iLine = new MInvoiceLine(invoice);
|
||||||
|
iLine.setOrderLine(oLine);
|
||||||
|
iLine.setLine(10);
|
||||||
|
iLine.setProduct(new MProduct(Env.getCtx(), product.getM_Product_ID(), getTrxName()));
|
||||||
|
iLine.setQty(BigDecimal.TEN);
|
||||||
|
iLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
invoice.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||||
|
invoice.getDocsPostProcess().clear();
|
||||||
|
if (!invoice.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate( Env.getCtx(), invoice.getAD_Client_ID(),
|
||||||
|
invoice.get_Table_ID(), invoice.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
// Testing Accounting for Invoice
|
||||||
|
// get ProductExpense Of the Invoice
|
||||||
|
ProductCost pc = new ProductCost(Env.getCtx(), iLine.getM_Product_ID(), 0, getTrxName());
|
||||||
|
MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
|
||||||
|
// get AccPayable of the Invoice
|
||||||
|
MAccount acctPT = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getV_Liability_Acct(), getTrxName());
|
||||||
|
|
||||||
|
String whereClause = "AD_Table_ID = " + MInvoice.Table_ID;
|
||||||
|
whereClause += " AND Record_ID = " + iLine.getC_Invoice_ID();
|
||||||
|
whereClause += " AND C_AcctSchema_ID = " + as.getC_AcctSchema_ID();
|
||||||
|
|
||||||
|
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : ids)
|
||||||
|
{
|
||||||
|
// Test Accounting for Invoice
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctPT.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing Accounting For MatchInv
|
||||||
|
MMatchInv[] matchInvoices = MMatchInv.getInOut(Env.getCtx(), rLine.getM_InOut_ID(), getTrxName());
|
||||||
|
assertEquals(1, matchInvoices.length);
|
||||||
|
assertEquals(rLine.getM_InOutLine_ID(), matchInvoices[0].getM_InOutLine_ID());
|
||||||
|
assertTrue( matchInvoices[0].getQty().compareTo(BigDecimal.TEN) == 0,
|
||||||
|
"Expected 5, get " + matchInvoices[0].getQty());
|
||||||
|
|
||||||
|
// get NotInvoicedReceipts for the MatchInv
|
||||||
|
Doc doc = DocManager.getDocument(as, MMatchInv.Table_ID, matchInvoices[0].get_ID(), getTrxName());
|
||||||
|
doc.setC_BPartner_ID(matchInvoices[0].getC_InvoiceLine().getC_Invoice().getC_BPartner_ID());
|
||||||
|
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
|
||||||
|
|
||||||
|
// get Product Expense for the MatchInv
|
||||||
|
pc = new ProductCost(Env.getCtx(), matchInvoices[0].getM_Product_ID(), 0, getTrxName());
|
||||||
|
productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
whereClause = null;
|
||||||
|
whereClause = "AD_Table_ID = " + MMatchInv.Table_ID;
|
||||||
|
whereClause += " AND Record_ID = " + matchInvoices[0].get_ID();
|
||||||
|
whereClause += " AND C_AcctSchema_ID = " + as.getC_AcctSchema_ID();
|
||||||
|
|
||||||
|
int[] idsMatchInv = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : idsMatchInv)
|
||||||
|
{
|
||||||
|
// Test Accounting for MatchINV
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpenseProduction()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
MProduct fgProdcut = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
|
||||||
|
// create PO And MR for the expense type product
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
assertNoAveragePOCost(product.get_ID());
|
||||||
|
|
||||||
|
// Create Product in which isBOM Flag true
|
||||||
|
fgProdcut = new MProduct(Env.getCtx(), 0, null);
|
||||||
|
fgProdcut.setName("Test_BOM");
|
||||||
|
fgProdcut.setIsBOM(true);
|
||||||
|
fgProdcut.setIsStocked(true);
|
||||||
|
fgProdcut.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
|
||||||
|
fgProdcut.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id);
|
||||||
|
fgProdcut.setProductType(MProduct.PRODUCTTYPE_Item);
|
||||||
|
fgProdcut.setM_Product_Category_ID(category.get_ID());
|
||||||
|
fgProdcut.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id);
|
||||||
|
fgProdcut.saveEx();
|
||||||
|
|
||||||
|
MCost fgCost = MCost.get(fgProdcut, 0, as, 0, DictionaryIDs.M_CostElement.MATERIAL.id, getTrxName());
|
||||||
|
|
||||||
|
// create Product BOM
|
||||||
|
MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName());
|
||||||
|
bom.setM_Product_ID(fgProdcut.get_ID());
|
||||||
|
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive);
|
||||||
|
bom.setBOMUse(MPPProductBOM.BOMUSE_Master);
|
||||||
|
bom.setName(fgProdcut.getName());
|
||||||
|
bom.saveEx();
|
||||||
|
|
||||||
|
// Add expense type product in the ProductBOMLine
|
||||||
|
MPPProductBOMLine bomLine = new MPPProductBOMLine(bom);
|
||||||
|
bomLine.setM_Product_ID(product.getM_Product_ID());
|
||||||
|
bomLine.setQtyBOM(BD_2);
|
||||||
|
bomLine.saveEx();
|
||||||
|
|
||||||
|
// Create production for the BOM product
|
||||||
|
MProduction production = new MProduction(Env.getCtx(), 0, getTrxName());
|
||||||
|
production.setM_Product_ID(fgProdcut.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(BD_2);
|
||||||
|
production.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID());
|
||||||
|
production.saveEx();
|
||||||
|
|
||||||
|
// Create ProductionLine for the BOM Product
|
||||||
|
MProcess process = MProcess.get(Env.getCtx(), 53226); // CreateProductionAndCreateLines
|
||||||
|
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(2, 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());
|
||||||
|
|
||||||
|
fgCost.load(getTrxName());
|
||||||
|
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Test the Cost and stock for the Expense type's Product
|
||||||
|
assertEquals(new BigDecimal("6.00"), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No stocked should be used for expense type product Usage in Poroduction");
|
||||||
|
|
||||||
|
|
||||||
|
// test Stock and cost of the Product in which BOM is created
|
||||||
|
assertEquals(BD_2, fgCost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fgCost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
// Assert that OnHand Qty for the Finished goods of item type is created
|
||||||
|
BigDecimal onHand = MStorageOnHand.getQtyOnHand(fgProdcut.get_ID(),DictionaryIDs.M_Warehouse.HQ.id, 0, getTrxName());
|
||||||
|
assertEquals(BD_2, onHand.setScale(2, RoundingMode.HALF_UP), "Storge for the Expense Type Product Should not be Created");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (fgProdcut != null)
|
||||||
|
fgProdcut.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Expense Type Product
|
||||||
|
*
|
||||||
|
* @param category_ID Standard Costing
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MProduct createProduct(int category_ID)
|
||||||
|
{
|
||||||
|
MProduct product = new MProduct(Env.getCtx(), 0, null);
|
||||||
|
product.setName("testStandardCosting");
|
||||||
|
product.setValue("testStandardCosting");
|
||||||
|
product.setProductType(MProduct.PRODUCTTYPE_ExpenseType);
|
||||||
|
product.setIsStocked(false);
|
||||||
|
product.setIsSold(true);
|
||||||
|
product.setIsPurchased(true);
|
||||||
|
product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
|
||||||
|
product.setM_Product_Category_ID(category_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.saveEx();
|
||||||
|
|
||||||
|
MPriceListVersion plvs = MPriceList.get(DictionaryIDs.M_PriceList.STANDARD.id).getPriceListVersion(null);
|
||||||
|
MProductPrice pps = new MProductPrice(Env.getCtx(), 0, getTrxName());
|
||||||
|
pps.setM_PriceList_Version_ID(plvs.getM_PriceList_Version_ID());
|
||||||
|
pps.setM_Product_ID(product.get_ID());
|
||||||
|
pps.saveEx();
|
||||||
|
|
||||||
|
return product;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create PO And MR for the Expense Type Product
|
||||||
|
*
|
||||||
|
* @param product
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MInOutLine createPOAndMRForProduct(MProduct product)
|
||||||
|
{
|
||||||
|
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 oLine = new MOrderLine(order);
|
||||||
|
oLine.setLine(10);
|
||||||
|
oLine.setProduct(product);
|
||||||
|
oLine.setQty(BigDecimal.TEN);
|
||||||
|
oLine.setDatePromised(today);
|
||||||
|
oLine.setPrice(BD_2);
|
||||||
|
oLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
order.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||||
|
|
||||||
|
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered());
|
||||||
|
receipt.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
receipt.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
receipt.saveEx();
|
||||||
|
|
||||||
|
MInOutLine rLine = new MInOutLine(receipt);
|
||||||
|
rLine.setOrderLine(oLine, 0, BigDecimal.TEN);
|
||||||
|
rLine.setQty(BigDecimal.TEN);
|
||||||
|
rLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
receipt.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||||
|
if (!receipt.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate( Env.getCtx(), receipt.getAD_Client_ID(), receipt.get_Table_ID(),
|
||||||
|
receipt.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create SO And Shipment for the Expense Type Product
|
||||||
|
*
|
||||||
|
* @param product
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MInOutLine createSOAndShipmentForProduct(MProduct product)
|
||||||
|
{
|
||||||
|
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.STANDARD_ORDER.id);
|
||||||
|
order.setIsSOTrx(true);
|
||||||
|
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 oLine = new MOrderLine(order);
|
||||||
|
oLine.setLine(10);
|
||||||
|
oLine.setProduct(product);
|
||||||
|
oLine.setQty(BD_5);
|
||||||
|
oLine.setDatePromised(today);
|
||||||
|
oLine.setPrice(BD_2);
|
||||||
|
oLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
order.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||||
|
|
||||||
|
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_SHIPMENT.id, order.getDateOrdered());
|
||||||
|
receipt.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
receipt.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
receipt.setIsSOTrx(true);
|
||||||
|
receipt.saveEx();
|
||||||
|
|
||||||
|
MInOutLine rLine = new MInOutLine(receipt);
|
||||||
|
rLine.setOrderLine(oLine, 0, BD_5);
|
||||||
|
rLine.setQty(BD_5);
|
||||||
|
rLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
receipt.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||||
|
|
||||||
|
if (!receipt.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate( Env.getCtx(), receipt.getAD_Client_ID(), receipt.get_Table_ID(),
|
||||||
|
receipt.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Accounting Schema
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MAcctSchema getAccountingSchema()
|
||||||
|
{
|
||||||
|
MClient client = MClient.get(Env.getCtx());
|
||||||
|
MAcctSchema as = client.getAcctSchema();
|
||||||
|
return as;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MCost getCost(MAcctSchema as, MProduct product)
|
||||||
|
{
|
||||||
|
return MCost.get(product, 0, as, 0, DictionaryIDs.M_CostElement.MATERIAL.id, getTrxName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Product Category "Standard Costing"
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MProductCategory createProductCategory()
|
||||||
|
{
|
||||||
|
MProductCategory category = new MProductCategory(Env.getCtx(), 0, null);
|
||||||
|
category.setName("Average PO");
|
||||||
|
category.saveEx();
|
||||||
|
String whereClause = "M_Product_Category_ID=?";
|
||||||
|
List<MProductCategoryAcct> categoryAccts = new Query(Env.getCtx(), MProductCategoryAcct.Table_Name, whereClause, null)
|
||||||
|
.setParameters(category.get_ID())
|
||||||
|
.list();
|
||||||
|
for (MProductCategoryAcct categoryAcct : categoryAccts)
|
||||||
|
{
|
||||||
|
categoryAcct.setCostingMethod(MAcctSchema.COSTINGMETHOD_AveragePO);
|
||||||
|
categoryAcct.saveEx();
|
||||||
|
}
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNoAveragePOCost(int productID)
|
||||||
|
{
|
||||||
|
List<MCost> costs = new Query(Env.getCtx(), MCost.Table_Name, " M_Product_ID = ? ", null)
|
||||||
|
.setParameters(productID)
|
||||||
|
.list();
|
||||||
|
for (MCost cost : costs)
|
||||||
|
{
|
||||||
|
assertFalse(DictionaryIDs.M_CostElement.AVERAGE_PO.id == cost.getM_CostElement_ID(),"Average PO cost should not be created for Expense type product");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,774 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Copyright (C) 2016 Logilite Technologies LLP *
|
||||||
|
* This program is free software; you can redistribute it and/or modify it *
|
||||||
|
* under the terms version 2 of the GNU General Public License as published *
|
||||||
|
* by the Free Software Foundation. This program is distributed in the hope *
|
||||||
|
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
|
||||||
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
||||||
|
* See the GNU General Public License for more details. *
|
||||||
|
* You should have received a copy of the GNU General Public License along *
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||||
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
||||||
|
*****************************************************************************/
|
||||||
|
package org.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 static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.compiere.acct.Doc;
|
||||||
|
import org.compiere.acct.DocManager;
|
||||||
|
import org.compiere.model.MAccount;
|
||||||
|
import org.compiere.model.MAcctSchema;
|
||||||
|
import org.compiere.model.MBPartner;
|
||||||
|
import org.compiere.model.MClient;
|
||||||
|
import org.compiere.model.MCost;
|
||||||
|
import org.compiere.model.MFactAcct;
|
||||||
|
import org.compiere.model.MInOut;
|
||||||
|
import org.compiere.model.MInOutConfirm;
|
||||||
|
import org.compiere.model.MInOutLine;
|
||||||
|
import org.compiere.model.MInvoice;
|
||||||
|
import org.compiere.model.MInvoiceLine;
|
||||||
|
import org.compiere.model.MMatchInv;
|
||||||
|
import org.compiere.model.MMatchPO;
|
||||||
|
import org.compiere.model.MOrder;
|
||||||
|
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.MRMA;
|
||||||
|
import org.compiere.model.MRMALine;
|
||||||
|
import org.compiere.model.MStorageOnHand;
|
||||||
|
import org.compiere.model.ProductCost;
|
||||||
|
import org.compiere.model.Query;
|
||||||
|
import org.compiere.model.SystemIDs;
|
||||||
|
import org.compiere.process.DocAction;
|
||||||
|
import org.compiere.process.DocumentEngine;
|
||||||
|
import org.compiere.process.ProcessInfo;
|
||||||
|
import org.compiere.process.ServerProcessCtl;
|
||||||
|
import org.compiere.util.Env;
|
||||||
|
import org.compiere.util.TimeUtil;
|
||||||
|
import org.compiere.util.Util;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases Stocked Expense type product with Standard PO Costing Method.
|
||||||
|
*
|
||||||
|
* @author Logilite Technologies
|
||||||
|
* @since September 15, 2023
|
||||||
|
*/
|
||||||
|
@Isolated
|
||||||
|
public class NonStockedExpTypeStdCostingTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
private static final BigDecimal BD_2 = new BigDecimal("2.00");
|
||||||
|
private static final BigDecimal BD_5 = new BigDecimal("5.00");
|
||||||
|
private static final BigDecimal BD_20 = new BigDecimal("20.00");
|
||||||
|
|
||||||
|
public NonStockedExpTypeStdCostingTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaterialReceipt()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
assertNotNull(cost, "No MCost record found");
|
||||||
|
|
||||||
|
// Create Material Receipt
|
||||||
|
MInOutLine rLine = createPOAndMRForProduct(product);
|
||||||
|
//MatchPO
|
||||||
|
MMatchPO[] matchPOs = MMatchPO.get(Env.getCtx(), rLine.getM_InOutLine_ID(), getTrxName());
|
||||||
|
assertEquals(1, matchPOs.length);
|
||||||
|
assertEquals(rLine.getC_OrderLine_ID(), matchPOs[0].getC_OrderLine_ID());
|
||||||
|
assertEquals(rLine.getM_InOutLine_ID(), matchPOs[0].getM_InOutLine_ID());
|
||||||
|
assertTrue(matchPOs[0].getQty().compareTo(BigDecimal.TEN) == 0);
|
||||||
|
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
|
||||||
|
// testing cost and stock of the product after completing MR
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current Qty");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
// Test Acc. For MR
|
||||||
|
// get NotInvoicedReceipts Of the created MR
|
||||||
|
Doc doc = DocManager.getDocument(as, MInOut.Table_ID, rLine.getM_InOut_ID(), getTrxName());
|
||||||
|
doc.setC_BPartner_ID(rLine.getM_InOut().getC_BPartner_ID());
|
||||||
|
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
|
||||||
|
|
||||||
|
// get ProductExpense Of the created MR
|
||||||
|
ProductCost pc = new ProductCost(Env.getCtx(), rLine.getM_Product_ID(), 0, getTrxName());
|
||||||
|
MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
|
||||||
|
StringBuilder whereClause = new StringBuilder();
|
||||||
|
whereClause
|
||||||
|
.append(MFactAcct.COLUMNNAME_AD_Table_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(MInOut.Table_ID)
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_Record_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(rLine.getM_InOut_ID())
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_C_AcctSchema_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(as.getC_AcctSchema_ID());
|
||||||
|
|
||||||
|
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : ids)
|
||||||
|
{
|
||||||
|
// Test Accounting for MR
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSOAndShipment()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
assertNotNull(cost, "No MCost record found");
|
||||||
|
|
||||||
|
// Create PO And MR
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Create SO And Shipment
|
||||||
|
createSOAndShipmentForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// testing cost and stock of the product after completing Shipment
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
assertEquals(BD_5, cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current Qty");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0,storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomerRMA()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
assertNotNull(cost, "No MCost record found");
|
||||||
|
// Create PO And MR
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Create SO And Shipment
|
||||||
|
MInOutLine sLine = createSOAndShipmentForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Create MRA
|
||||||
|
MRMA rma = new MRMA(Env.getCtx(), 0, getTrxName());
|
||||||
|
rma.setName("testCustomerReturnMaterial");
|
||||||
|
rma.setC_DocType_ID(DictionaryIDs.C_DocType.CUSTOMER_RETURN_MATERIAL.id);
|
||||||
|
rma.setM_RMAType_ID(DictionaryIDs.M_RMAType.DAMAGE_ON_ARRIVAL.id);
|
||||||
|
rma.setM_InOut_ID(sLine.getM_InOut_ID());
|
||||||
|
rma.setIsSOTrx(true);
|
||||||
|
rma.setSalesRep_ID(SystemIDs.USER_SUPERUSER);
|
||||||
|
rma.saveEx();
|
||||||
|
|
||||||
|
// Create MRALine
|
||||||
|
MRMALine rmaLine = new MRMALine(Env.getCtx(), 0, getTrxName());
|
||||||
|
rmaLine.setLine(10);
|
||||||
|
rmaLine.setM_RMA_ID(rma.get_ID());
|
||||||
|
rmaLine.setQty(new BigDecimal("3"));
|
||||||
|
rmaLine.setM_InOutLine_ID(sLine.get_ID());
|
||||||
|
rmaLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(rma, DocAction.ACTION_Complete);
|
||||||
|
rma.load(getTrxName());
|
||||||
|
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, rma.getDocStatus());
|
||||||
|
|
||||||
|
// Create shipment for Customer Return Material
|
||||||
|
MInOut delivery = new MInOut(Env.getCtx(), 0, getTrxName());
|
||||||
|
delivery.setM_RMA_ID(rma.get_ID());
|
||||||
|
delivery.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id));
|
||||||
|
delivery.setC_DocType_ID(DictionaryIDs.C_DocType.MM_CUSTOMER_RETURN.id);
|
||||||
|
delivery.setMovementType(MInOut.MOVEMENTTYPE_CustomerReturns);
|
||||||
|
delivery.setIsSOTrx(true);
|
||||||
|
delivery.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
delivery.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
delivery.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id);
|
||||||
|
delivery.saveEx();
|
||||||
|
|
||||||
|
MInOutLine dLine = new MInOutLine(delivery);
|
||||||
|
dLine.setM_RMALine_ID(rmaLine.get_ID());
|
||||||
|
dLine.setLine(10);
|
||||||
|
dLine.setProduct(new MProduct(Env.getCtx(), product.getM_Product_ID(), getTrxName()));
|
||||||
|
dLine.setM_Locator_ID(sLine.getM_Locator_ID());
|
||||||
|
dLine.setQty(new BigDecimal("3"));
|
||||||
|
dLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(delivery, DocAction.ACTION_Complete);
|
||||||
|
delivery.load(getTrxName());
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
|
||||||
|
// Need to complete the InoutConfirmation to complete the return
|
||||||
|
// material
|
||||||
|
MInOutConfirm[] confirmations = delivery.getConfirmations(true);
|
||||||
|
assertEquals(1, confirmations.length, "Expected 1 Shipment Confirmation Document");
|
||||||
|
|
||||||
|
assertTrue(confirmations[0].getM_InOutConfirm_ID() != delivery.getM_InOut_ID(), "Test should run with M_InOut_ID != M_InOutConfirm_ID");
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(confirmations[0], DocAction.ACTION_Complete);
|
||||||
|
if (info.isError())
|
||||||
|
{
|
||||||
|
fail("Failed to complete shipment confirmation" + (Util.isEmpty(info.getSummary()) ? "" : " : " + info.getSummary()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmations[0].load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, confirmations[0].getDocStatus(), "Expected Completed Status for Shipment Confirmation");
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(delivery, DocAction.ACTION_Complete);
|
||||||
|
delivery.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, delivery.getDocStatus(), "Expected Completed Status for Shipment");
|
||||||
|
|
||||||
|
if (!delivery.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate(Env.getCtx(), delivery.getAD_Client_ID(), MInOut.Table_ID, delivery.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Complete the MInOutConfirm to complete the delivery
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price");
|
||||||
|
assertEquals(new BigDecimal("8.00"), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost Qty");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0, storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvoice()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
assertNotNull(cost, "No MCost record found");
|
||||||
|
// Create PO And MR
|
||||||
|
MInOutLine rLine = createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Create Invoice from the MR
|
||||||
|
MInOut mInOut = (MInOut) rLine.getM_InOut();
|
||||||
|
MInvoice invoice = new MInvoice(mInOut, rLine.getM_InOut().getMovementDate());
|
||||||
|
invoice.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
invoice.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
invoice.saveEx();
|
||||||
|
|
||||||
|
MOrderLine oLine = (MOrderLine) rLine.getC_OrderLine();
|
||||||
|
MInvoiceLine iLine = new MInvoiceLine(invoice);
|
||||||
|
iLine.setOrderLine(oLine);
|
||||||
|
iLine.setLine(10);
|
||||||
|
iLine.setProduct(new MProduct(Env.getCtx(), product, getTrxName()));
|
||||||
|
iLine.setQty(BigDecimal.TEN);
|
||||||
|
iLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
invoice.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus());
|
||||||
|
invoice.getDocsPostProcess().clear();
|
||||||
|
if (!invoice.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate( Env.getCtx(), invoice.getAD_Client_ID(),
|
||||||
|
invoice.get_Table_ID(), invoice.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get ProductExpense Of the Invoice
|
||||||
|
ProductCost pc = new ProductCost(Env.getCtx(), iLine.getM_Product_ID(), 0, getTrxName());
|
||||||
|
MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
|
||||||
|
// get AccPayable of the Invoice
|
||||||
|
MAccount acctPT = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getV_Liability_Acct(), getTrxName());
|
||||||
|
|
||||||
|
StringBuilder whereClause = new StringBuilder();
|
||||||
|
|
||||||
|
whereClause
|
||||||
|
.append(MFactAcct.COLUMNNAME_AD_Table_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(MInvoice.Table_ID)
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_Record_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(iLine.getC_Invoice_ID())
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_C_AcctSchema_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(as.getC_AcctSchema_ID());
|
||||||
|
|
||||||
|
int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : ids)
|
||||||
|
{
|
||||||
|
// Test Accounting for Invoice
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctPT.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Accounting MatchInv
|
||||||
|
MMatchInv[] matchInvoices = MMatchInv.getInOut(Env.getCtx(), rLine.getM_InOut_ID(), getTrxName());
|
||||||
|
assertEquals(1, matchInvoices.length);
|
||||||
|
assertEquals(rLine.getM_InOutLine_ID(), matchInvoices[0].getM_InOutLine_ID());
|
||||||
|
assertTrue(matchInvoices[0].getQty().compareTo(BigDecimal.TEN) == 0,
|
||||||
|
"Expected 5, get " + matchInvoices[0].getQty());
|
||||||
|
|
||||||
|
// get NotInvoicedReceipts for the MatchInv
|
||||||
|
Doc doc = DocManager.getDocument(as, MMatchInv.Table_ID, matchInvoices[0].get_ID(), getTrxName());
|
||||||
|
doc.setC_BPartner_ID(matchInvoices[0].getC_InvoiceLine().getC_Invoice().getC_BPartner_ID());
|
||||||
|
MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as);
|
||||||
|
|
||||||
|
// get Product Expense for the MatchInv
|
||||||
|
pc = new ProductCost(Env.getCtx(), matchInvoices[0].getM_Product_ID(), 0, getTrxName());
|
||||||
|
productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as);
|
||||||
|
|
||||||
|
whereClause = null;
|
||||||
|
whereClause = new StringBuilder();
|
||||||
|
whereClause
|
||||||
|
.append(MFactAcct.COLUMNNAME_AD_Table_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(MMatchInv.Table_ID)
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_Record_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(matchInvoices[0].get_ID())
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MFactAcct.COLUMNNAME_C_AcctSchema_ID)
|
||||||
|
.append("=")
|
||||||
|
.append(as.getC_AcctSchema_ID());
|
||||||
|
|
||||||
|
int[] idsMatchInv = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName());
|
||||||
|
|
||||||
|
for (int id : idsMatchInv)
|
||||||
|
{
|
||||||
|
// Test Accounting for MatchINV
|
||||||
|
MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName());
|
||||||
|
if (fa.getAccount_ID() == acctNIR.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
else if (fa.getAccount_ID() == productExpense.getAccount_ID())
|
||||||
|
{
|
||||||
|
assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpenseProductInBOM()
|
||||||
|
{
|
||||||
|
MProductCategory category = createProductCategory();
|
||||||
|
MProduct product = createProduct(category.get_ID());
|
||||||
|
MProduct fgProdcut = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MAcctSchema as = getAccountingSchema();
|
||||||
|
MCost cost = getCost(as, product);
|
||||||
|
assertNotNull(cost, "No MCost record found");
|
||||||
|
|
||||||
|
createPOAndMRForProduct(product);
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// create product in which BOM is true
|
||||||
|
fgProdcut = new MProduct(Env.getCtx(), 0, null);
|
||||||
|
fgProdcut.setName("Test_BOM");
|
||||||
|
fgProdcut.setIsBOM(true);
|
||||||
|
fgProdcut.setIsStocked(true);
|
||||||
|
fgProdcut.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
|
||||||
|
fgProdcut.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id);
|
||||||
|
fgProdcut.setProductType(MProduct.PRODUCTTYPE_Item);
|
||||||
|
fgProdcut.setM_Product_Category_ID(category.get_ID());
|
||||||
|
fgProdcut.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id);
|
||||||
|
fgProdcut.saveEx();
|
||||||
|
|
||||||
|
MCost fgCost = MCost.get(fgProdcut, 0, as, 0, DictionaryIDs.M_CostElement.MATERIAL.id, getTrxName());
|
||||||
|
|
||||||
|
// create productBOM
|
||||||
|
MPPProductBOM bom = new MPPProductBOM(Env.getCtx(), 0, getTrxName());
|
||||||
|
bom.setM_Product_ID(fgProdcut.get_ID());
|
||||||
|
bom.setBOMType(MPPProductBOM.BOMTYPE_CurrentActive);
|
||||||
|
bom.setBOMUse(MPPProductBOM.BOMUSE_Master);
|
||||||
|
bom.setName(fgProdcut.getName());
|
||||||
|
bom.saveEx();
|
||||||
|
|
||||||
|
// Add expense type product in the BOM Line
|
||||||
|
MPPProductBOMLine bomLine = new MPPProductBOMLine(bom);
|
||||||
|
bomLine.setM_Product_ID(product.getM_Product_ID());
|
||||||
|
bomLine.setQtyBOM(BD_2);
|
||||||
|
bomLine.saveEx();
|
||||||
|
|
||||||
|
// create production for the BOM Product
|
||||||
|
MProduction production = new MProduction(Env.getCtx(), 0, getTrxName());
|
||||||
|
production.setM_Product_ID(fgProdcut.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(BD_2);
|
||||||
|
production.setPP_Product_BOM_ID(bom.getPP_Product_BOM_ID());
|
||||||
|
production.saveEx();
|
||||||
|
|
||||||
|
// create production lines from the process
|
||||||
|
MProcess process = MProcess.get(Env.getCtx(), 53226);// CreateProductionAndCreateLines
|
||||||
|
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(2, 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());
|
||||||
|
fgCost.load(getTrxName());
|
||||||
|
|
||||||
|
cost.load(getTrxName());
|
||||||
|
|
||||||
|
// Test the Cost and stock for the Expense type's Product
|
||||||
|
assertEquals(new BigDecimal("6.00"), cost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BD_2, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
// Assert no storage records created for expense type product
|
||||||
|
MStorageOnHand[] storages = MStorageOnHand.getAll( Env.getCtx(), product.get_ID(),
|
||||||
|
DictionaryIDs.M_Locator.HQ.id, getTrxName(), false, 0);
|
||||||
|
assertEquals(0, storages.length,"No storage records should be created for expense type product");
|
||||||
|
|
||||||
|
// test Stock and cost of the Product in which BOM is created
|
||||||
|
assertEquals(BD_2, fgCost.getCurrentQty().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fgCost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "");
|
||||||
|
// Assert that OnHand Qty for the item type finished good Product created
|
||||||
|
BigDecimal onHand = MStorageOnHand.getQtyOnHand(fgProdcut.get_ID(),DictionaryIDs.M_Warehouse.HQ.id, 0, getTrxName());
|
||||||
|
assertEquals(BD_2, onHand.setScale(2, RoundingMode.HALF_UP), "Storge for the Item Type Product Should not be Created");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rollback();
|
||||||
|
if (product != null)
|
||||||
|
product.deleteEx(true);
|
||||||
|
if (fgProdcut != null)
|
||||||
|
fgProdcut.deleteEx(true);
|
||||||
|
if (category != null)
|
||||||
|
category.deleteEx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Expense Type Product
|
||||||
|
*
|
||||||
|
* @param category_ID Standard Costing
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MProduct createProduct(int category_ID)
|
||||||
|
{
|
||||||
|
MProduct product = new MProduct(Env.getCtx(), 0, null);
|
||||||
|
product.setName("testStandardCosting");
|
||||||
|
product.setValue("testStandardCosting");
|
||||||
|
product.setProductType(MProduct.PRODUCTTYPE_ExpenseType);
|
||||||
|
product.setIsStocked(false);
|
||||||
|
product.setIsSold(true);
|
||||||
|
product.setIsPurchased(true);
|
||||||
|
product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id);
|
||||||
|
product.setM_Product_Category_ID(category_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.saveEx();
|
||||||
|
|
||||||
|
MPriceListVersion plvs = MPriceList.get(DictionaryIDs.M_PriceList.STANDARD.id).getPriceListVersion(null);
|
||||||
|
MProductPrice pps = new MProductPrice(Env.getCtx(), 0, getTrxName());
|
||||||
|
pps.setM_PriceList_Version_ID(plvs.getM_PriceList_Version_ID());
|
||||||
|
pps.setM_Product_ID(product.get_ID());
|
||||||
|
pps.saveEx();
|
||||||
|
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create PO And MR for the expense type product
|
||||||
|
*
|
||||||
|
* @param product
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MInOutLine createPOAndMRForProduct(MProduct product)
|
||||||
|
{
|
||||||
|
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 oLine = new MOrderLine(order);
|
||||||
|
oLine.setLine(10);
|
||||||
|
oLine.setProduct(product);
|
||||||
|
oLine.setQty(BigDecimal.TEN);
|
||||||
|
oLine.setDatePromised(today);
|
||||||
|
oLine.setPrice(BD_2);
|
||||||
|
oLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
order.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||||
|
|
||||||
|
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered());
|
||||||
|
receipt.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
receipt.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
receipt.saveEx();
|
||||||
|
|
||||||
|
MInOutLine rLine = new MInOutLine(receipt);
|
||||||
|
rLine.setOrderLine(oLine, 0, BigDecimal.TEN);
|
||||||
|
rLine.setQty(BigDecimal.TEN);
|
||||||
|
rLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
receipt.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||||
|
if (!receipt.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), receipt.get_Table_ID(),
|
||||||
|
receipt.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create SO And Shipment for the Expense type Product
|
||||||
|
*
|
||||||
|
* @param product
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MInOutLine createSOAndShipmentForProduct(MProduct product)
|
||||||
|
{
|
||||||
|
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.STANDARD_ORDER.id);
|
||||||
|
order.setIsSOTrx(true);
|
||||||
|
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 oLine = new MOrderLine(order);
|
||||||
|
oLine.setLine(10);
|
||||||
|
oLine.setProduct(product);
|
||||||
|
oLine.setQty(BD_5);
|
||||||
|
oLine.setDatePromised(today);
|
||||||
|
oLine.setPrice(BD_2);
|
||||||
|
oLine.saveEx();
|
||||||
|
|
||||||
|
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
order.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
|
||||||
|
|
||||||
|
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_SHIPMENT.id, order.getDateOrdered());
|
||||||
|
receipt.setDocStatus(DocAction.STATUS_Drafted);
|
||||||
|
receipt.setDocAction(DocAction.ACTION_Complete);
|
||||||
|
receipt.setIsSOTrx(true);
|
||||||
|
receipt.saveEx();
|
||||||
|
|
||||||
|
MInOutLine rLine = new MInOutLine(receipt);
|
||||||
|
rLine.setOrderLine(oLine, 0, BD_5);
|
||||||
|
rLine.setQty(BD_5);
|
||||||
|
rLine.saveEx();
|
||||||
|
|
||||||
|
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
|
||||||
|
assertFalse(info.isError(), info.getSummary());
|
||||||
|
receipt.load(getTrxName());
|
||||||
|
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
|
||||||
|
|
||||||
|
if (!receipt.isPosted())
|
||||||
|
{
|
||||||
|
String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), receipt.get_Table_ID(),
|
||||||
|
receipt.get_ID(), false, getTrxName());
|
||||||
|
assertNull(error, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MCost getCost(MAcctSchema as, MProduct product)
|
||||||
|
{
|
||||||
|
return product.getCostingRecord(as, getAD_Org_ID(), 0, MAcctSchema.COSTINGMETHOD_StandardCosting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Accounting Schema
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MAcctSchema getAccountingSchema()
|
||||||
|
{
|
||||||
|
MClient client = MClient.get(Env.getCtx());
|
||||||
|
MAcctSchema as = client.getAcctSchema();
|
||||||
|
return as;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Product Category "Standard Costing"
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private MProductCategory createProductCategory()
|
||||||
|
{
|
||||||
|
MProductCategory category = new MProductCategory(Env.getCtx(), 0, null);
|
||||||
|
category.setName("Standard Costing");
|
||||||
|
category.saveEx();
|
||||||
|
|
||||||
|
String whereClause = "M_Product_Category_ID=?";
|
||||||
|
List<MProductCategoryAcct> categoryAccts = new Query(Env.getCtx(), MProductCategoryAcct.Table_Name, whereClause, null)
|
||||||
|
.setParameters(category.get_ID())
|
||||||
|
.list();
|
||||||
|
for (MProductCategoryAcct categoryAcct : categoryAccts)
|
||||||
|
{
|
||||||
|
categoryAcct.setCostingMethod(MAcctSchema.COSTINGMETHOD_StandardCosting);
|
||||||
|
categoryAcct.saveEx();
|
||||||
|
}
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue