From 18f925fd2f771ea9115dbcd7fb0895ed0321d506 Mon Sep 17 00:00:00 2001 From: Deepak Pansheriya Date: Wed, 23 Aug 2023 07:09:24 +0530 Subject: [PATCH] =?UTF-8?q?IDEMPIERE-5768=20:=20Adding=20Credit=20manager?= =?UTF-8?q?=20factory=20to=20override=20or=20extend=20=E2=80=A6=20(#1949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IDEMPIERE-5768 : Adding Credit manager factory to override or extend credit management * IDEMPIERE-5768 : Fixing as as per code review comments from Hengsin * IDEMPIERE-5768: Use component annotation instead of xml service definition * IDEMPIERE-5768: Adding Unit tests and Making return type of CheckCreditStatus to be object instead of String. * IDEMPIERE-5768: Fixing as per code review comment. * IDEMPIERE-5768: Removed explicit rollback from test cases --- .../src/org/adempiere/base/Core.java | 39 ++++ .../src/org/adempiere/base/CreditStatus.java | 58 ++++++ .../base/DefaultCreditManagerFactory.java | 50 ++++++ .../org/adempiere/base/ICreditManager.java | 30 ++++ .../adempiere/base/ICreditManagerFactory.java | 32 ++++ .../src/org/compiere/model/MInOut.java | 44 ++--- .../src/org/compiere/model/MInvoice.java | 103 ++--------- .../src/org/compiere/model/MOrder.java | 50 ++---- .../src/org/compiere/model/MPayment.java | 83 +++------ .../model/credit/CreditManagerInOut.java | 89 ++++++++++ .../model/credit/CreditManagerInvoice.java | 168 ++++++++++++++++++ .../model/credit/CreditManagerOrder.java | 99 +++++++++++ .../model/credit/CreditManagerPayment.java | 137 ++++++++++++++ .../org/idempiere/test/base/InOutTest.java | 86 +++++++-- .../org/idempiere/test/base/InvoiceTest.java | 91 ++++++++++ .../org/idempiere/test/model/PaymentTest.java | 49 +++++ .../idempiere/test/model/SalesOrderTest.java | 44 +++++ 17 files changed, 1019 insertions(+), 233 deletions(-) create mode 100644 org.adempiere.base/src/org/adempiere/base/CreditStatus.java create mode 100644 org.adempiere.base/src/org/adempiere/base/DefaultCreditManagerFactory.java create mode 100644 org.adempiere.base/src/org/adempiere/base/ICreditManager.java create mode 100644 org.adempiere.base/src/org/adempiere/base/ICreditManagerFactory.java create mode 100644 org.adempiere.base/src/org/compiere/model/credit/CreditManagerInOut.java create mode 100644 org.adempiere.base/src/org/compiere/model/credit/CreditManagerInvoice.java create mode 100644 org.adempiere.base/src/org/compiere/model/credit/CreditManagerOrder.java create mode 100644 org.adempiere.base/src/org/compiere/model/credit/CreditManagerPayment.java create mode 100644 org.idempiere.test/src/org/idempiere/test/base/InvoiceTest.java diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java index 2a76748d04..7554718057 100644 --- a/org.adempiere.base/src/org/adempiere/base/Core.java +++ b/org.adempiere.base/src/org/adempiere/base/Core.java @@ -48,6 +48,7 @@ import org.compiere.model.MPaymentProcessor; import org.compiere.model.MSysConfig; import org.compiere.model.MTaxProvider; import org.compiere.model.ModelValidator; +import org.compiere.model.PO; import org.compiere.model.PaymentInterface; import org.compiere.model.PaymentProcessor; import org.compiere.model.StandardTaxProvider; @@ -1087,4 +1088,42 @@ public class Core { } return null; } + + /** + * get Credit Manager + * + * @param PO + * @return instance of the ICreditManager + */ + public static ICreditManager getCreditManager(PO po) + { + if (po == null) + { + s_log.log(Level.SEVERE, "Invalid PO"); + return null; + } + + ICreditManager myCreditManager = null; + + List factoryList = Service.locator().list(ICreditManagerFactory.class).getServices(); + if (factoryList != null) + { + for (ICreditManagerFactory factory : factoryList) + { + myCreditManager = factory.getCreditManager(po); + if (myCreditManager != null) + { + break; + } + } + } + + if (myCreditManager == null) + { + s_log.log(Level.CONFIG, "For " + po.get_TableName() + " not found any service/extension registry."); + return null; + } + + return myCreditManager; + } // getCreditManager } diff --git a/org.adempiere.base/src/org/adempiere/base/CreditStatus.java b/org.adempiere.base/src/org/adempiere/base/CreditStatus.java new file mode 100644 index 0000000000..75c685eb17 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/CreditStatus.java @@ -0,0 +1,58 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.base; + +/** + * POJO for Credit Status + * + * @author Logilite Technologies + * @since August 04, 2023 + */ +public class CreditStatus +{ + protected String errorMsg; + protected boolean isError; + + /** + * Credit Status Load Constructor + * + * @param errorMsg + * @param isError + */ + public CreditStatus(String errorMsg, boolean isError) + { + super(); + this.errorMsg = errorMsg; + this.isError = isError; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public boolean isError() + { + return isError; + } + + public void setError(boolean isError) + { + this.isError = isError; + } +} diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultCreditManagerFactory.java b/org.adempiere.base/src/org/adempiere/base/DefaultCreditManagerFactory.java new file mode 100644 index 0000000000..b79f5ff727 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/DefaultCreditManagerFactory.java @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.base; + +import org.compiere.model.MInOut; +import org.compiere.model.MInvoice; +import org.compiere.model.MOrder; +import org.compiere.model.MPayment; +import org.compiere.model.PO; +import org.compiere.model.credit.CreditManagerInOut; +import org.compiere.model.credit.CreditManagerInvoice; +import org.compiere.model.credit.CreditManagerOrder; +import org.compiere.model.credit.CreditManagerPayment; +import org.osgi.service.component.annotations.Component; + +/** + * Default Credit Manager Factory + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +@Component(immediate = true, service = ICreditManagerFactory.class) +public class DefaultCreditManagerFactory implements ICreditManagerFactory +{ + + @Override + public ICreditManager getCreditManager(PO po) + { + if (po instanceof MOrder) + return new CreditManagerOrder((MOrder) po); + else if (po instanceof MInvoice) + return new CreditManagerInvoice((MInvoice) po); + else if (po instanceof MPayment) + return new CreditManagerPayment((MPayment) po); + else if (po instanceof MInOut) + return new CreditManagerInOut((MInOut) po); + return null; + } + +} diff --git a/org.adempiere.base/src/org/adempiere/base/ICreditManager.java b/org.adempiere.base/src/org/adempiere/base/ICreditManager.java new file mode 100644 index 0000000000..20d2a6e5f9 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/ICreditManager.java @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.base; + +/** + * Interface for Credit Manager implementor + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public interface ICreditManager +{ + /** + * Check credit status as per document action + * + * @param docAction Document Action + * @return Credit Status POJO + */ + public CreditStatus checkCreditStatus(String docAction); +} diff --git a/org.adempiere.base/src/org/adempiere/base/ICreditManagerFactory.java b/org.adempiere.base/src/org/adempiere/base/ICreditManagerFactory.java new file mode 100644 index 0000000000..f5317d0f10 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/ICreditManagerFactory.java @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.base; + +import org.compiere.model.PO; + +/** + * Interface for Credit Manager Factory + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public interface ICreditManagerFactory +{ + /** + * get Credit Manager + * + * @param po Model class + * @return instance of the ICreditManager + */ + public ICreditManager getCreditManager(PO po); +} diff --git a/org.adempiere.base/src/org/compiere/model/MInOut.java b/org.adempiere.base/src/org/compiere/model/MInOut.java index a4cd8f8b48..0df61980c0 100644 --- a/org.adempiere.base/src/org/compiere/model/MInOut.java +++ b/org.adempiere.base/src/org/compiere/model/MInOut.java @@ -29,6 +29,8 @@ import java.util.Properties; import java.util.logging.Level; import org.adempiere.base.Core; +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.adempiere.exceptions.NegativeInventoryDisallowedException; @@ -1454,40 +1456,14 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess } // Credit Check - if (isSOTrx() && !isReversal() && !isCustomerReturn()) + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - I_C_Order order = getC_Order(); - if (order != null && MDocType.DOCSUBTYPESO_PrepayOrder.equals(order.getC_DocType().getDocSubTypeSO()) - && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_PREPAY_ORDER, true, getAD_Client_ID(), getAD_Org_ID())) { - // ignore -- don't validate Prepay Orders depending on sysconfig parameter - } else { - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), get_TrxName()); - if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) - { - m_processMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) - { - m_processMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - if (!MBPartner.SOCREDITSTATUS_NoCreditCheck.equals(bp.getSOCreditStatus()) - && Env.ZERO.compareTo(bp.getSO_CreditLimit()) != 0) - { - BigDecimal notInvoicedAmt = MBPartner.getNotInvoicedAmt(getC_BPartner_ID()); - if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus(notInvoicedAmt))) - { - m_processMsg = "@BPartnerOverSCreditHold@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() + ", @NotInvoicedAmt@=" + notInvoicedAmt - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - } + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Prepare); + if (status.isError()) + { + m_processMsg = status.getErrorMsg(); + return DocAction.STATUS_Invalid; } } @@ -1557,7 +1533,7 @@ public class MInOut extends X_M_InOut implements DocAction, IDocsPostProcess * Check if Document is Customer Return. * @return True if Document is Customer Return */ - private boolean isCustomerReturn() { + public boolean isCustomerReturn() { MDocType doctype = MDocType.get(getC_DocType_ID()); if(isSOTrx() && doctype.getDocBaseType().equals("MMR") && doctype.isSOTrx()) return true; diff --git a/org.adempiere.base/src/org/compiere/model/MInvoice.java b/org.adempiere.base/src/org/compiere/model/MInvoice.java index 23b6d37b3a..afeb88da99 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoice.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoice.java @@ -32,6 +32,8 @@ import java.util.Vector; import java.util.logging.Level; import org.adempiere.base.Core; +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.BPartnerNoAddressException; import org.adempiere.exceptions.DBException; @@ -1737,23 +1739,15 @@ public class MInvoice extends X_C_Invoice implements DocAction, IDocsPostProcess } // Credit Status - if (isSOTrx()) + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - MDocType doc = (MDocType) getC_DocTypeTarget(); - // IDEMPIERE-365 - just check credit if is going to increase the debt - if ( (doc.getDocBaseType().equals(MDocType.DOCBASETYPE_ARCreditMemo) && getGrandTotal().signum() < 0 ) || - (doc.getDocBaseType().equals(MDocType.DOCBASETYPE_ARInvoice) && getGrandTotal().signum() > 0 ) - ) - { - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), null); - if ( MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus()) ) - { - m_processMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - } + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Prepare); + if (status.isError()) + { + m_processMsg = status.getErrorMsg(); + return DocAction.STATUS_Invalid; + } } // Landed Costs @@ -2144,76 +2138,15 @@ public class MInvoice extends X_C_Invoice implements DocAction, IDocsPostProcess if (matchPO > 0) info.append(" @M_MatchPO_ID@#").append(matchPO).append(" "); - - - // Update BP Statistics - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), get_TrxName()); - DB.getDatabase().forUpdate(bp, 0); - // Update total revenue and balance / credit limit (reversed on AllocationLine.processIt) - BigDecimal invAmt = null; - int baseCurrencyId = Env.getContextAsInt(getCtx(), Env.C_CURRENCY_ID); - if (getC_Currency_ID() != baseCurrencyId && isOverrideCurrencyRate()) + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - invAmt = getGrandTotal(true).multiply(getCurrencyRate()); - int stdPrecision = MCurrency.getStdPrecision(getCtx(), baseCurrencyId); - if (invAmt.scale() > stdPrecision) - invAmt = invAmt.setScale(stdPrecision, RoundingMode.HALF_UP); - } - else - { - invAmt = MConversionRate.convertBase(getCtx(), getGrandTotal(true), // CM adjusted - getC_Currency_ID(), getDateAcct(), getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - } - if (invAmt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), getC_ConversionType_ID(), getDateAcct(), get_TrxName()); - return DocAction.STATUS_Invalid; - } - // Total Balance - BigDecimal newBalance = bp.getTotalOpenBalance(); - if (newBalance == null) - newBalance = Env.ZERO; - if (isSOTrx()) - { - newBalance = newBalance.add(invAmt); - // - if (bp.getFirstSale() == null) - bp.setFirstSale(getDateInvoiced()); - BigDecimal newLifeAmt = bp.getActualLifeTimeValue(); - if (newLifeAmt == null) - newLifeAmt = invAmt; - else - newLifeAmt = newLifeAmt.add(invAmt); - BigDecimal newCreditAmt = bp.getSO_CreditUsed(); - if (newCreditAmt == null) - newCreditAmt = invAmt; - else - newCreditAmt = newCreditAmt.add(invAmt); - // - if (log.isLoggable(Level.FINE)) log.fine("GrandTotal=" + getGrandTotal(true) + "(" + invAmt - + ") BP Life=" + bp.getActualLifeTimeValue() + "->" + newLifeAmt - + ", Credit=" + bp.getSO_CreditUsed() + "->" + newCreditAmt - + ", Balance=" + bp.getTotalOpenBalance() + " -> " + newBalance); - bp.setActualLifeTimeValue(newLifeAmt); - bp.setSO_CreditUsed(newCreditAmt); - } // SO - else - { - newBalance = newBalance.subtract(invAmt); - if (log.isLoggable(Level.FINE)) log.fine("GrandTotal=" + getGrandTotal(true) + "(" + invAmt - + ") Balance=" + bp.getTotalOpenBalance() + " -> " + newBalance); - } - // the payment just created already updated the open balance - if ( ! (PAYMENTRULE_Cash.equals(getPaymentRule()) && !fromPOS ) ) - { - bp.setTotalOpenBalance(newBalance); - } - bp.setSOCreditStatus(); - if (!bp.save(get_TrxName())) - { - m_processMsg = "Could not update Business Partner"; - return DocAction.STATUS_Invalid; + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Complete); + if (status.isError()) + { + m_processMsg = status.getErrorMsg(); + return DocAction.STATUS_Invalid; + } } // User - Last Result/Contact diff --git a/org.adempiere.base/src/org/compiere/model/MOrder.java b/org.adempiere.base/src/org/compiere/model/MOrder.java index 433139775d..5fa7a8898e 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrder.java +++ b/org.adempiere.base/src/org/compiere/model/MOrder.java @@ -30,6 +30,8 @@ import java.util.Properties; import java.util.logging.Level; import org.adempiere.base.Core; +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.BPartnerNoBillToAddressException; import org.adempiere.exceptions.BPartnerNoShipToAddressException; @@ -1683,47 +1685,15 @@ public class MOrder extends X_C_Order implements DocAction } // Credit Check - if (isSOTrx()) + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - if ( MDocType.DOCSUBTYPESO_POSOrder.equals(dt.getDocSubTypeSO()) - && PAYMENTRULE_Cash.equals(getPaymentRule()) - && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_CASH_POS_ORDER, true, getAD_Client_ID(), getAD_Org_ID())) { - // ignore -- don't validate for Cash POS Orders depending on sysconfig parameter - } else if (MDocType.DOCSUBTYPESO_PrepayOrder.equals(dt.getDocSubTypeSO()) - && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_PREPAY_ORDER, true, getAD_Client_ID(), getAD_Org_ID())) { - // ignore -- don't validate Prepay Orders depending on sysconfig parameter - } else { - MBPartner bp = new MBPartner (getCtx(), getBill_BPartner_ID(), get_TrxName()); // bill bp is guaranteed on beforeSave - - if (getGrandTotal().signum() > 0) // IDEMPIERE-365 - just check credit if is going to increase the debt - { - - if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) - { - m_processMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) - { - m_processMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - BigDecimal grandTotal = MConversionRate.convertBase(getCtx(), - getGrandTotal(), getC_Currency_ID(), getDateOrdered(), - getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus(grandTotal))) - { - m_processMsg = "@BPartnerOverOCreditHold@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() + ", @GrandTotal@=" + grandTotal - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - } - } + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Prepare); + if (status.isError()) + { + m_processMsg = status.getErrorMsg(); + return DocAction.STATUS_Invalid; + } } m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_PREPARE); diff --git a/org.adempiere.base/src/org/compiere/model/MPayment.java b/org.adempiere.base/src/org/compiere/model/MPayment.java index 6774fed997..3141c9957b 100644 --- a/org.adempiere.base/src/org/compiere/model/MPayment.java +++ b/org.adempiere.base/src/org/compiere/model/MPayment.java @@ -29,6 +29,9 @@ import java.util.Properties; import java.util.Vector; import java.util.logging.Level; +import org.adempiere.base.Core; +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.PeriodClosedException; import org.adempiere.util.IProcessUI; @@ -1985,22 +1988,13 @@ public class MPayment extends X_C_Payment return DocAction.STATUS_Invalid; } - // Do not pay when Credit Stop/Hold - if (!isReceipt()) + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), get_TrxName()); - if (X_C_BPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Prepare); + if (status.isError()) { - m_processMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); - return DocAction.STATUS_Invalid; - } - if (X_C_BPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) - { - m_processMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" - + bp.getTotalOpenBalance() - + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + m_processMsg = status.getErrorMsg(); return DocAction.STATUS_Invalid; } } @@ -2066,54 +2060,22 @@ public class MPayment extends X_C_Payment if (log.isLoggable(Level.INFO)) log.info(toString()); // Charge Handling - boolean createdAllocationRecords = false; if (getC_Charge_ID() != 0) { setIsAllocated(true); } - else + + ICreditManager creditManager = Core.getCreditManager(this); + if (creditManager != null) { - createdAllocationRecords = allocateIt(); // Create Allocation Records - testAllocation(); + CreditStatus status = creditManager.checkCreditStatus(DOCACTION_Complete); + if (status.isError()) + { + m_processMsg = status.getErrorMsg(); + return DocAction.STATUS_Invalid; + } } - - // Update BP for Prepayments - if (getC_BPartner_ID() != 0 && getC_Invoice_ID() == 0 && getC_Charge_ID() == 0 && MPaymentAllocate.get(this).length == 0 && !createdAllocationRecords) - { - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), get_TrxName()); - DB.getDatabase().forUpdate(bp, 0); - // Update total balance to include this payment - BigDecimal payAmt = null; - int baseCurrencyId = Env.getContextAsInt(getCtx(), Env.C_CURRENCY_ID); - if (getC_Currency_ID() != baseCurrencyId && isOverrideCurrencyRate()) - { - payAmt = getConvertedAmt(); - } - else - { - payAmt = MConversionRate.convertBase(getCtx(), getPayAmt(), - getC_Currency_ID(), getDateAcct(), getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); - if (payAmt == null) - { - m_processMsg = MConversionRateUtil.getErrorMessage(getCtx(), "ErrorConvertingCurrencyToBaseCurrency", - getC_Currency_ID(), MClient.get(getCtx()).getC_Currency_ID(), getC_ConversionType_ID(), getDateAcct(), get_TrxName()); - return DocAction.STATUS_Invalid; - } - } - // Total Balance - BigDecimal newBalance = bp.getTotalOpenBalance(); - if (newBalance == null) - newBalance = Env.ZERO; - if (isReceipt()) - newBalance = newBalance.subtract(payAmt); - else - newBalance = newBalance.add(payAmt); - - bp.setTotalOpenBalance(newBalance); - bp.setSOCreditStatus(); - bp.saveEx(); - } - + // Counter Doc MPayment counter = createCounterDoc(); if (counter != null) @@ -2832,13 +2794,10 @@ public class MPayment extends X_C_Payment // info.append(" - @C_AllocationHdr_ID@: ").append(alloc.getDocumentNo()); + ICreditManager creditManager = Core.getCreditManager(this); // Update BPartner - if (getC_BPartner_ID() != 0) - { - MBPartner bp = new MBPartner (getCtx(), getC_BPartner_ID(), get_TrxName()); - bp.setTotalOpenBalance(); - bp.saveEx(get_TrxName()); - } + if (creditManager != null) + creditManager.checkCreditStatus(accrual ? DOCACTION_Reverse_Accrual : DOCACTION_Reverse_Correct); return info; } diff --git a/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInOut.java b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInOut.java new file mode 100644 index 0000000000..a5ce64b33a --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInOut.java @@ -0,0 +1,89 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model.credit; + +import java.math.BigDecimal; + +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; +import org.compiere.model.I_C_Order; +import org.compiere.model.MBPartner; +import org.compiere.model.MDocType; +import org.compiere.model.MInOut; +import org.compiere.model.MSysConfig; +import org.compiere.util.Env; +import org.compiere.util.Util; + +/** + * Credit Manager for InOut + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public class CreditManagerInOut implements ICreditManager +{ + private MInOut mInOut; + + /** + * InOut Credit Manager Load Constructor + * + * @param po MInOut + */ + public CreditManagerInOut(MInOut po) + { + this.mInOut = po; + } + + @Override + public CreditStatus checkCreditStatus(String docAction) + { + String errorMsg = null; + if (MInOut.DOCACTION_Prepare.equals(docAction) && mInOut.isSOTrx() && !mInOut.isReversal() && !mInOut.isCustomerReturn()) + { + I_C_Order order = mInOut.getC_Order(); + if (order != null + && MDocType.DOCSUBTYPESO_PrepayOrder.equals(order.getC_DocType().getDocSubTypeSO()) + && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_PREPAY_ORDER, true, mInOut.getAD_Client_ID(), mInOut.getAD_Org_ID())) + { + // ignore -- don't validate Prepay Orders depending on sysconfig parameter + } + else + { + MBPartner bp = new MBPartner(mInOut.getCtx(), mInOut.getC_BPartner_ID(), mInOut.get_TrxName()); + if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + if (!MBPartner.SOCREDITSTATUS_NoCreditCheck.equals(bp.getSOCreditStatus()) + && Env.ZERO.compareTo(bp.getSO_CreditLimit()) != 0) + { + BigDecimal notInvoicedAmt = MBPartner.getNotInvoicedAmt(mInOut.getC_BPartner_ID()); + if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus(notInvoicedAmt))) + { + errorMsg = "@BPartnerOverSCreditHold@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @NotInvoicedAmt@=" + notInvoicedAmt + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + } + } + } + + return new CreditStatus(errorMsg, !Util.isEmpty(errorMsg)); + } // creditCheck +} diff --git a/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInvoice.java b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInvoice.java new file mode 100644 index 0000000000..a89d93a800 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerInvoice.java @@ -0,0 +1,168 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model.credit; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.logging.Level; + +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; +import org.compiere.model.MBPartner; +import org.compiere.model.MClient; +import org.compiere.model.MConversionRate; +import org.compiere.model.MConversionRateUtil; +import org.compiere.model.MCurrency; +import org.compiere.model.MDocType; +import org.compiere.model.MInvoice; +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Util; + +/** + * Credit Manager for Invoice + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public class CreditManagerInvoice implements ICreditManager +{ + /** Logger */ + protected transient CLogger log = CLogger.getCLogger(CreditManagerInvoice.class); + + private MInvoice mInvoice; + + /** + * Invoice Credit Manager Load Constructor + * + * @param po MInvoice + */ + public CreditManagerInvoice(MInvoice po) + { + this.mInvoice = po; + } + + @Override + public CreditStatus checkCreditStatus(String docAction) + { + String errorMsg = null; + if (MInvoice.DOCACTION_Prepare.equals(docAction) && mInvoice.isSOTrx()) + { + MDocType doc = (MDocType) mInvoice.getC_DocTypeTarget(); + // IDEMPIERE-365 - just check credit if is going to increase the debt + if ((doc.getDocBaseType().equals(MDocType.DOCBASETYPE_ARCreditMemo) && mInvoice.getGrandTotal().signum() < 0) + || (doc.getDocBaseType().equals(MDocType.DOCBASETYPE_ARInvoice) && mInvoice.getGrandTotal().signum() > 0)) + { + MBPartner bp = new MBPartner(mInvoice.getCtx(), mInvoice.getC_BPartner_ID(), mInvoice.get_TrxName()); + if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + } + } + else if (MInvoice.DOCACTION_Complete.equals(docAction)) + { + // POS supports multiple payments + boolean fromPOS = false; + if (mInvoice.getC_Order_ID() > 0) + { + fromPOS = mInvoice.getC_Order().getC_POS_ID() > 0; + } + // Update BP Statistics + MBPartner bp = new MBPartner(mInvoice.getCtx(), mInvoice.getC_BPartner_ID(), mInvoice.get_TrxName()); + DB.getDatabase().forUpdate(bp, 0); + // Update total revenue and balance / credit limit (reversed on + // AllocationLine.processIt) + BigDecimal invAmt = null; + int baseCurrencyId = Env.getContextAsInt(mInvoice.getCtx(), Env.C_CURRENCY_ID); + if (mInvoice.getC_Currency_ID() != baseCurrencyId && mInvoice.isOverrideCurrencyRate()) + { + invAmt = mInvoice.getGrandTotal(true).multiply(mInvoice.getCurrencyRate()); + int stdPrecision = MCurrency.getStdPrecision(mInvoice.getCtx(), baseCurrencyId); + if (invAmt.scale() > stdPrecision) + invAmt = invAmt.setScale(stdPrecision, RoundingMode.HALF_UP); + } + else + { + invAmt = MConversionRate.convertBase( mInvoice.getCtx(), + mInvoice.getGrandTotal(true), // CM adjusted + mInvoice.getC_Currency_ID(), + mInvoice.getDateAcct(), + mInvoice.getC_ConversionType_ID(), + mInvoice.getAD_Client_ID(), + mInvoice.getAD_Org_ID()); + } + if (invAmt == null) + { + errorMsg = MConversionRateUtil.getErrorMessage( mInvoice.getCtx(), + "ErrorConvertingCurrencyToBaseCurrency", + mInvoice.getC_Currency_ID(), + MClient.get(mInvoice.getCtx()).getC_Currency_ID(), + mInvoice.getC_ConversionType_ID(), + mInvoice.getDateAcct(), + mInvoice.get_TrxName()); + } + // Total Balance + BigDecimal newBalance = bp.getTotalOpenBalance(); + if (newBalance == null) + newBalance = Env.ZERO; + if (mInvoice.isSOTrx()) + { + newBalance = newBalance.add(invAmt); + // + if (bp.getFirstSale() == null) + bp.setFirstSale(mInvoice.getDateInvoiced()); + + BigDecimal newLifeAmt = bp.getActualLifeTimeValue(); + if (newLifeAmt == null) + newLifeAmt = invAmt; + else + newLifeAmt = newLifeAmt.add(invAmt); + + BigDecimal newCreditAmt = bp.getSO_CreditUsed(); + if (newCreditAmt == null) + newCreditAmt = invAmt; + else + newCreditAmt = newCreditAmt.add(invAmt); + // + if (log.isLoggable(Level.FINE)) + log.fine( "GrandTotal=" + mInvoice.getGrandTotal(true) + "(" + invAmt + + ") BP Life=" + bp.getActualLifeTimeValue() + "->" + newLifeAmt + + ", Credit=" + bp.getSO_CreditUsed() + "->" + newCreditAmt + + ", Balance=" + bp.getTotalOpenBalance() + " -> " + newBalance); + bp.setActualLifeTimeValue(newLifeAmt); + bp.setSO_CreditUsed(newCreditAmt); + } // SO + else + { + newBalance = newBalance.subtract(invAmt); + if (log.isLoggable(Level.FINE)) + log.fine( "GrandTotal=" + mInvoice.getGrandTotal(true) + "(" + invAmt + + ") Balance=" + bp.getTotalOpenBalance() + " -> " + newBalance); + } + // the payment just created already updated the open balance + if (!(MInvoice.PAYMENTRULE_Cash.equals(mInvoice.getPaymentRule()) && !fromPOS)) + { + bp.setTotalOpenBalance(newBalance); + } + bp.setSOCreditStatus(); + if (!bp.save(mInvoice.get_TrxName())) + { + errorMsg = "Could not update Business Partner"; + } + } + return new CreditStatus(errorMsg, !Util.isEmpty(errorMsg)); + } // creditCheck +} diff --git a/org.adempiere.base/src/org/compiere/model/credit/CreditManagerOrder.java b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerOrder.java new file mode 100644 index 0000000000..95133f1cf5 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerOrder.java @@ -0,0 +1,99 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model.credit; + +import java.math.BigDecimal; +import java.util.Properties; + +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; +import org.compiere.model.MBPartner; +import org.compiere.model.MConversionRate; +import org.compiere.model.MDocType; +import org.compiere.model.MOrder; +import org.compiere.model.MSysConfig; +import org.compiere.util.Util; + +/** + * Credit Manager for Order + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public class CreditManagerOrder implements ICreditManager +{ + + private MOrder order; + + /** + * Order Credit Manager Load Constructor + * + * @param po MOrder + */ + public CreditManagerOrder(MOrder po) + { + this.order = po; + } + + @Override + public CreditStatus checkCreditStatus(String docAction) + { + String errorMsg = null; + if (MOrder.DOCACTION_Prepare.equals(docAction) && order.isSOTrx()) + { + Properties ctx = order.getCtx(); + MDocType dt = MDocType.get(ctx, order.getC_DocTypeTarget_ID()); + if (MDocType.DOCSUBTYPESO_POSOrder.equals(dt.getDocSubTypeSO()) + && MOrder.PAYMENTRULE_Cash.equals(order.getPaymentRule()) + && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_CASH_POS_ORDER, true, order.getAD_Client_ID(), order.getAD_Org_ID())) + { + // ignore -- don't validate for Cash POS Orders depending on sysconfig parameter + } + else if (MDocType.DOCSUBTYPESO_PrepayOrder.equals(dt.getDocSubTypeSO()) + && !MSysConfig.getBooleanValue(MSysConfig.CHECK_CREDIT_ON_PREPAY_ORDER, true, order.getAD_Client_ID(), order.getAD_Org_ID())) + { + // ignore -- don't validate Prepay Orders depending on sysconfig parameter + } + else + { + // bill bp is guaranteed on beforeSave + MBPartner bp = new MBPartner(ctx, order.getBill_BPartner_ID(), order.get_TrxName()); + // IDEMPIERE-365 - just check credit if is going to increase the debt + if (order.getGrandTotal().signum() > 0) + { + if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + + BigDecimal grandTotal = MConversionRate.convertBase(ctx, order.getGrandTotal(), order.getC_Currency_ID(), + order.getDateOrdered(), order.getC_ConversionType_ID(), + order.getAD_Client_ID(), order.getAD_Org_ID()); + if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus(grandTotal))) + { + errorMsg = "@BPartnerOverOCreditHold@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @GrandTotal@=" + grandTotal + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + } + } + } + return new CreditStatus(errorMsg, !Util.isEmpty(errorMsg)); + } // creditCheck +} diff --git a/org.adempiere.base/src/org/compiere/model/credit/CreditManagerPayment.java b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerPayment.java new file mode 100644 index 0000000000..3766b24adb --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/credit/CreditManagerPayment.java @@ -0,0 +1,137 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model.credit; + +import java.math.BigDecimal; +import java.util.Properties; + +import org.adempiere.base.CreditStatus; +import org.adempiere.base.ICreditManager; +import org.compiere.model.MBPartner; +import org.compiere.model.MClient; +import org.compiere.model.MConversionRate; +import org.compiere.model.MConversionRateUtil; +import org.compiere.model.MPayment; +import org.compiere.model.MPaymentAllocate; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Util; + +/** + * Credit Manager for Payment + * + * @author Logilite Technologies + * @since June 25, 2023 + */ +public class CreditManagerPayment implements ICreditManager +{ + private MPayment payment; + + /** + * Payment Credit Manager Load Constructor + * + * @param po MPayment + */ + public CreditManagerPayment(MPayment po) + { + this.payment = po; + } + + @Override + public CreditStatus checkCreditStatus(String docAction) + { + String errorMsg = null; + if (MPayment.DOCACTION_Prepare.equals(docAction) && !payment.isReceipt()) + { // Do not pay when Credit Stop/Hold and issue refund to customer + MBPartner bp = new MBPartner(payment.getCtx(), payment.getC_BPartner_ID(), payment.get_TrxName()); + if (MBPartner.SOCREDITSTATUS_CreditStop.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditStop@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + if (MBPartner.SOCREDITSTATUS_CreditHold.equals(bp.getSOCreditStatus())) + { + errorMsg = "@BPartnerCreditHold@ - @TotalOpenBalance@=" + bp.getTotalOpenBalance() + + ", @SO_CreditLimit@=" + bp.getSO_CreditLimit(); + } + } + else if (MPayment.DOCACTION_Complete.equals(docAction)) + { + // Charge Handling + boolean createdAllocationRecords = false; + if (payment.getC_Charge_ID() == 0) + { + createdAllocationRecords = payment.allocateIt(); // Create Allocation Records + payment.testAllocation(); + } + // Update BP for Prepayments + if (payment.getC_BPartner_ID() != 0 + && payment.getC_Invoice_ID() == 0 + && payment.getC_Charge_ID() == 0 + && MPaymentAllocate.get(payment).length == 0 + && !createdAllocationRecords) + { + Properties ctx = payment.getCtx(); + MBPartner bp = new MBPartner(ctx, payment.getC_BPartner_ID(), payment.get_TrxName()); + DB.getDatabase().forUpdate(bp, 0); + // Update total balance to include this payment + BigDecimal payAmt = null; + int baseCurrencyId = Env.getContextAsInt(ctx, Env.C_CURRENCY_ID); + if (payment.getC_Currency_ID() != baseCurrencyId && payment.isOverrideCurrencyRate()) + { + payAmt = payment.getConvertedAmt(); + } + else + { + payAmt = MConversionRate.convertBase( ctx, payment.getPayAmt(), + payment.getC_Currency_ID(), + payment.getDateAcct(), + payment.getC_ConversionType_ID(), + payment.getAD_Client_ID(), + payment.getAD_Org_ID()); + + if (payAmt == null) + { + errorMsg = MConversionRateUtil.getErrorMessage( ctx, "ErrorConvertingCurrencyToBaseCurrency", + payment.getC_Currency_ID(), + MClient.get(ctx).getC_Currency_ID(), + payment.getC_ConversionType_ID(), + payment.getDateAcct(), payment.get_TrxName()); + } + } + // Total Balance + BigDecimal newBalance = bp.getTotalOpenBalance(); + if (newBalance == null) + newBalance = Env.ZERO; + if (payment.isReceipt()) + newBalance = newBalance.subtract(payAmt); + else + newBalance = newBalance.add(payAmt); + + bp.setTotalOpenBalance(newBalance); + bp.setSOCreditStatus(); + bp.saveEx(); + } + } + else if (MPayment.DOCACTION_Reverse_Accrual.equals(docAction) || MPayment.DOCACTION_Reverse_Correct.equals(docAction)) + { + if (payment.getC_BPartner_ID() != 0) + { + MBPartner bp = new MBPartner(payment.getCtx(), payment.getC_BPartner_ID(), payment.get_TrxName()); + bp.setTotalOpenBalance(); + bp.saveEx(payment.get_TrxName()); + } + } + return new CreditStatus(errorMsg, !Util.isEmpty(errorMsg)); + } // creditCheck +} diff --git a/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java b/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java index 94119801aa..8827077a5d 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java @@ -121,12 +121,12 @@ public class InOutTest extends AbstractTestCase { try { MOrder order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), Company_ConversionType_ID); BigDecimal qtyOrdered = new BigDecimal(500); - MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInAud); + MOrderLine orderLine = createOrderLine(order, 10, product, qtyOrdered, priceInAud); completeDocument(order); MInOut receipt = createMMReceipt(order, currentDate); BigDecimal qtyDelivered = new BigDecimal(500); - MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + MInOutLine receiptLine = createInOutLine(receipt, orderLine, qtyDelivered); completeDocument(receipt); postDocument(receipt); @@ -162,11 +162,11 @@ public class InOutTest extends AbstractTestCase { } order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), Spot_ConversionType_ID); - orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInAud); + orderLine = createOrderLine(order, 10, product, qtyOrdered, priceInAud); completeDocument(order); receipt = createMMReceipt(order, currentDate); - receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + receiptLine = createInOutLine(receipt, orderLine, qtyDelivered); completeDocument(receipt); postDocument(receipt); @@ -251,12 +251,12 @@ public class InOutTest extends AbstractTestCase { try { MOrder order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), Company_ConversionType_ID); BigDecimal qtyOrdered = BigDecimal.TEN; - MOrderLine orderLine = createPurchaseOrderLine(order, 10, product, qtyOrdered, priceInAud); + MOrderLine orderLine = createOrderLine(order, 10, product, qtyOrdered, priceInAud); completeDocument(order); MInOut receipt = createMMReceipt(order, currentDate); BigDecimal qtyDelivered = BigDecimal.TEN; - MInOutLine receiptLine = createMMReceiptLine(receipt, orderLine, qtyDelivered); + MInOutLine receiptLine = createInOutLine(receipt, orderLine, qtyDelivered); completeDocument(receipt); postDocument(receipt); @@ -384,22 +384,35 @@ public class InOutTest extends AbstractTestCase { ConversionRateHelper.deleteConversionRate(cr); } - private MOrder createPurchaseOrder(MBPartner bpartner, Timestamp date, int M_PriceList_ID, int C_ConversionType_ID) { + private MOrder createPurchaseOrder(MBPartner bpartner, Timestamp date, int M_PriceList_ID, int C_ConversionType_ID) + { + return createOrder(bpartner, date, M_PriceList_ID, C_ConversionType_ID, false); + } + + private MOrder createSalseOrder(MBPartner bpartner, Timestamp date, int M_PriceList_ID, int C_ConversionType_ID) + { + return createOrder(bpartner, date, M_PriceList_ID, C_ConversionType_ID, true); + } + + private MOrder createOrder(MBPartner bpartner, Timestamp date, int M_PriceList_ID, int C_ConversionType_ID, boolean isSOTrx) + { MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setAD_Org_ID(DictionaryIDs.AD_Org.HQ.id); order.setBPartner(bpartner); - order.setIsSOTrx(false); + order.setIsSOTrx(isSOTrx); order.setC_DocTypeTarget_ID(); order.setDateOrdered(date); order.setDateAcct(date); order.setM_PriceList_ID(M_PriceList_ID); order.setC_ConversionType_ID(C_ConversionType_ID); + order.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id); order.setDocStatus(DocAction.STATUS_Drafted); order.setDocAction(DocAction.ACTION_Complete); order.saveEx(); return order; } - private MOrderLine createPurchaseOrderLine(MOrder order, int line, MProduct product, BigDecimal qty, BigDecimal price) { + private MOrderLine createOrderLine(MOrder order, int line, MProduct product, BigDecimal qty, BigDecimal price) { MOrderLine orderLine = new MOrderLine(order); orderLine.setLine(line); orderLine.setProduct(product); @@ -415,13 +428,20 @@ public class InOutTest extends AbstractTestCase { return receipt; } - private MInOutLine createMMReceiptLine(MInOut receipt, MOrderLine orderLine, BigDecimal qty) { - MInOutLine receiptLine = new MInOutLine(receipt); + + private MInOut createShipment(MOrder order, Timestamp date) { + MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_SHIPMENT.id, date); // MM Shipment + receipt.saveEx(); + return receipt; + } + + private MInOutLine createInOutLine(MInOut mInOut, MOrderLine orderLine, BigDecimal qty) { + MInOutLine receiptLine = new MInOutLine(mInOut); receiptLine.setC_OrderLine_ID(orderLine.get_ID()); receiptLine.setLine(orderLine.getLine()); receiptLine.setProduct(orderLine.getProduct()); receiptLine.setQty(qty); - MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + MWarehouse wh = MWarehouse.get(Env.getCtx(), mInOut.getM_Warehouse_ID()); int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); receiptLine.setM_Locator_ID(M_Locator_ID); receiptLine.saveEx(); @@ -500,4 +520,46 @@ public class InOutTest extends AbstractTestCase { assertEquals(shipperAccount, inout.getShipperAccount(), "Unexpected shipper account"); assertEquals(MInOut.FREIGHTCHARGES_Collect, inout.getFreightCharges(), "Unexpected freight charges rule"); } + + /** + * Test cases for Credit Check + */ + @Test + public void testCreditCheckInOut() + { + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id, getTrxName()); + bpartner.setSOCreditStatus(MBPartner.SOCREDITSTATUS_NoCreditCheck); + bpartner.saveEx(); + + MProduct product = MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.ELM.id); + Timestamp currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + + MOrder order = createSalseOrder(bpartner, currentDate, DictionaryIDs.M_PriceList.STANDARD.id, DictionaryIDs.C_ConversionType.COMPANY.id); + MOrderLine orderLine = createOrderLine(order, 10, product, new BigDecimal(500), new BigDecimal(23.32)); + completeDocument(order); + + MInOut receipt = createShipment(order, currentDate); + BigDecimal qtyDelivered = new BigDecimal(500); + createInOutLine(receipt, orderLine, qtyDelivered); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Prepare); + receipt.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_InProgress, receipt.getDocStatus()); + + bpartner.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditStop); + bpartner.saveEx(); + + receipt.load(getTrxName()); + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, receipt.getDocStatus()); + + bpartner.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditHold); + bpartner.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, receipt.getDocStatus()); + } } diff --git a/org.idempiere.test/src/org/idempiere/test/base/InvoiceTest.java b/org.idempiere.test/src/org/idempiere/test/base/InvoiceTest.java new file mode 100644 index 0000000000..afa1257c1d --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/InvoiceTest.java @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright (C) 2016 Logilite Technologies LLP * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.idempiere.test.base; + +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 org.compiere.model.MBPartner; +import org.compiere.model.MInvoice; +import org.compiere.model.MInvoiceLine; +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.idempiere.test.DictionaryIDs; +import org.junit.jupiter.api.Test; + +/** + * Invoice Test cases. + * + * @author Logilite Technologies + * @since August 07, 2023 + */ +public class InvoiceTest extends AbstractTestCase +{ + + public InvoiceTest() + { + } + + /** + * Test cases for Credit Check + */ + @Test + public void testCreditCheckInvoice() + { + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + // Joe Block + MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id, getTrxName()); + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditStop); + bp.saveEx(); + + MInvoice invoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + invoice.setBPartner(bp); + invoice.setAD_Org_ID(DictionaryIDs.AD_Org.HQ.id); + invoice.setC_DocTypeTarget_ID(MInvoice.DOCBASETYPE_ARInvoice); + invoice.setDateInvoiced(today); + invoice.setDateAcct(today); + invoice.setM_PriceList_ID(DictionaryIDs.M_PriceList.STANDARD.id); + invoice.setPaymentRule(MInvoice.PAYMENTRULE_OnCredit); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(Env.getCtx(), 0, getTrxName()); + invoiceLine.setInvoice(invoice); + invoiceLine.setC_Invoice_ID(invoice.getC_Invoice_ID()); + invoiceLine.setAD_Org_ID(DictionaryIDs.AD_Org.HQ.id); + invoiceLine.setLine(10); + invoiceLine.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.FERTILIZER_50.id)); + invoiceLine.setQty(new BigDecimal("1")); + invoiceLine.saveEx(); + + invoice.load(getTrxName()); + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, invoice.getDocStatus()); + + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_NoCreditCheck); + bp.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/model/PaymentTest.java b/org.idempiere.test/src/org/idempiere/test/model/PaymentTest.java index 8897bbb9f2..b283a31a2a 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/PaymentTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/PaymentTest.java @@ -25,12 +25,21 @@ package org.idempiere.test.model; 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 org.compiere.model.MBPartner; import org.compiere.model.MPayment; +import org.compiere.process.DocAction; +import org.compiere.process.ProcessInfo; import org.compiere.util.DB; import org.compiere.util.Env; +import org.compiere.util.TimeUtil; import org.compiere.util.Util; +import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; import org.junit.jupiter.api.Test; @@ -63,4 +72,44 @@ public class PaymentTest extends AbstractTestCase { payment.saveEx(); assertTrue(Util.isEmpty(payment.getCreditCardVV()), "Credit card verification code not clear after change of tender type: "+payment.getCreditCardVV()); } + + /** + * Test cases for Credit Check + */ + @Test + public void testCreditCheckPayment() + { + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + // Joe Block + MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id, getTrxName()); + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditStop); + bp.saveEx(); + + MPayment payment = new MPayment(Env.getCtx(), 0, getTrxName()); + payment.setC_BPartner_ID(bp.getC_BPartner_ID()); + payment.setC_BankAccount_ID(DictionaryIDs.C_BankAccount.HQ_POS_CASH.id); + payment.setC_Currency_ID(DictionaryIDs.C_Currency.USD.id); + payment.setAD_Org_ID(DictionaryIDs.AD_Org.HQ.id); + payment.setC_DocType_ID(false); + payment.setDateTrx(today); + payment.setPayAmt(new BigDecimal(1000)); + payment.setDateAcct(today); + payment.saveEx(); + + payment.load(getTrxName()); + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, payment.getDocStatus()); + + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_NoCreditCheck); + bp.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, payment.getDocStatus()); + + info = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Reverse_Accrual); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, payment.getDocStatus()); + } } diff --git a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java index 4690096bd3..1bd392b4bc 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/SalesOrderTest.java @@ -1596,4 +1596,48 @@ public class SalesOrderTest extends AbstractTestCase { assertFalse(pi.isError(), pi.getSummary()); } + /** + * Test cases for Credit Check + */ + @Test + public void testCreditCheckOrder() + { + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + // Joe Block + MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id, getTrxName()); + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditHold); + bp.saveEx(); + + // test 2 - credit Check + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setM_Warehouse_ID(DictionaryIDs.AD_Org.HQ.id); + order.setBPartner(bp); + order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + order.setDeliveryRule(MOrder.DELIVERYRULE_Availability); + order.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Prepare); + order.setDatePromised(today); + order.saveEx(); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + // Azalea Bush + line1.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id)); + line1.setQty(new BigDecimal("1")); + line1.setDatePromised(today); + line1.saveEx(); + + order.load(getTrxName()); + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, order.getDocStatus()); + + bp.setSOCreditStatus(MBPartner.SOCREDITSTATUS_CreditStop); + bp.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Prepare); + assertTrue(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Invalid, order.getDocStatus()); + } }