From 24bc7bb5fbee2e73639e7ba6ededcd7461140eef Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Tue, 21 Mar 2023 14:21:12 +0100 Subject: [PATCH] FINERACT-1905: New repayment strategy --- .../core/config/FineractProperties.java | 31 ++ .../loanaccount/domain/LoanCharge.java | 18 + .../LoanChargeEffectiveDueDateComparator.java | 45 ++ ...RepaymentScheduleTransactionProcessor.java | 36 +- ...RepaymentScheduleTransactionProcessor.java | 2 +- ...RepaymentScheduleTransactionProcessor.java | 21 +- ...RepaymentScheduleTransactionProcessor.java | 243 ++++++++++ ...RepaymentScheduleTransactionProcessor.java | 15 +- ...RepaymentScheduleTransactionProcessor.java | 20 +- ...RepaymentScheduleTransactionProcessor.java | 17 +- ...RepaymentScheduleTransactionProcessor.java | 18 +- ...RepaymentScheduleTransactionProcessor.java | 18 +- ...RepaymentScheduleTransactionProcessor.java | 18 +- .../domain/AbstractLoanScheduleGenerator.java | 17 +- ...ScheduleTransactionProcessorCondition.java | 30 ++ ...ScheduleTransactionProcessorCondition.java | 31 ++ ...ScheduleTransactionProcessorCondition.java | 30 ++ ...ScheduleTransactionProcessorCondition.java | 30 ++ ...ScheduleTransactionProcessorCondition.java | 30 ++ .../starter/LoanAccountAutoStarter.java | 23 +- ...ScheduleTransactionProcessorCondition.java | 30 ++ ...ScheduleTransactionProcessorCondition.java | 30 ++ ...ScheduleTransactionProcessorCondition.java | 30 ++ .../src/main/resources/application.properties | 1 + .../db/changelog/tenant/changelog-tenant.xml | 1 + .../parts/0100_new_repayment_strategy.xml | 42 ++ ...nChargeEffectiveDueDateComparatorTest.java | 149 ++++++ .../resources/application-test.properties | 1 + ...teRespectiveLoanRepaymentScheduleTest.java | 428 ++++++++++++++++++ .../loans/LoanApplicationTestBuilder.java | 1 + .../common/loans/LoanProductTestBuilder.java | 1 + 31 files changed, 1316 insertions(+), 91 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/CreocoreLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0100_new_repayment_strategy.xml create mode 100644 fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparatorTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 7348d52e2..0b667d65c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -60,6 +60,8 @@ public class FineractProperties { private FineractNotificationProperties notification; + private FineractLoanProperties loan; + @Getter @Setter public static class FineractTenantProperties { @@ -295,4 +297,33 @@ public class FineractProperties { private boolean enabled; } + + @Getter + @Setter + public static class FineractLoanProperties { + + private FineractTransactionProcessorProperties transactionProcessor; + } + + @Getter + @Setter + public static class FineractTransactionProcessorProperties { + + private FineractTransactionProcessorItemProperties creocore; + private FineractTransactionProcessorItemProperties earlyRepayment; + private FineractTransactionProcessorItemProperties mifosStandard; + private FineractTransactionProcessorItemProperties heavensFamily; + private FineractTransactionProcessorItemProperties interestPrincipalPenaltiesFees; + private FineractTransactionProcessorItemProperties principalInterestPenaltiesFees; + private FineractTransactionProcessorItemProperties rbiIndia; + private FineractTransactionProcessorItemProperties duePenaltyFeeInterestPrincipalInAdvancePrincipalPenaltyFeeInterest; + private boolean errorNotFoundFail; + } + + @Getter + @Setter + public static class FineractTransactionProcessorItemProperties { + + private boolean enabled; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 601c0bacd..eea10227a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.persistence.CascadeType; @@ -1006,6 +1007,23 @@ public class LoanCharge extends AbstractPersistableCustom { return ChargeTimeType.fromInt(this.chargeTime); } + /** + * Return the effective due date of the loan charge. For installment fee we are using the earliest not fully paid + * installment due date + * + * @return LocalDate + */ + public LocalDate getEffectiveDueDate() { + LocalDate dueDate; + if (Objects.requireNonNull(getChargeTimeType()) == ChargeTimeType.INSTALMENT_FEE) { + LoanInstallmentCharge firstUnpaidInstallment = getUnpaidInstallmentLoanCharge(); + dueDate = firstUnpaidInstallment != null ? firstUnpaidInstallment.getInstallment().getDueDate() : null; + } else { + dueDate = getDueLocalDate(); + } + return dueDate; + } + public LoanChargeData toData() { EnumOptionData chargeTimeTypeData = new EnumOptionData((long) getChargeTimeType().ordinal(), getChargeTimeType().getCode(), String.valueOf(getChargeTimeType().getValue())); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparator.java new file mode 100644 index 000000000..f5360d284 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparator.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import java.util.Comparator; + +/** + * Sort loan charges by effective due date + * + * Null values go to the end + */ +public final class LoanChargeEffectiveDueDateComparator implements Comparator { + + public static final LoanChargeEffectiveDueDateComparator INSTANCE = new LoanChargeEffectiveDueDateComparator(); + + private LoanChargeEffectiveDueDateComparator() {} + + @Override + public int compare(final LoanCharge o1, final LoanCharge o2) { + if (o1.getEffectiveDueDate() == null && o2.getEffectiveDueDate() != null) { + return 1; + } else if (o1.getEffectiveDueDate() == null) { + return 0; + } else if (o2.getEffectiveDueDate() == null) { + return -1; + } + return o1.getEffectiveDueDate().compareTo(o2.getEffectiveDueDate()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 6ee1af846..6994ed8bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -431,7 +431,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { loanTransaction.resetDerivedComponents(); } - Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess); + Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, charges, chargeAmountToProcess); final Set loanFees = extractFeeCharges(charges); final Set loanPenalties = extractPenaltyCharges(charges); @@ -462,7 +462,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } private Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, - final List installments, Money amountToProcess) { + final List installments, final Set charges, Money amountToProcess) { int installmentIndex = 0; final LocalDate transactionDate = loanTransaction.getTransactionDate(); @@ -479,20 +479,18 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen // is this transaction early/late/on-time with respect to // the // current installment? - if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, - transactionAmountUnprocessed)) { + if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate)) { transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment, - installments, loanTransaction, transactionDate, transactionAmountUnprocessed, transactionMappings); - } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, - loanTransaction.getTransactionDate())) { + installments, loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); + } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, transactionDate)) { // does this result in a late payment of existing // installment? transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments, - loanTransaction, transactionAmountUnprocessed, transactionMappings); + loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); } else { // standard transaction transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, - loanTransaction, transactionAmountUnprocessed, transactionMappings); + loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); } } } @@ -630,10 +628,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen * * @param transactionMappings * TODO + * @param charges */ protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment, List installments, LoanTransaction loanTransaction, Money transactionAmountUnprocessed, - List transactionMappings); + List transactionMappings, Set charges); /** * This method is responsible for checking if the current transaction is 'an advance/early payment' based on the @@ -642,8 +641,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen * Default implementation is check transaction date is before installment due date. */ protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate, - @SuppressWarnings("unused") final Money transactionAmount) { + final List installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); @@ -655,20 +653,22 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen * * @param transactionMappings * TODO + * @param charges */ protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(LoanRepaymentScheduleInstallment currentInstallment, - List installments, LoanTransaction loanTransaction, LocalDate transactionDate, - Money paymentInAdvance, List transactionMappings); + List installments, LoanTransaction loanTransaction, Money paymentInAdvance, + List transactionMappings, Set charges); /** * For normal on-time repayments. * * @param transactionMappings * TODO + * @param charges */ protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment, LoanTransaction loanTransaction, Money transactionAmountUnprocessed, - List transactionMappings); + List transactionMappings, Set charges); /** * Invoked when a transaction results in an over-payment of the full loan. @@ -682,7 +682,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen @Override public Money handleRepaymentSchedule(final List transactionsPostDisbursement, final MonetaryCurrency currency, - final List installments) { + final List installments, Set loanCharges) { Money unProcessed = Money.zero(currency); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { Money amountToProcess = null; @@ -690,9 +690,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen loanTransaction.resetDerivedComponents(); } if (loanTransaction.isInterestWaiver()) { - processTransaction(loanTransaction, currency, installments, amountToProcess); + processTransaction(loanTransaction, currency, installments, loanCharges, amountToProcess); } else { - unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess); + unProcessed = processTransaction(loanTransaction, currency, installments, loanCharges, amountToProcess); } } return unProcessed; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java index 41b051283..52dd30b4b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java @@ -46,7 +46,7 @@ public interface LoanRepaymentScheduleTransactionProcessor { List repaymentScheduleInstallments); Money handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, - List installments); + List installments, Set loanCharges); /** * Used in interest recalculation to introduce new interest only installment. diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java index d00d6f3ed..88719e6f5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -57,10 +59,9 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL /** * For creocore, early is defined as any date before the installment due date */ - @SuppressWarnings("unused") @Override protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate, final Money transactionAmount) { + final List installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); @@ -73,12 +74,11 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL @SuppressWarnings("unused") @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - final List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + final List transactionMappings, Set charges) { - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, - transactionMappings); + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); } /** @@ -88,10 +88,11 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, final List transactionMappings) { + final Money transactionAmountUnprocessed, final List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } /** @@ -100,7 +101,7 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - final List transactionMappings) { + final List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java new file mode 100644 index 000000000..7d5b9ec02 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java @@ -0,0 +1,243 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeEffectiveDueDateComparator; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; + +/** + * `First due/late charges, interest, principal, after in advance principal, charges, interest` style + * {@link LoanRepaymentScheduleTransactionProcessor}. + * + * For ALL types of transactions, pays off components in order of: Due/late penalty Due/late Fee Due/late interest + * Due/late principal In advance principal In advance penalty In advance fee In advance interest + */ +@SuppressWarnings("unused") +public class DueDateRespectiveLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor { + + private static final String STRATEGY_CODE = "due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy"; + + private static final String STRATEGY_NAME = "Due penalty, fee, interest, principal, In advance principal, penalty, fee, interest"; + + @Override + public String getCode() { + return STRATEGY_CODE; + } + + @Override + public String getName() { + return STRATEGY_NAME; + } + + @Override + protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, + final List installments, final LocalDate transactionDate) { + + final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); + + return transactionDate.isBefore(currentInstallment.getDueDate()); + } + + /** + * For early/'in advance' repayments + */ + @Override + protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { + + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); + } + + /** + * For late repayments + */ + @Override + protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, + transactionMappings, charges); + } + + /** + * For normal on-time repayments + */ + @Override + protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings, Set charges) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltyChargesPortion = Money.zero(currency); + + if (loanTransaction.isChargesWaiver()) { + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, + loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + + } else if (loanTransaction.isInterestWaiver()) { + interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } else if (loanTransaction.isChargePayment()) { + if (loanTransaction.isPenaltyPayment()) { + penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } else { + feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } else { + boolean ignoreDueDateCheck = false; + boolean rerun = false; + + List orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid) + .filter(loanCharge -> loanCharge.getEffectiveDueDate() == null + || !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) + .sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList(); + Money calculatedPenaltyCharge = Money.zero(currency); + Money calculatedFeeCharge = Money.zero(currency); + // Calculate the amount of due charges + for (LoanCharge charge : orderedLoanChargesByDueDate) { + if (charge.isPenaltyCharge()) { + calculatedPenaltyCharge = calculatedPenaltyCharge.add(charge.getAmount(currency)); + } else { + calculatedFeeCharge = calculatedFeeCharge.add(charge.getAmount(currency)); + } + } + + do { + Money subPenaltyPortion; + if (!ignoreDueDateCheck) { + if (calculatedPenaltyCharge.isGreaterThan(transactionAmountRemaining)) { + calculatedPenaltyCharge = transactionAmountRemaining; + } + } else { + calculatedPenaltyCharge = transactionAmountUnprocessed; + } + subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, calculatedPenaltyCharge); + transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion); + penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion); + + Money subFeePortion; + + if (!ignoreDueDateCheck) { + if (calculatedFeeCharge.isGreaterThan(transactionAmountRemaining)) { + calculatedFeeCharge = transactionAmountRemaining; + } + } else { + calculatedFeeCharge = transactionAmountUnprocessed; + } + subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, calculatedFeeCharge); + transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion); + feeChargesPortion = feeChargesPortion.add(subFeePortion); + + if (ignoreDueDateCheck || !transactionDate.isBefore(currentInstallment.getDueDate())) { + interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + } + + principalPortion = principalPortion + .add(currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining)); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + // If the transactionAmountRemaining is greater than zero, rerun the allocation without due date check + // to distribute the in advance portions + if (transactionAmountRemaining.isGreaterThanZero()) { + ignoreDueDateCheck = true; + } + rerun = !rerun; + } while (ignoreDueDateCheck && rerun); + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } + + @Override + protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { + // TODO - KW - dont do anything with loan over-payment for now + } + + @Override + protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltyChargesPortion = Money.zero(currency); + + principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + + if (transactionAmountRemaining.isGreaterThanZero()) { + interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java index f569e7278..eb3c4273e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -55,10 +57,10 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr @SuppressWarnings("unused") @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - final List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + final List transactionMappings, Set charges) { + final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = paymentInAdvance.getCurrency(); Money transactionAmountRemaining = paymentInAdvance; Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); @@ -112,10 +114,11 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } /** @@ -124,7 +127,7 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - final List transactionMappings) { + final List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java index b14158c23..11d73813b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -56,7 +58,7 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst @Override protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate, final Money transactionAmount) { + final List installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); @@ -68,12 +70,11 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst */ @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, - transactionMappings); + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); } /** @@ -82,10 +83,11 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } /** @@ -94,7 +96,7 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { + List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java index b752e0c97..3e327c5cd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -61,15 +63,16 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } @Override protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate, final Money transactionAmount) { + final List installments, final LocalDate transactionDate) { boolean isInAdvance = false; @@ -92,10 +95,10 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst */ @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - final List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + final List transactionMappings, Set charges) { + final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = paymentInAdvance.getCurrency(); Money transactionAmountRemaining = paymentInAdvance; Money principalPortion = Money.zero(currency); @@ -158,7 +161,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { + List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index 23b9d9648..05af5e8f3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -56,12 +58,11 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @SuppressWarnings("unused") @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, - transactionMappings); + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); } /** @@ -71,10 +72,11 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } /** @@ -83,7 +85,7 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { + List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index 725b1f774..898d909be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -56,12 +58,11 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @SuppressWarnings("unused") @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, - transactionMappings); + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); } /** @@ -71,10 +72,11 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings); + transactionMappings, charges); } /** @@ -83,7 +85,7 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { + List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java index c09200357..97de4da3b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java @@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; @@ -63,7 +65,7 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe @SuppressWarnings("unused") @Override protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate, final Money transactionAmount) { + final List installments, final LocalDate transactionDate) { final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); @@ -76,12 +78,11 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe @SuppressWarnings("unused") @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final LocalDate transactionDate, final Money paymentInAdvance, - List transactionMappings) { + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, - transactionMappings); + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); } /** @@ -90,7 +91,8 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe @Override protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings) { + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { // pay of overdue and current interest due given transaction date final LocalDate transactionDate = loanTransaction.getTransactionDate(); @@ -203,7 +205,7 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - final List transactionMappings) { + final List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 991e53d0c..796508e59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -505,7 +505,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // on scheduled installments to identify the // unprocessed(early payment ) amounts Money unprocessed = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, - scheduleParams.getInstallments()); + scheduleParams.getInstallments(), loanCharges); if (unprocessed.isGreaterThanZero()) { scheduleParams.reduceOutstandingBalance(unprocessed); @@ -805,7 +805,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // on scheduled installments to identify the // unprocessed(early payment ) amounts Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor() - .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments()); + .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments(), loanCharges); if (unprocessed.isGreaterThanZero()) { if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { @@ -850,7 +850,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (applicableDate.isBefore(scheduledDueDate)) { List currentTransactions = createCurrentTransactionList(detail); Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor() - .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments()); + .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments(), loanCharges); Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate); @@ -1377,7 +1377,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener startDate = transactionDate; additionalPeriodsStartDate = startDate; } - loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments()); + loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments(), + loanCharges); updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, lastRestDate, params); updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, params.getLatePaymentMap(), currentDate, @@ -2323,7 +2324,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, - installment, applicableTransactions, actualPrincipalPortion); + installment, applicableTransactions, actualPrincipalPortion, loan.getActiveCharges()); // this block is to adjust the period number based on the // actual @@ -2413,7 +2414,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); } - public List fetchRetainedInstallments( + private List fetchRetainedInstallments( final List repaymentScheduleInstallments, final LocalDate rescheduleFrom, MonetaryCurrency currency) { List newRepaymentScheduleInstallments = new ArrayList<>(); @@ -2455,7 +2456,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final List newRepaymentScheduleInstallments, MonetaryCurrency currency, final Map principalPortionMap, LoanRepaymentScheduleInstallment installment, - Collection applicableTransactions, Money actualPrincipalPortion) { + Collection applicableTransactions, Money actualPrincipalPortion, Set loanCharges) { Money unprocessed = Money.zero(currency); Money totalUnprocessed = Money.zero(currency); for (RecalculationDetail detail : applicableTransactions) { @@ -2467,7 +2468,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // on scheduled installments to identify the // unprocessed(early payment ) amounts loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, - newRepaymentScheduleInstallments); + newRepaymentScheduleInstallments, loanCharges); // Identifies totalEarlyPayment and early paid amount with this // transaction diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/CreocoreLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/CreocoreLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..2a2dd4f30 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/CreocoreLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class CreocoreLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getCreocore().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..7bead6d98 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getDuePenaltyFeeInterestPrincipalInAdvancePrincipalPenaltyFeeInterest() + .isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..e7ac158c6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getEarlyRepayment().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..9e681a40f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getHeavensFamily().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..cac74e1f8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getInterestPrincipalPenaltiesFees().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java index be365229f..8e04519cd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DueDateRespectiveLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor; @@ -29,55 +30,61 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class LoanAccountAutoStarter { @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.creocore.enabled") + @Conditional(CreocoreLoanRepaymentScheduleTransactionProcessorCondition.class) public CreocoreLoanRepaymentScheduleTransactionProcessor creocoreLoanRepaymentScheduleTransactionProcessor() { return new CreocoreLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.early-repayment.enabled") + @Conditional(EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.class) public EarlyPaymentLoanRepaymentScheduleTransactionProcessor earlyPaymentLoanRepaymentScheduleTransactionProcessor() { return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.mifos-standard.enabled") + @Conditional(MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.class) public FineractStyleLoanRepaymentScheduleTransactionProcessor fineractStyleLoanRepaymentScheduleTransactionProcessor() { return new FineractStyleLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.heavensfamily.enabled") + @Conditional(HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.class) public HeavensFamilyLoanRepaymentScheduleTransactionProcessor heavensFamilyLoanRepaymentScheduleTransactionProcessor() { return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.interest-principal-penalties-fees.enabled") + @Conditional(InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class) public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor interestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor() { return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.principal-interest-penalties-fees.enabled") + @Conditional(PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class) public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor principalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor() { return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(); } @Bean - @ConditionalOnProperty("fineract.loan.transactionprocessor.rbi-india.enabled") + @Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class) public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor() { return new RBILoanRepaymentScheduleTransactionProcessor(); } + @Bean + @Conditional(DueDateRespectiveLoanRepaymentScheduleTransactionProcessorCondition.class) + public DueDateRespectiveLoanRepaymentScheduleTransactionProcessor dueDateRespectiveTransactionProcessor() { + return new DueDateRespectiveLoanRepaymentScheduleTransactionProcessor(); + } + @Bean @ConditionalOnMissingBean(LoanRepaymentScheduleTransactionProcessorFactory.class) public LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory( diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..196cf2f93 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class MifosStandardLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getMifosStandard().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..2afa6cee6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getPrincipalInterestPenaltiesFees().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..88754617b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getRbiIndia().isEnabled(); + } +} diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 195496ba0..4521a4791 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -81,6 +81,7 @@ fineract.loan.transactionprocessor.heavensfamily.enabled=${FINERACT_LOAN_TRANSAC fineract.loan.transactionprocessor.interest-principal-penalties-fees.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_INTEREST_PRINCIPAL_PENALTIES_FEES_ENABLED:true} fineract.loan.transactionprocessor.principal-interest-penalties-fees.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_PRINCIPAL_INTEREST_PENALTIES_FEES_ENABLED:true} fineract.loan.transactionprocessor.rbi-india.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_RBI_INDIA_ENABLED:true} +fineract.loan.transactionprocessor.due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_ENABLED:true} fineract.loan.transactionprocessor.error-not-found-fail=${FINERACT_LOAN_TRANSACTIONPROCESSOR_ERROR_NOT_FOUND_FAIL:true} fineract.content.regex-whitelist-enabled=${FINERACT_CONTENT_REGEX_WHITELIST_ENABLED:true} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 8d74fd24a..bcc49f35a 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -119,4 +119,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0100_new_repayment_strategy.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0100_new_repayment_strategy.xml new file mode 100644 index 000000000..bc48a0c12 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0100_new_repayment_strategy.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparatorTest.java new file mode 100644 index 000000000..a3172411b --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanChargeEffectiveDueDateComparatorTest.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import static org.junit.Assert.assertEquals; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class LoanChargeEffectiveDueDateComparatorTest { + + @Test + public void testLoanChargeEffectiveDueDateComparator() { + LoanCharge lc1 = new LoanCharge(); + ReflectionTestUtils.setField(lc1, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc1, "dueDate", LocalDate.of(2023, 3, 17)); + + LoanCharge lc2 = new LoanCharge(); + ReflectionTestUtils.setField(lc2, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc2, "dueDate", LocalDate.of(2023, 3, 18)); + + LoanCharge lc3 = new LoanCharge(); + ReflectionTestUtils.setField(lc3, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc3, "dueDate", LocalDate.of(2023, 3, 16)); + + LoanCharge lc4 = new LoanCharge(); + LoanInstallmentCharge installmentCharge = new LoanInstallmentCharge(); + LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(); + ReflectionTestUtils.setField(installment, "dueDate", LocalDate.of(2023, 3, 15)); + ReflectionTestUtils.setField(installmentCharge, "installment", installment); + ReflectionTestUtils.setField(lc4, "chargeTime", ChargeTimeType.INSTALMENT_FEE.getValue()); + ReflectionTestUtils.setField(lc4, "loanInstallmentCharge", Set.of(installmentCharge)); + + List list = new ArrayList<>(); + list.add(lc1); + list.add(lc2); + list.add(lc3); + list.add(lc4); + list.sort(LoanChargeEffectiveDueDateComparator.INSTANCE); + + assertEquals(LocalDate.of(2023, 3, 15), list.get(0).getEffectiveDueDate()); + assertEquals(LocalDate.of(2023, 3, 16), list.get(1).getEffectiveDueDate()); + assertEquals(LocalDate.of(2023, 3, 17), list.get(2).getEffectiveDueDate()); + assertEquals(LocalDate.of(2023, 3, 18), list.get(3).getEffectiveDueDate()); + } + + @Test + public void testBothNull() { + LoanCharge lc1 = new LoanCharge(); + ReflectionTestUtils.setField(lc1, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc1, "dueDate", null); + + LoanCharge lc2 = new LoanCharge(); + ReflectionTestUtils.setField(lc2, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc2, "dueDate", null); + + List list = new ArrayList<>(); + list.add(lc1); + list.add(lc2); + list.sort(LoanChargeEffectiveDueDateComparator.INSTANCE); + + assertEquals(lc1, list.get(0)); + assertEquals(lc2, list.get(1)); + + list = new ArrayList<>(); + list.add(lc2); + list.add(lc1); + list.sort(LoanChargeEffectiveDueDateComparator.INSTANCE); + + assertEquals(lc2, list.get(0)); + assertEquals(lc1, list.get(1)); + } + + @Test + public void testOneOfThemIsNull() { + LoanCharge lc1 = new LoanCharge(); + ReflectionTestUtils.setField(lc1, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc1, "dueDate", null); + + LoanCharge lc2 = new LoanCharge(); + ReflectionTestUtils.setField(lc2, "chargeTime", ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); + ReflectionTestUtils.setField(lc2, "dueDate", LocalDate.of(2023, 3, 17)); + + LoanCharge lc3 = new LoanCharge(); + LoanInstallmentCharge installmentCharge1 = new LoanInstallmentCharge(); + LoanRepaymentScheduleInstallment installment1 = new LoanRepaymentScheduleInstallment(); + ReflectionTestUtils.setField(installment1, "dueDate", LocalDate.of(2023, 3, 15)); + ReflectionTestUtils.setField(installmentCharge1, "installment", installment1); + ReflectionTestUtils.setField(lc3, "chargeTime", ChargeTimeType.INSTALMENT_FEE.getValue()); + ReflectionTestUtils.setField(lc3, "loanInstallmentCharge", Set.of(installmentCharge1)); + + LoanCharge lc4 = new LoanCharge(); + LoanInstallmentCharge installmentCharge2 = new LoanInstallmentCharge(); + LoanRepaymentScheduleInstallment installment2 = new LoanRepaymentScheduleInstallment(); + ReflectionTestUtils.setField(installment2, "dueDate", null); + ReflectionTestUtils.setField(installmentCharge2, "paid", Boolean.TRUE); + ReflectionTestUtils.setField(installmentCharge2, "installment", installment2); + ReflectionTestUtils.setField(lc4, "chargeTime", ChargeTimeType.INSTALMENT_FEE.getValue()); + ReflectionTestUtils.setField(lc4, "loanInstallmentCharge", Set.of(installmentCharge2)); + + List list = new ArrayList<>(); + list.add(lc1); + list.add(lc2); + list.add(lc3); + list.add(lc4); + list.sort(LoanChargeEffectiveDueDateComparator.INSTANCE); + + assertEquals(lc3, list.get(0)); + assertEquals(lc2, list.get(1)); + assertEquals(lc1, list.get(2)); + assertEquals(lc4, list.get(3)); + + // Reversed order + list = new ArrayList<>(); + list.add(lc4); + list.add(lc3); + list.add(lc2); + list.add(lc1); + list.sort(LoanChargeEffectiveDueDateComparator.INSTANCE); + + assertEquals(lc3, list.get(0)); + assertEquals(lc2, list.get(1)); + assertEquals(lc4, list.get(2)); + assertEquals(lc1, list.get(3)); + + } + +} diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 459034b08..cc3993557 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -60,6 +60,7 @@ fineract.loan.transactionprocessor.heavensfamily.enabled=true fineract.loan.transactionprocessor.interest-principal-penalties-fees.enabled=true fineract.loan.transactionprocessor.principal-interest-penalties-fees.enabled=true fineract.loan.transactionprocessor.rbi-india.enabled=true +fineract.loan.transactionprocessor.due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest.enabled=true fineract.loan.transactionprocessor.error-not-found-fail=true fineract.content.regex-whitelist-enabled=true diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java new file mode 100644 index 000000000..1bb1aca81 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DueDateRespectiveLoanRepaymentScheduleTest.java @@ -0,0 +1,428 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.HashMap; +import org.apache.fineract.client.models.BusinessDateRequest; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.integrationtests.common.BusinessDateHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.AccountHelper; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DueDateRespectiveLoanRepaymentScheduleTest { + + private static final Logger LOG = LoggerFactory.getLogger(DueDateRespectiveLoanRepaymentScheduleTest.class); + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private BusinessDateHelper businessDateHelper; + private LoanTransactionHelper loanTransactionHelper; + + private AccountHelper accountHelper; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.requestSpec.header("Fineract-Platform-TenantId", "default"); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.businessDateHelper = new BusinessDateHelper(); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + } + + // Scenario1: + // 1. Disburse the loan + // 2. Adding a partial repayment + // 3. Adding a charge + // 3.1 No reverse-replay + // 4 Adding a partial repayment + // 4.1 Paying only principal portion + // 4.2 Adding a partial repayment + // 4.3 Paying only charge portion + @Test + public void scenario1() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("2023.02.01").dateFormat("yyyy.MM.dd").locale("en")); + + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", true)); + final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", + LoanProductTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); + + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", + LoanApplicationTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, "01 January 2023", "01 January 2023"); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.approveLoan("01 January 2023", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000"); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + Integer firstRepaymentId = (Integer) loanTransactionHelper.makeRepayment("10 January 2023", Float.parseFloat("500.00"), loanID) + .get("resourceId"); + Integer firstChargeId = loanTransactionHelper.addChargesForLoan(loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "20 January 2023", "50")); + Integer secondRepaymentId = (Integer) loanTransactionHelper.makeRepayment("17 January 2023", Float.parseFloat("450.00"), loanID) + .get("resourceId"); + + Integer thirdRepaymentId = (Integer) loanTransactionHelper.makeRepayment("21 January 2023", Float.parseFloat("50.00"), loanID) + .get("resourceId"); + + GetLoansLoanIdResponse response = loanTransactionHelper.getLoanDetails((long) loanID); + assertEquals(50.0, response.getSummary().getTotalOutstanding()); + assertEquals(50.0, response.getRepaymentSchedule().getTotalOutstanding()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding()); + assertEquals(1000.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue()); + assertEquals(950.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding()); + assertTrue(response.getStatus().getActive()); + + assertEquals(firstRepaymentId, response.getTransactions().get(1).getId().intValue()); + assertNull(response.getTransactions().get(1).getReversedOnDate()); + assertTrue(response.getTransactions().get(1).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(1).getType().getRepayment()); + assertEquals(500.0, response.getTransactions().get(1).getAmount()); + assertEquals(500.0, response.getTransactions().get(1).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(1).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(1).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(1).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(1).getFeeChargesPortion()); + assertEquals(500.0, response.getTransactions().get(1).getOutstandingLoanBalance()); + assertEquals(secondRepaymentId, response.getTransactions().get(2).getId().intValue()); + assertNull(response.getTransactions().get(2).getReversedOnDate()); + assertTrue(response.getTransactions().get(2).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(2).getType().getRepayment()); + assertEquals(450.0, response.getTransactions().get(2).getAmount()); + assertEquals(450.0, response.getTransactions().get(2).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(2).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(2).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(2).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(2).getFeeChargesPortion()); + assertEquals(50.0, response.getTransactions().get(2).getOutstandingLoanBalance()); + assertEquals(thirdRepaymentId, response.getTransactions().get(3).getId().intValue()); + assertNull(response.getTransactions().get(3).getReversedOnDate()); + assertTrue(response.getTransactions().get(3).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(3).getType().getRepayment()); + assertEquals(50.0, response.getTransactions().get(3).getAmount()); + assertEquals(0.0, response.getTransactions().get(3).getPrincipalPortion()); + assertEquals(50.0, response.getTransactions().get(3).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(3).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(3).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(3).getFeeChargesPortion()); + assertEquals(50.0, response.getTransactions().get(3).getOutstandingLoanBalance()); + assertEquals(firstChargeId, response.getTransactions().get(3).getLoanChargePaidByList().get(0).getChargeId().intValue()); + assertEquals(1, response.getTransactions().get(3).getLoanChargePaidByList().size()); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + + // Scenario2: + // 1. Disburse the loan + // 2. Adding a partial repayment + // 3. Adding a charge + // 3.1 No reverse-replay + // 4. Adding a partial repayment + // 4.1 Paying only principal portion + // 5. Adding a charge + // 5.1 No any reverse-replay + // 6. Adding a partial repayment + // 6.1 Paying the 1st charge + // 6.2 Paying secondly the in advance principal + // 6.3 Not paying the 2nd charge (id: #5) + @Test + public void scenario2() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("2023.02.01").dateFormat("yyyy.MM.dd").locale("en")); + + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", true)); + + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); + final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", + LoanProductTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); + + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", + LoanApplicationTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, "01 January 2023", "01 January 2023"); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.approveLoan("01 January 2023", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000"); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + Integer firstRepaymentId = (Integer) loanTransactionHelper.makeRepayment("10 January 2023", Float.parseFloat("500.00"), loanID) + .get("resourceId"); + Integer firstChargeId = loanTransactionHelper.addChargesForLoan(loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "20 January 2023", "50")); + Integer secondRepaymentId = (Integer) loanTransactionHelper.makeRepayment("17 January 2023", Float.parseFloat("100.00"), loanID) + .get("resourceId"); + + Integer secondChargeId = loanTransactionHelper.addChargesForLoan(loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), "23 January 2023", "10")); + + Integer thirdRepaymentId = (Integer) loanTransactionHelper.makeRepayment("20 January 2023", Float.parseFloat("100.00"), loanID) + .get("resourceId"); + + GetLoansLoanIdResponse response = loanTransactionHelper.getLoanDetails((long) loanID); + assertEquals(360.0, response.getSummary().getTotalOutstanding()); + assertEquals(360.0, response.getRepaymentSchedule().getTotalOutstanding()); + assertEquals(10.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid()); + assertEquals(10.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding()); + assertEquals(1000.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue()); + assertEquals(650.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid()); + assertEquals(350.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding()); + assertTrue(response.getStatus().getActive()); + + assertEquals(firstRepaymentId, response.getTransactions().get(1).getId().intValue()); + assertNull(response.getTransactions().get(1).getReversedOnDate()); + assertTrue(response.getTransactions().get(1).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(1).getType().getRepayment()); + assertEquals(500.0, response.getTransactions().get(1).getAmount()); + assertEquals(500.0, response.getTransactions().get(1).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(1).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(1).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(1).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(1).getFeeChargesPortion()); + assertEquals(500.0, response.getTransactions().get(1).getOutstandingLoanBalance()); + assertEquals(secondRepaymentId, response.getTransactions().get(2).getId().intValue()); + assertNull(response.getTransactions().get(2).getReversedOnDate()); + assertTrue(response.getTransactions().get(2).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(2).getType().getRepayment()); + assertEquals(100.0, response.getTransactions().get(2).getAmount()); + assertEquals(100.0, response.getTransactions().get(2).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(2).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(2).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(2).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(2).getFeeChargesPortion()); + assertEquals(400.0, response.getTransactions().get(2).getOutstandingLoanBalance()); + assertEquals(thirdRepaymentId, response.getTransactions().get(3).getId().intValue()); + assertNull(response.getTransactions().get(3).getReversedOnDate()); + assertTrue(response.getTransactions().get(3).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(3).getType().getRepayment()); + assertEquals(100.0, response.getTransactions().get(3).getAmount()); + assertEquals(50.0, response.getTransactions().get(3).getPrincipalPortion()); + assertEquals(50.0, response.getTransactions().get(3).getFeeChargesPortion()); + assertEquals(0.0, response.getTransactions().get(3).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(3).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(3).getPenaltyChargesPortion()); + assertEquals(350.0, response.getTransactions().get(3).getOutstandingLoanBalance()); + assertEquals(firstChargeId, response.getTransactions().get(3).getLoanChargePaidByList().get(0).getChargeId().intValue()); + assertEquals(1, response.getTransactions().get(3).getLoanChargePaidByList().size()); + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + + // Scenario3: + // 1. Disburse the loan + // 2. Adding a partial repayment + // 3. Adding a charge + // 3.1 No reverse-replay + // 4. Adding a full repayment + // 4.1 Paying first the in advanced principal portion, and after the in advanced charges + @Test + public void scenario3() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("2023.02.01").dateFormat("yyyy.MM.dd").locale("en")); + + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); + final Integer loanProductID = createLoanProductWithNoAccountingNoInterest("1000", "1", "1", "0", + LoanProductTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2023"); + + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "1", "1", "1", "0", + LoanApplicationTestBuilder.DUE_DATE_RESPECTIVE_STRATEGY, "01 January 2023", "01 January 2023"); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.approveLoan("01 January 2023", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.disburseLoanWithTransactionAmount("01 January 2023", loanID, "1000"); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + Integer firstRepaymentId = (Integer) loanTransactionHelper.makeRepayment("10 January 2023", Float.parseFloat("500.00"), loanID) + .get("resourceId"); + Integer firstChargeId = loanTransactionHelper.addChargesForLoan(loanID, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "20 January 2023", "50")); + Integer secondRepaymentId = (Integer) loanTransactionHelper.makeRepayment("17 January 2023", Float.parseFloat("550.00"), loanID) + .get("resourceId"); + + GetLoansLoanIdResponse response = loanTransactionHelper.getLoanDetails((long) loanID); + assertEquals(0.0, response.getSummary().getTotalOutstanding()); + assertEquals(0.0, response.getRepaymentSchedule().getTotalOutstanding()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesDue()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesPaid()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPenaltyChargesOutstanding()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesDue()); + assertEquals(50.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesPaid()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getFeeChargesOutstanding()); + assertEquals(1000.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue()); + assertEquals(1000.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid()); + assertEquals(0.0, response.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding()); + assertTrue(response.getStatus().getClosedObligationsMet()); + + assertEquals(firstRepaymentId, response.getTransactions().get(1).getId().intValue()); + assertNull(response.getTransactions().get(1).getReversedOnDate()); + assertTrue(response.getTransactions().get(1).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(1).getType().getRepayment()); + assertEquals(500.0, response.getTransactions().get(1).getAmount()); + assertEquals(500.0, response.getTransactions().get(1).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(1).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(1).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(1).getInterestPortion()); + assertEquals(0.0, response.getTransactions().get(1).getFeeChargesPortion()); + assertEquals(500.0, response.getTransactions().get(1).getOutstandingLoanBalance()); + + int repaymentOrderNo; + int accrualOrderNo; + + if (response.getTransactions().get(2).getType().getAccrual()) { + accrualOrderNo = 2; + repaymentOrderNo = 3; + } else { + accrualOrderNo = 3; + repaymentOrderNo = 2; + } + + assertNull(response.getTransactions().get(accrualOrderNo).getReversedOnDate()); + assertTrue(response.getTransactions().get(accrualOrderNo).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(accrualOrderNo).getType().getAccrual()); + assertEquals(50.0, response.getTransactions().get(accrualOrderNo).getAmount()); + assertEquals(0.0, response.getTransactions().get(accrualOrderNo).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(accrualOrderNo).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(accrualOrderNo).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(accrualOrderNo).getInterestPortion()); + assertEquals(50.0, response.getTransactions().get(accrualOrderNo).getFeeChargesPortion()); + assertEquals(0.0, response.getTransactions().get(accrualOrderNo).getOutstandingLoanBalance()); + assertEquals(firstChargeId, + response.getTransactions().get(accrualOrderNo).getLoanChargePaidByList().get(0).getChargeId().intValue()); + assertEquals(1, response.getTransactions().get(accrualOrderNo).getLoanChargePaidByList().size()); + + assertEquals(secondRepaymentId, response.getTransactions().get(repaymentOrderNo).getId().intValue()); + assertNull(response.getTransactions().get(repaymentOrderNo).getReversedOnDate()); + assertTrue(response.getTransactions().get(repaymentOrderNo).getTransactionRelations().isEmpty()); + assertTrue(response.getTransactions().get(repaymentOrderNo).getType().getRepayment()); + assertEquals(550.0, response.getTransactions().get(repaymentOrderNo).getAmount()); + assertEquals(500.0, response.getTransactions().get(repaymentOrderNo).getPrincipalPortion()); + assertEquals(0.0, response.getTransactions().get(repaymentOrderNo).getPenaltyChargesPortion()); + assertEquals(0.0, response.getTransactions().get(repaymentOrderNo).getOverpaymentPortion()); + assertEquals(0.0, response.getTransactions().get(repaymentOrderNo).getInterestPortion()); + assertEquals(50.0, response.getTransactions().get(repaymentOrderNo).getFeeChargesPortion()); + assertEquals(0.0, response.getTransactions().get(repaymentOrderNo).getOutstandingLoanBalance()); + assertEquals(firstChargeId, + response.getTransactions().get(repaymentOrderNo).getLoanChargePaidByList().get(0).getChargeId().intValue()); + assertEquals(1, response.getTransactions().get(repaymentOrderNo).getLoanChargePaidByList().size()); + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + + private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String principal, + final String loanTermFrequency, final String repaymentAfterEvery, final String numberOfRepayments, final String interestRate, + final String repaymentStrategy, final String expectedDisbursementDate, final String submittedOnDate) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principal) + .withLoanTermFrequency(loanTermFrequency).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments) + .withRepaymentEveryAfter(repaymentAfterEvery).withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod(interestRate) + .withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments().withRepaymentStrategy(repaymentStrategy) + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate(expectedDisbursementDate) + .withSubmittedOnDate(submittedOnDate).withLoanType("individual").build(clientID.toString(), loanProductID.toString(), null); + return loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private Integer createLoanProductWithNoAccountingNoInterest(final String principal, final String repaymentAfterEvery, + final String numberOfRepayments, final String interestRate, final String repaymentStrategy, final Account... accounts) { + LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(principal).withRepaymentTypeAsMonth() + .withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments).withRepaymentTypeAsMonth() + .withinterestRatePerPeriod(interestRate).withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy) + .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts) + .withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0").build(null); + return loanTransactionHelper.getLoanProductId(loanProductJSON); + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index aaedefa19..3fd737bda 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -42,6 +42,7 @@ public class LoanApplicationTestBuilder { public static final String DEFAULT_STRATEGY = "mifos-standard-strategy"; public static final String RBI_INDIA_STRATEGY = "rbi-india-strategy"; public static final String INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY = "interest-principal-penalties-fees-order-strategy"; + public static final String DUE_DATE_RESPECTIVE_STRATEGY = "due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy"; private String externalId = null; private String principal = "10,000"; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index d5f9fb637..1c4a38e04 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -42,6 +42,7 @@ public class LoanProductTestBuilder { private static final String FLAT_BALANCE = "1"; public static final String DEFAULT_STRATEGY = "mifos-standard-strategy"; public static final String INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY = "interest-principal-penalties-fees-order-strategy"; + public static final String DUE_DATE_RESPECTIVE_STRATEGY = "due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy"; // private static final String HEAVENS_FAMILY_STRATEGY ="heavensfamily-strategy"; // private static final String CREO_CORE_STRATEGY ="creocore-strategy"; public static final String RBI_INDIA_STRATEGY = "rbi-india-strategy";