From bb3856dbb06a833eede865b9cd65d811f8282316 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Wed, 18 Oct 2023 08:44:43 +0200 Subject: [PATCH] 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 --- .../compiere/process/DunningRunCreate.java | 20 +-- .../src/org/idempiere/test/DictionaryIDs.java | 24 ++- .../test/dunning/DunningRunTest.java | 161 ++++++++++++++++++ 3 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 org.idempiere.test/src/org/idempiere/test/dunning/DunningRunTest.java diff --git a/org.adempiere.base.process/src/org/compiere/process/DunningRunCreate.java b/org.adempiere.base.process/src/org/compiere/process/DunningRunCreate.java index 86932a6ed9..cdf9cab4bf 100644 --- a/org.adempiere.base.process/src/org/compiere/process/DunningRunCreate.java +++ b/org.adempiere.base.process/src/org/compiere/process/DunningRunCreate.java @@ -233,20 +233,20 @@ public class DunningRunCreate extends SvrProcess int C_BPartner_ID = rs.getInt(7); int C_InvoicePaySchedule_ID = rs.getInt(8); - StringBuilder msglog = new StringBuilder() - .append("DaysAfterDue: ").append(DaysAfterDue.intValue()).append(" isShowAllDue: ").append(level.isShowAllDue()); - if (log.isLoggable(Level.FINE)) log.fine(msglog.toString()); - msglog = new StringBuilder() - .append("C_Invoice_ID - DaysDue - GrandTotal: ").append(C_Invoice_ID).append(" - ").append(DaysDue).append(" - ").append(GrandTotal); - if (log.isLoggable(Level.FINE)) log.fine(msglog.toString()); - msglog = new StringBuilder("C_InvoicePaySchedule_ID: ").append(C_InvoicePaySchedule_ID); - if (log.isLoggable(Level.FINE)) log.fine(msglog.toString()); + if (log.isLoggable(Level.FINE)) { + StringBuilder msglog = new StringBuilder().append("DaysAfterDue: ").append(DaysAfterDue.intValue()).append(" isShowAllDue: ").append(level.isShowAllDue()); + log.fine(msglog.toString()); + msglog = new StringBuilder().append("C_Invoice_ID - DaysDue - GrandTotal: ").append(C_Invoice_ID).append(" - ").append(DaysDue).append(" - ").append(GrandTotal); + log.fine(msglog.toString()); + msglog = new StringBuilder("C_InvoicePaySchedule_ID: ").append(C_InvoicePaySchedule_ID); + log.fine(msglog.toString()); + } // // Check for Dispute if (!p_IncludeInDispute && IsInDispute) continue; // Check the day again based on rulesets - if (DaysDue > 0 && DaysDue < DaysAfterDue.intValue() && !level.isShowAllDue ()) + if (DaysDue < DaysAfterDue.intValue() && !level.isShowAllDue ()) continue; // Check for an open amount if (Env.ZERO.compareTo(Open) == 0) @@ -275,7 +275,7 @@ public class DunningRunCreate extends SvrProcess continue; // We don't want to show non due documents - if (DaysDue<0 && !level.isShowNotDue ()) + if (DaysDue<=0 && !level.isShowNotDue ()) continue; // We will minus the timesDunned if this is the DaysBetweenDunning is not fullfilled. diff --git a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java index 7eb3ecb1df..4333921bfb 100644 --- a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java +++ b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java @@ -55,6 +55,7 @@ public final class DictionaryIDs { } public enum AD_Org { + GLOBAL(0), HQ(11), STORE_CENTRAL(12), FURNITURE(50000), @@ -251,7 +252,27 @@ public final class DictionaryIDs { this.id = id; } } - + + 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 { CHECKING_IN_TRANSFER(509); @@ -264,6 +285,7 @@ public final class DictionaryIDs { public enum C_PaymentTerm { NET_30(100), + NET_30_DAYS(107), IMMEDIATE(105), TWO_PERCENT_10_NET_30(106), //2%10 Net 30 FIFTY_IMMEDIATE_FIFTY_30DAYS(108); //50% Immediate - 50% in 30 days diff --git a/org.idempiere.test/src/org/idempiere/test/dunning/DunningRunTest.java b/org.idempiere.test/src/org/idempiere/test/dunning/DunningRunTest.java new file mode 100644 index 0000000000..353632a073 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/dunning/DunningRunTest.java @@ -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()); + } + } + } + +}