diff --git a/org.adempiere.base/src/org/compiere/model/MMatchPO.java b/org.adempiere.base/src/org/compiere/model/MMatchPO.java index 4ebaeba4c9..fe8c721b3e 100644 --- a/org.adempiere.base/src/org/compiere/model/MMatchPO.java +++ b/org.adempiere.base/src/org/compiere/model/MMatchPO.java @@ -1117,16 +1117,24 @@ public class MMatchPO extends X_M_MatchPO if (validateOrderedQty) { MOrderLine line = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName()); + BigDecimal qtyOrdered = line.getQtyOrdered(); BigDecimal invoicedQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE C_InvoiceLine_ID > 0 and C_OrderLine_ID=? AND Reversal_ID IS NULL" , getC_OrderLine_ID()); - if (invoicedQty != null && invoicedQty.compareTo(line.getQtyOrdered()) > 0) + if ( invoicedQty != null + && ( (qtyOrdered.signum() > 0 && invoicedQty.compareTo(qtyOrdered) > 0) + || (qtyOrdered.signum() < 0 && invoicedQty.compareTo(qtyOrdered) < 0) + ) + ) { - throw new IllegalStateException("Total matched invoiced qty > ordered qty. MatchedInvoicedQty="+invoicedQty+", OrderedQty="+line.getQtyOrdered()+", Line="+line); + throw new IllegalStateException("Total matched invoiced qty > ordered qty. MatchedInvoicedQty="+invoicedQty+", OrderedQty="+qtyOrdered+", Line="+line); } - BigDecimal deliveredQty = DB.getSQLValueBD(get_TrxName(), "SELECT Coalesce(SUM(Qty),0) FROM M_MatchPO WHERE M_InOutLine_ID > 0 and C_OrderLine_ID=? AND Reversal_ID IS NULL" , getC_OrderLine_ID()); - if (deliveredQty != null && deliveredQty.compareTo(line.getQtyOrdered()) > 0) + if ( deliveredQty != null + && ( (qtyOrdered.signum() > 0 && deliveredQty.compareTo(qtyOrdered) > 0) + || (qtyOrdered.signum() < 0 && deliveredQty.compareTo(qtyOrdered) < 0) + ) + ) { - throw new IllegalStateException("Total matched delivered qty > ordered qty. MatchedDeliveredQty="+deliveredQty+", OrderedQty="+line.getQtyOrdered()+", Line="+line); + throw new IllegalStateException("Total matched delivered qty > ordered qty. MatchedDeliveredQty="+deliveredQty+", OrderedQty="+qtyOrdered+", Line="+line); } } } diff --git a/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java b/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java new file mode 100644 index 0000000000..d88a65c230 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/PurchaseOrderTest.java @@ -0,0 +1,116 @@ +/*********************************************************************** + * 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: * + * - Carlos Ruiz - globalqss * + **********************************************************************/ +package org.idempiere.test.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.Properties; + +import org.compiere.model.MBPartner; +import org.compiere.model.MInOut; +import org.compiere.model.MInOutLine; +import org.compiere.model.MOrder; +import org.compiere.model.MOrderLine; +import org.compiere.model.MProduct; +import org.compiere.process.DocAction; +import org.compiere.process.ProcessInfo; +import org.compiere.util.Env; +import org.compiere.util.TimeUtil; +import org.compiere.wf.MWorkflow; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * @author Carlos Ruiz - globalqss + */ +public class PurchaseOrderTest extends AbstractTestCase { + + public PurchaseOrderTest() { + } + + private static final int BP_PATIO = 121; + private static final int DOCTYPE_PO = 126; + private static final int DOCTYPE_RECEIPT = 122; + private static final int PRODUCT_SEEDER = 143; + private static final int USER_GARDENADMIN = 101; + + /** + * https://idempiere.atlassian.net/browse/IDEMPIERE-4575 + */ + @Test + public void testQtyReservedForNegativeOrderAndReceipt() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + MOrder order = new MOrder(ctx, 0, trxName); + order.setBPartner(MBPartner.get(ctx, BP_PATIO)); + order.setC_DocTypeTarget_ID(DOCTYPE_PO); + order.setIsSOTrx(false); + order.setSalesRep_ID(USER_GARDENADMIN); + 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(MProduct.get(ctx, PRODUCT_SEEDER)); + line1.setQty(new BigDecimal("-1")); + line1.setDatePromised(today); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError()); + order.load(trxName); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().intValue()); + + MInOut receipt = new MInOut(order, DOCTYPE_RECEIPT, order.getDateOrdered()); + receipt.setDocStatus(DocAction.STATUS_Drafted); + receipt.setDocAction(DocAction.ACTION_Complete); + receipt.saveEx(); + + // negative receipt + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setOrderLine(line1, 0, new BigDecimal("-1")); + receiptLine.setQty(new BigDecimal("-1")); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + assertFalse(info.isError()); + receipt.load(trxName); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + line1.load(trxName); + assertEquals(0, line1.getQtyReserved().intValue()); + } + +}