IDEMPIERE-5812 Reversal of Material Receipt for a Closed PO leave quantity ordered of product in a bad state (#1964)

This commit is contained in:
hengsin 2023-08-08 20:02:28 +08:00 committed by GitHub
parent 472032fa44
commit e46d57e1ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 111 additions and 8 deletions

View File

@ -1643,8 +1643,8 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
if (log.isLoggable(Level.FINE)) log.fine("OrderLine - Reserved=" + oLine.getQtyReserved() if (log.isLoggable(Level.FINE)) log.fine("OrderLine - Reserved=" + oLine.getQtyReserved()
+ ", Delivered=" + oLine.getQtyDelivered()); + ", Delivered=" + oLine.getQtyDelivered());
} }
boolean orderClosed = oLine != null && DocAction.STATUS_Closed.equals(oLine.getParent().getDocStatus());
// Load RMA Line // Load RMA Line
MRMALine rmaLine = null; MRMALine rmaLine = null;
@ -1726,7 +1726,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
return status; return status;
} }
// Update Storage - see also VMatch.createMatchRecord // Update Storage - see also Match.createMatchRecord
if (!MStorageOnHand.add(getCtx(), if (!MStorageOnHand.add(getCtx(),
sLine.getM_Locator_ID(), sLine.getM_Locator_ID(),
sLine.getM_Product_ID(), sLine.getM_Product_ID(),
@ -1760,8 +1760,8 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
return status; return status;
} }
} }
if (oLine!=null && mtrx!=null && if (oLine!=null && mtrx!=null && !orderClosed &&
((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0))) ((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0)))
{ {
if (sLine.getC_OrderLine_ID() != 0 && oLine.getM_Product_ID() > 0) if (sLine.getC_OrderLine_ID() != 0 && oLine.getM_Product_ID() > 0)
@ -1847,7 +1847,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
if (dateMPolicy == null) if (dateMPolicy == null)
dateMPolicy = getMovementDate(); dateMPolicy = getMovementDate();
// Fallback: Update Storage - see also VMatch.createMatchRecord // Fallback: Update Storage - see also Match.createMatchRecord
if (pendingQty.signum() != 0 && if (pendingQty.signum() != 0 &&
!MStorageOnHand.add(getCtx(), !MStorageOnHand.add(getCtx(),
sLine.getM_Locator_ID(), sLine.getM_Locator_ID(),
@ -1859,7 +1859,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
m_processMsg = "Cannot correct Inventory OnHand [" + product.getValue() + "] - " + lastError; m_processMsg = "Cannot correct Inventory OnHand [" + product.getValue() + "] - " + lastError;
return DocAction.STATUS_Invalid; return DocAction.STATUS_Invalid;
} }
if (oLine!=null && oLine.getM_Product_ID() > 0 && if (oLine!=null && oLine.getM_Product_ID() > 0 && !orderClosed &&
((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0))) ((!isReversal() && oLine.getQtyReserved().signum() > 0) || (isReversal() && oLine.getQtyOrdered().signum() > 0)))
{ {
IReservationTracer tracer = null; IReservationTracer tracer = null;
@ -1903,7 +1903,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess
} // stock movement } // stock movement
// Correct Order Line // Correct Order Line
if (product != null && oLine != null) // other in VMatch.createMatchRecord if (product != null && oLine != null && !orderClosed) // other in Match.createMatchRecord
{ {
if (oLine.getQtyOrdered().signum() >= 0) if (oLine.getQtyOrdered().signum() >= 0)
{ {

View File

@ -29,6 +29,7 @@ import org.adempiere.base.IProductPricing;
import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.AdempiereException;
import org.adempiere.exceptions.ProductNotOnPriceListException; import org.adempiere.exceptions.ProductNotOnPriceListException;
import org.adempiere.model.ITaxProvider; import org.adempiere.model.ITaxProvider;
import org.compiere.process.DocAction;
import org.compiere.util.CLogger; import org.compiere.util.CLogger;
import org.compiere.util.DB; import org.compiere.util.DB;
import org.compiere.util.Env; import org.compiere.util.Env;
@ -897,6 +898,20 @@ public class MOrderLine extends X_C_OrderLine
} }
} }
//sync qtyordered and qtylostsales for closed order
if (!newRecord && DocAction.STATUS_Closed.equals(getParent().getDocStatus()) && is_ValueChanged(COLUMNNAME_QtyDelivered)
&& !getParent().is_ValueChanged(MOrder.COLUMNNAME_DocStatus)) {
if (getQtyOrdered().compareTo(getQtyDelivered()) > 0)
{
setQtyLostSales(getQtyLostSales().add(getQtyOrdered().subtract(getQtyDelivered())));
setQtyOrdered(getQtyDelivered());
}
else
{
setQtyLostSales(Env.ZERO);
}
}
return true; return true;
} // beforeSave } // beforeSave

View File

@ -960,4 +960,92 @@ public class MatchPOTest extends AbstractTestCase {
receipt.load(getTrxName()); receipt.load(getTrxName());
assertEquals(0, receipt.getC_Order_ID(), "Material receipt: order not clear after void of purchase order"); assertEquals(0, receipt.getC_Order_ID(), "Material receipt: order not clear after void of purchase order");
} }
@Test
public void testReverseReceiptAfterClosePO() {
MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id);
MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id);
//Create PO of 4
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
order.setBPartner(bpartner);
order.setIsSOTrx(false);
order.setC_DocTypeTarget_ID();
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
order.saveEx();
MOrderLine orderLine = new MOrderLine(order);
orderLine.setLine(10);
orderLine.setProduct(product);
orderLine.setQty(new BigDecimal("4"));
orderLine.saveEx();
BigDecimal qtyOrdered = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName());
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
BigDecimal qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName());
assertEquals(4, qtyOrdered1.subtract(qtyOrdered).intValue(), "QtyOrdered not increase as expected");
//Create MR
MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt
receipt.saveEx();
MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID());
int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID();
MInOutLine receiptLine = new MInOutLine(receipt);
receiptLine.setOrderLine(orderLine, M_Locator_ID, new BigDecimal("2"));
receiptLine.setLine(10);
receiptLine.setQty(new BigDecimal("2"));
receiptLine.saveEx();
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete);
assertFalse(info.isError(), info.getSummary());
receipt.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus());
qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName());
assertEquals(qtyOrdered.intValue()+2, qtyOrdered1.intValue(), "QtyOrdered not release as expected");
orderLine.load(getTrxName());
assertEquals(2, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered");
assertEquals(2, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved");
//Close PO
order.load(getTrxName());
info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Close);
assertFalse(info.isError(), info.getSummary());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Closed, order.getDocStatus());
orderLine.load(getTrxName());
assertEquals(4, orderLine.getQtyEntered().intValue(), "Unexpected QtyEntered");
assertEquals(2, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered");
assertEquals(2, orderLine.getQtyOrdered().intValue(), "Unexpected QtyOrdered");
assertEquals(2, orderLine.getQtyLostSales().intValue(), "Unexpected QtyLostSales");
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved");
qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName());
assertEquals(qtyOrdered.intValue(), qtyOrdered1.intValue(), "Unexpected change in QtyOrdered");
//Reverse MR
info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Reverse_Accrual);
assertFalse(info.isError(), info.getSummary());
receipt.load(getTrxName());
assertEquals(DocAction.STATUS_Reversed, receipt.getDocStatus());
orderLine.load(getTrxName());
assertEquals(4, orderLine.getQtyEntered().intValue(), "Unexpected QtyEntered");
assertEquals(0, orderLine.getQtyDelivered().intValue(), "Unexpected QtyDelivered");
assertEquals(0, orderLine.getQtyOrdered().intValue(), "Unexpected QtyOrdered");
assertEquals(4, orderLine.getQtyLostSales().intValue(), "Unexpected QtyLostSales");
assertEquals(0, orderLine.getQtyReserved().intValue(), "Unexpected QtyReserved");
qtyOrdered1 = MStorageReservation.getQty(product.get_ID(), order.getM_Warehouse_ID(), 0, false, getTrxName());
assertEquals(qtyOrdered.intValue(), qtyOrdered1.intValue(), "Unexpected change in QtyOrdered");
}
} }