IDEMPIERE-5793 premature dunning when dunning run is done on exactly the due date (#2064)

* IDEMPIERE-5793 premature dunning when dunning run is done on exactly the due date

* - fix unit test
This commit is contained in:
Carlos Ruiz 2023-10-18 08:44:43 +02:00 committed by GitHub
parent af8f5db555
commit bb3856dbb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 194 additions and 11 deletions

View File

@ -233,20 +233,20 @@ public class DunningRunCreate extends SvrProcess
int C_BPartner_ID = rs.getInt(7); int C_BPartner_ID = rs.getInt(7);
int C_InvoicePaySchedule_ID = rs.getInt(8); int C_InvoicePaySchedule_ID = rs.getInt(8);
StringBuilder msglog = new StringBuilder() if (log.isLoggable(Level.FINE)) {
.append("DaysAfterDue: ").append(DaysAfterDue.intValue()).append(" isShowAllDue: ").append(level.isShowAllDue()); StringBuilder msglog = new StringBuilder().append("DaysAfterDue: ").append(DaysAfterDue.intValue()).append(" isShowAllDue: ").append(level.isShowAllDue());
if (log.isLoggable(Level.FINE)) log.fine(msglog.toString()); log.fine(msglog.toString());
msglog = new StringBuilder() msglog = new StringBuilder().append("C_Invoice_ID - DaysDue - GrandTotal: ").append(C_Invoice_ID).append(" - ").append(DaysDue).append(" - ").append(GrandTotal);
.append("C_Invoice_ID - DaysDue - GrandTotal: ").append(C_Invoice_ID).append(" - ").append(DaysDue).append(" - ").append(GrandTotal); log.fine(msglog.toString());
if (log.isLoggable(Level.FINE)) log.fine(msglog.toString());
msglog = new StringBuilder("C_InvoicePaySchedule_ID: ").append(C_InvoicePaySchedule_ID); msglog = new StringBuilder("C_InvoicePaySchedule_ID: ").append(C_InvoicePaySchedule_ID);
if (log.isLoggable(Level.FINE)) log.fine(msglog.toString()); log.fine(msglog.toString());
}
// //
// Check for Dispute // Check for Dispute
if (!p_IncludeInDispute && IsInDispute) if (!p_IncludeInDispute && IsInDispute)
continue; continue;
// Check the day again based on rulesets // Check the day again based on rulesets
if (DaysDue > 0 && DaysDue < DaysAfterDue.intValue() && !level.isShowAllDue ()) if (DaysDue < DaysAfterDue.intValue() && !level.isShowAllDue ())
continue; continue;
// Check for an open amount // Check for an open amount
if (Env.ZERO.compareTo(Open) == 0) if (Env.ZERO.compareTo(Open) == 0)
@ -275,7 +275,7 @@ public class DunningRunCreate extends SvrProcess
continue; continue;
// We don't want to show non due documents // We don't want to show non due documents
if (DaysDue<0 && !level.isShowNotDue ()) if (DaysDue<=0 && !level.isShowNotDue ())
continue; continue;
// We will minus the timesDunned if this is the DaysBetweenDunning is not fullfilled. // We will minus the timesDunned if this is the DaysBetweenDunning is not fullfilled.

View File

@ -55,6 +55,7 @@ public final class DictionaryIDs {
} }
public enum AD_Org { public enum AD_Org {
GLOBAL(0),
HQ(11), HQ(11),
STORE_CENTRAL(12), STORE_CENTRAL(12),
FURNITURE(50000), FURNITURE(50000),
@ -252,6 +253,26 @@ public final class DictionaryIDs {
} }
} }
public enum C_Dunning {
DEFAULT(100);
public final int id;
private C_Dunning(int id) {
this.id = id;
}
}
public enum C_DunningLevel {
DUN_ALL_DUE_INVOICES(101);
public final int id;
private C_DunningLevel(int id) {
this.id = id;
}
}
public enum C_ElementValue { public enum C_ElementValue {
CHECKING_IN_TRANSFER(509); CHECKING_IN_TRANSFER(509);
@ -264,6 +285,7 @@ public final class DictionaryIDs {
public enum C_PaymentTerm { public enum C_PaymentTerm {
NET_30(100), NET_30(100),
NET_30_DAYS(107),
IMMEDIATE(105), IMMEDIATE(105),
TWO_PERCENT_10_NET_30(106), //2%10 Net 30 TWO_PERCENT_10_NET_30(106), //2%10 Net 30
FIFTY_IMMEDIATE_FIFTY_30DAYS(108); //50% Immediate - 50% in 30 days FIFTY_IMMEDIATE_FIFTY_30DAYS(108); //50% Immediate - 50% in 30 days

View File

@ -0,0 +1,161 @@
/***********************************************************************
* 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 - bxservice *
**********************************************************************/
package org.idempiere.test.dunning;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Properties;
import org.compiere.model.MBPartner;
import org.compiere.model.MDocType;
import org.compiere.model.MDunningLevel;
import org.compiere.model.MDunningRun;
import org.compiere.model.MDunningRunEntry;
import org.compiere.model.MDunningRunLine;
import org.compiere.model.MInvoice;
import org.compiere.model.MInvoiceLine;
import org.compiere.model.MPInstance;
import org.compiere.model.MPInstancePara;
import org.compiere.model.MProcess;
import org.compiere.process.DocAction;
import org.compiere.process.ProcessInfo;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.compiere.util.Trx;
import org.compiere.wf.MWorkflow;
import org.idempiere.test.AbstractTestCase;
import org.idempiere.test.DictionaryIDs;
import org.junit.jupiter.api.Test;
/**
*
* @author Carlos Ruiz - globalqss - bxservice
*
*/
public class DunningRunTest extends AbstractTestCase {
private static final int PROCESS_DUNNING_RUN_CREATE = 289;
/**
*
*/
public DunningRunTest() {
}
@Test
public void testDunning10DaysDue() {
Properties ctx = Env.getCtx();
String trxName = getTrxName();
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
MDunningLevel dl = new MDunningLevel(ctx, DictionaryIDs.C_DunningLevel.DUN_ALL_DUE_INVOICES.id, trxName);
// change to due invoices after 10 days due
dl.setDaysAfterDue(BigDecimal.valueOf(10.0));
dl.setIsShowAllDue(false);
dl.setName("Dun all due invoices after 10 days due");
dl.saveEx();
// create an invoice for GardenUser
MInvoice invoice = new MInvoice(ctx, 0, trxName);
invoice.setBPartner(MBPartner.get(ctx, DictionaryIDs.C_BPartner.C_AND_W.id));
invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_ARInvoice);
invoice.setC_DocType_ID(invoice.getC_DocTypeTarget_ID()); // required to avoid runDocumentActionWorkflow exception
invoice.setPaymentRule(MInvoice.PAYMENTRULE_Check);
invoice.setC_PaymentTerm_ID(DictionaryIDs.C_PaymentTerm.IMMEDIATE.id);
invoice.setDateInvoiced(TimeUtil.addDays(today, -30)); // date 30 days ago
invoice.setDateAcct(TimeUtil.addDays(today, -30));
invoice.setC_PaymentTerm_ID(DictionaryIDs.C_PaymentTerm.NET_30_DAYS.id); // payment term 30 days, so the invoice is due exactly today
invoice.setDocStatus(DocAction.STATUS_Drafted);
invoice.setDocAction(DocAction.ACTION_Complete);
invoice.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_USER.id);
invoice.saveEx();
MInvoiceLine line1 = new MInvoiceLine(invoice);
line1.setLine(10);
line1.setM_Product_ID(DictionaryIDs.M_Product.AZALEA_BUSH.id);
line1.setQty(new BigDecimal("7"));
line1.setPrice(BigDecimal.valueOf(23.75));
line1.saveEx();
ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete);
invoice.load(trxName);
assertFalse(info.isError(), "Error processing invoice: " + info.getSummary());
assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus(), "Invoice document status is not completed: " + invoice.getDocStatus());
// create a dunning run today for the 10 days level
MDunningRun dr = new MDunningRun(ctx, 0, trxName);
dr.setDunningDate(today);
dr.setC_Dunning_ID(DictionaryIDs.C_Dunning.DEFAULT.id);
dr.setC_DunningLevel_ID(DictionaryIDs.C_DunningLevel.DUN_ALL_DUE_INVOICES.id);
dr.saveEx();
// Run the process Dunning Run Create
MProcess process = MProcess.get(PROCESS_DUNNING_RUN_CREATE);
MPInstance pinstance = new MPInstance(process, 0, 0, null);
MPInstancePara[] paras = pinstance.getParameters();
for (MPInstancePara para : paras) {
if (para.getParameterName().equals("AD_Org_ID")) {
para.setP_Number(DictionaryIDs.AD_Org.GLOBAL.id);
para.saveEx();
} else if (para.getParameterName().equals("IncludeInDispute")) {
para.setP_String("N");
para.saveEx();
} else if (para.getParameterName().equals("OnlySOTrx")) {
para.setP_String("Y");
para.saveEx();
} else if (para.getParameterName().equals("SalesRep_ID")) {
para.setP_Number(DictionaryIDs.AD_User.GARDEN_ADMIN.id);
para.saveEx();
} else if (para.getParameterName().equals("C_Currency_ID")) {
para.setP_Number(DictionaryIDs.C_Currency.USD.id);
para.saveEx();
} else if (para.getParameterName().equals("IsAllCurrencies")) {
para.setP_String("Y");
para.saveEx();
} else if (para.getParameterName().equals("C_BPartner_ID")) {
para.setP_Number(DictionaryIDs.C_BPartner.C_AND_W.id);
para.saveEx();
}
}
ProcessInfo pi = new ProcessInfo(process.getName(), PROCESS_DUNNING_RUN_CREATE);
pi.setAD_PInstance_ID(pinstance.getAD_PInstance_ID());
pi.setRecord_ID(dr.getC_DunningRun_ID());
process.processIt(pi, Trx.get(getTrxName(), false), false);
assertTrue(!pi.isError(), pi.getSummary());
// The invoice must not be reported in this dunning because the due date is exactly today
for (MDunningRunEntry dre : dr.getEntries(true)) {
for (MDunningRunLine drl : dre.getLines()) {
assertTrue(drl.getC_Invoice_ID() != invoice.getC_Invoice_ID());
}
}
}
}