IDEMPIERE-4654 Sales Order After Receipt Delivery Rule is not working (#536)

* IDEMPIERE-4654 Sales Order After Receipt Delivery Rule is not working

create shipment if it is fully paid and stock is available
use currencyconvertpayment
- rename after receipt to after payment
- added unit test for invoice payment

Co-authored-by: Carlos Ruiz <carg67@gmail.com>
This commit is contained in:
hengsin 2021-01-26 19:34:38 +08:00 committed by GitHub
parent 245b471d06
commit f5e9cd9336
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 350 additions and 2 deletions

View File

@ -0,0 +1,10 @@
SET SQLBLANKLINES ON
SET DEFINE OFF
-- Jan 26, 2021, 3:58:13 PM MYT
UPDATE AD_Ref_List SET Name='After Payment', Description='After full payment has been received for the order',Updated=TO_DATE('2021-01-26 15:58:13','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=217
;
SELECT register_migration_script('202101260800_IDEMPIERE-4654.sql') FROM dual
;

View File

@ -0,0 +1,7 @@
-- Jan 26, 2021, 3:58:13 PM MYT
UPDATE AD_Ref_List SET Name='After Payment', Description='After full payment has been received for the order',Updated=TO_TIMESTAMP('2021-01-26 15:58:13','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=217
;
SELECT register_migration_script('202101260800_IDEMPIERE-4654.sql') FROM dual
;

View File

@ -220,6 +220,13 @@ public class InOutGenerate extends SvrProcess
MOrder order = new MOrder (getCtx(), rs, get_TrxName());
statusUpdate(Msg.getMsg(getCtx(), "Processing") + " " + order.getDocumentInfo());
if (MOrder.DELIVERYRULE_AfterReceipt.equals(order.getDeliveryRule()))
{
BigDecimal payment = order.getPaymentAmt();
if (payment == null || payment.compareTo(order.getGrandTotal()) < 0)
continue;
}
// New Header different Shipper, Shipment Location
if (!p_ConsolidateDocument
|| (m_shipment != null
@ -337,7 +344,7 @@ public class InOutGenerate extends SvrProcess
createLine (order, line, toDeliver, storages, false);
}
// Availability
else if (MOrder.DELIVERYRULE_Availability.equals(order.getDeliveryRule())
else if ((MOrder.DELIVERYRULE_Availability.equals(order.getDeliveryRule()) || MOrder.DELIVERYRULE_AfterReceipt.equals(order.getDeliveryRule()))
&& (onHand.signum() > 0
|| toDeliver.signum() < 0))
{

View File

@ -19,7 +19,9 @@ package org.compiere.model;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Hashtable;
import java.util.List;
@ -31,6 +33,7 @@ import org.adempiere.base.Core;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.exceptions.BPartnerNoBillToAddressException;
import org.adempiere.exceptions.BPartnerNoShipToAddressException;
import org.adempiere.exceptions.DBException;
import org.adempiere.exceptions.FillMandatoryException;
import org.adempiere.model.ITaxProvider;
import org.adempiere.process.SalesOrderRateInquiryProcess;
@ -3000,4 +3003,61 @@ public class MOrder extends X_C_Order implements DocAction
return getC_DocType_ID() > 0 ? getC_DocType_ID() : getC_DocTypeTarget_ID();
}
/**
*
* @return payment amount for order (prepayment + invoice payment)
*/
public BigDecimal getPaymentAmt()
{
BigDecimal orderPaid = null;
String sql = "SELECT SUM(currencyconvertpayment(p.c_payment_id, o.c_currency_id, p.PayAmt+p.DiscountAmt+p.WriteOffAmt, null) "
+ " - paymentallocated(p.c_payment_id, o.c_currency_id) "
+ " * (CASE WHEN p.IsReceipt='Y' THEN 1 ELSE -1 END)) "
+ "FROM C_Payment p "
+ "INNER JOIN C_Order o ON (p.C_Order_ID=o.C_Order_ID) "
+ "WHERE p.C_Order_ID=? AND p.AD_Client_ID=? "
+ "AND p.IsActive='Y' AND p.DocStatus IN ('CO','CL') ";
try (PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName());)
{
pstmt.setInt(1, getC_Order_ID());
pstmt.setInt(2, getAD_Client_ID());
ResultSet rs = pstmt.executeQuery();
if (rs.next())
{
orderPaid = rs.getBigDecimal(1);
}
}
catch (SQLException e)
{
throw new DBException(e, sql);
}
BigDecimal invoicePaid = null;
sql = "SELECT SUM(invoicepaid(i.c_invoice_id, o.c_currency_id, 1)) "
+ "FROM C_Invoice i "
+ "INNER JOIN C_Order o ON (i.C_Order_ID=o.C_Order_ID) "
+ "WHERE i.C_Order_ID=? AND i.AD_Client_ID=? "
+ "AND i.IsActive='Y' AND i.DocStatus IN ('CO','CL') ";
try (PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName());)
{
pstmt.setInt(1, getC_Order_ID());
pstmt.setInt(2, getAD_Client_ID());
ResultSet rs = pstmt.executeQuery();
if (rs.next())
{
invoicePaid = rs.getBigDecimal(1);
}
}
catch (SQLException e)
{
throw new DBException(e, sql);
}
BigDecimal retValue = orderPaid != null ? orderPaid : BigDecimal.ZERO;
if (invoicePaid != null)
retValue = retValue.add(invoicePaid);
return retValue;
}
} // MOrder

View File

@ -23,6 +23,7 @@ public class ServerProcessCtl implements Runnable {
/** Process Info */
ProcessInfo m_pi;
private Trx m_trx;
private boolean managedTrxForJavaProcess = true;
/**************************************************************************
* Constructor
@ -324,7 +325,7 @@ public class ServerProcessCtl implements Runnable {
if (m_pi.getClassName().toLowerCase().startsWith(MRule.SCRIPT_PREFIX)) {
return ProcessUtil.startScriptProcess(Env.getCtx(), m_pi, m_trx);
} else {
return ProcessUtil.startJavaProcess(Env.getCtx(), m_pi, m_trx);
return ProcessUtil.startJavaProcess(Env.getCtx(), m_pi, m_trx, managedTrxForJavaProcess);
}
} // startProcess
@ -341,4 +342,21 @@ public class ServerProcessCtl implements Runnable {
return ProcessUtil.startDatabaseProcedure(m_pi, ProcedureName, m_trx);
} // startDBProcess
/**
* set whether java process call will commit/rollback trx (default is true)
* @param managedTrx
*/
public void setManagedTrxForJavaProcess(boolean managedTrx)
{
this.managedTrxForJavaProcess = managedTrx;
}
/**
*
* @return true if java process call will commit/rollback trx
*/
public boolean isManagedTrxForJavaProcess()
{
return managedTrxForJavaProcess;
}
}

View File

@ -24,6 +24,7 @@
**********************************************************************/
package org.idempiere.test.model;
import static org.compiere.model.SystemIDs.PROCESS_M_INOUT_GENERATE_MANUAL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -39,10 +40,15 @@ import org.compiere.model.MInOutLine;
import org.compiere.model.MInvoice;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MPInstance;
import org.compiere.model.MPInstancePara;
import org.compiere.model.MPayment;
import org.compiere.model.MProduct;
import org.compiere.model.SystemIDs;
import org.compiere.process.DocAction;
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;
@ -397,4 +403,244 @@ public class SalesOrderTest extends AbstractTestCase {
assertTrue(actualBalance.compareTo(initialBalance) == 0);
}
@Test
public void testGenerateShipmentDeliveryRule() {
//test1 with CompleteOrder
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block
order.setBPartner(MBPartner.get(Env.getCtx(), BP_JOE_BLOCK));
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.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
//Azalea Bush
line1.setProduct(MProduct.get(Env.getCtx(), PRODUCT_AZALEA));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
assertFalse(info.isError());
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus());
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue());
int AD_Process_ID = PROCESS_M_INOUT_GENERATE_MANUAL;
MPInstance instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0);
instance.saveEx();
String insert = "INSERT INTO T_SELECTION(AD_PINSTANCE_ID, T_SELECTION_ID) Values (?, ?)";
DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order.getC_Order_ID()}, null);
//call process
ProcessInfo pi = new ProcessInfo ("InOutGen", AD_Process_ID);
pi.setAD_PInstance_ID (instance.getAD_PInstance_ID());
// Add Parameter - Selection=Y
MPInstancePara ip = new MPInstancePara(instance, 10);
ip.setParameter("Selection","Y");
ip.saveEx();
//Add Document action parameter
ip = new MPInstancePara(instance, 20);
ip.setParameter("DocAction", "CO");
ip.saveEx();
// Add Parameter - M_Warehouse_ID=x
ip = new MPInstancePara(instance, 30);
ip.setParameter("M_Warehouse_ID", getM_Warehouse_ID());
ip.saveEx();
ServerProcessCtl processCtl = new ServerProcessCtl(pi, getTrx());
processCtl.setManagedTrxForJavaProcess(false);
processCtl.run();
assertFalse(pi.isError(), pi.getSummary());
line1.load(getTrxName());
assertEquals(0, line1.getQtyReserved().intValue());
assertEquals(1, line1.getQtyDelivered().intValue());
order.getLines();
rollback();
//test2 with AfterReceipt
MOrder order1 = MOrder.copyFrom(order, today, order.getC_DocType_ID(), true, false, false, getTrxName());
order1.setDeliveryRule(MOrder.DELIVERYRULE_AfterReceipt);
order1.saveEx();
info = MWorkflow.runDocumentActionWorkflow(order1, DocAction.ACTION_Complete);
assertFalse(info.isError());
order1.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order1.getDocStatus());
line1 = order1.getLines()[0];
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue());
instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0);
instance.saveEx();
DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order1.getC_Order_ID()}, null);
//call process without payment
pi = new ProcessInfo ("InOutGen", AD_Process_ID);
pi.setAD_PInstance_ID (instance.getAD_PInstance_ID());
// Add Parameter - Selection=Y
ip = new MPInstancePara(instance, 10);
ip.setParameter("Selection","Y");
ip.saveEx();
//Add Document action parameter
ip = new MPInstancePara(instance, 20);
ip.setParameter("DocAction", "CO");
ip.saveEx();
// Add Parameter - M_Warehouse_ID=x
ip = new MPInstancePara(instance, 30);
ip.setParameter("M_Warehouse_ID", getM_Warehouse_ID());
ip.saveEx();
processCtl = new ServerProcessCtl(pi, getTrx());
processCtl.setManagedTrxForJavaProcess(false);
processCtl.run();
assertFalse(pi.isError(), pi.getSummary());
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue());
assertEquals(0, line1.getQtyDelivered().intValue());
//create payment
MPayment payment = new MPayment(Env.getCtx(), 0, getTrxName());
payment.setC_DocType_ID(true);
int C_BankAccount_ID = DB.getSQLValueEx(getTrxName(), "select c_bankaccount_id from c_bankaccount where ad_client_id=? and isdefault='Y'", getAD_Client_ID());
payment.setC_BankAccount_ID(C_BankAccount_ID);
payment.setC_BPartner_ID(order1.getC_BPartner_ID());
payment.setC_Order_ID(order1.getC_Order_ID());
payment.setTenderType(MPayment.TENDERTYPE_DirectDeposit);
payment.setPayAmt(order1.getGrandTotal());
payment.setC_Currency_ID(order1.getC_Currency_ID());
payment.saveEx();
info = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Complete);
assertFalse(info.isError());
payment.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, payment.getDocStatus());
//call process with payment
instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0);
instance.saveEx();
DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order1.getC_Order_ID()}, null);
pi = new ProcessInfo ("InOutGen", AD_Process_ID);
pi.setAD_PInstance_ID (instance.getAD_PInstance_ID());
// Add Parameter - Selection=Y
ip = new MPInstancePara(instance, 10);
ip.setParameter("Selection","Y");
ip.saveEx();
//Add Document action parameter
ip = new MPInstancePara(instance, 20);
ip.setParameter("DocAction", "CO");
ip.saveEx();
// Add Parameter - M_Warehouse_ID=x
ip = new MPInstancePara(instance, 30);
ip.setParameter("M_Warehouse_ID", getM_Warehouse_ID());
ip.saveEx();
processCtl = new ServerProcessCtl(pi, getTrx());
processCtl.setManagedTrxForJavaProcess(false);
processCtl.run();
assertFalse(pi.isError(), pi.getSummary());
line1.load(getTrxName());
assertEquals(0, line1.getQtyReserved().intValue());
assertEquals(1, line1.getQtyDelivered().intValue());
//test3 with AfterReceipt
MOrder order2 = MOrder.copyFrom(order, today, order.getC_DocType_ID(), true, false, false, getTrxName());
order2.setDeliveryRule(MOrder.DELIVERYRULE_AfterReceipt);
order2.saveEx();
info = MWorkflow.runDocumentActionWorkflow(order2, DocAction.ACTION_Complete);
assertFalse(info.isError());
order2.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order2.getDocStatus());
line1 = order2.getLines()[0];
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue());
//create invoice
instance = new MPInstance(Env.getCtx(), SystemIDs.PROCESS_C_INVOICE_GENERATE, 0);
instance.saveEx();
DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order2.getC_Order_ID()}, null);
pi = new ProcessInfo ("InvoiceGen", AD_Process_ID);
pi.setAD_PInstance_ID (instance.getAD_PInstance_ID());
// Add Parameter - Selection=Y
ip = new MPInstancePara(instance, 10);
ip.setParameter("Selection","Y");
ip.saveEx();
//Add Document action parameter
ip = new MPInstancePara(instance, 20);
ip.setParameter("DocAction", "CO");
ip.saveEx();
processCtl = new ServerProcessCtl(pi, getTrx());
processCtl.setManagedTrxForJavaProcess(false);
processCtl.run();
assertFalse(pi.isError(), pi.getSummary());
line1.load(getTrxName());
assertEquals(1, line1.getQtyReserved().intValue());
assertEquals(1, line1.getQtyInvoiced().intValue());
//create payment
payment = new MPayment(Env.getCtx(), 0, getTrxName());
payment.setC_DocType_ID(true);
payment.setC_BankAccount_ID(C_BankAccount_ID);
payment.setC_BPartner_ID(order2.getC_BPartner_ID());
payment.setC_Invoice_ID(DB.getSQLValueEx(getTrxName(), "SELECT C_Invoice_ID FROM C_Invoice WHERE C_Order_ID=?", order2.getC_Order_ID()));
payment.setTenderType(MPayment.TENDERTYPE_DirectDeposit);
payment.setPayAmt(order2.getGrandTotal());
payment.setC_Currency_ID(order2.getC_Currency_ID());
payment.saveEx();
info = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Complete);
assertFalse(info.isError());
payment.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, payment.getDocStatus());
//call process with payment
instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0);
instance.saveEx();
DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order2.getC_Order_ID()}, null);
pi = new ProcessInfo ("InOutGen", AD_Process_ID);
pi.setAD_PInstance_ID (instance.getAD_PInstance_ID());
// Add Parameter - Selection=Y
ip = new MPInstancePara(instance, 10);
ip.setParameter("Selection","Y");
ip.saveEx();
//Add Document action parameter
ip = new MPInstancePara(instance, 20);
ip.setParameter("DocAction", "CO");
ip.saveEx();
// Add Parameter - M_Warehouse_ID=x
ip = new MPInstancePara(instance, 30);
ip.setParameter("M_Warehouse_ID", getM_Warehouse_ID());
ip.saveEx();
processCtl = new ServerProcessCtl(pi, getTrx());
processCtl.setManagedTrxForJavaProcess(false);
processCtl.run();
assertFalse(pi.isError(), pi.getSummary());
line1.load(getTrxName());
assertEquals(0, line1.getQtyReserved().intValue());
assertEquals(1, line1.getQtyDelivered().intValue());
}
}