This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 24bc7bb5f FINERACT-1905: New repayment strategy
24bc7bb5f is described below
commit 24bc7bb5fbee2e73639e7ba6ededcd7461140eef
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Mar 21 14:21:12 2023 +0100
FINERACT-1905: New repayment strategy
---
.../core/config/FineractProperties.java | 31 ++
.../portfolio/loanaccount/domain/LoanCharge.java | 18 +
.../LoanChargeEffectiveDueDateComparator.java | 45 +++
...tLoanRepaymentScheduleTransactionProcessor.java | 36 +-
.../LoanRepaymentScheduleTransactionProcessor.java | 2 +-
...eLoanRepaymentScheduleTransactionProcessor.java | 21 +-
...LoanRepaymentScheduleTransactionProcessor.java} | 129 +++++--
...tLoanRepaymentScheduleTransactionProcessor.java | 15 +-
...eLoanRepaymentScheduleTransactionProcessor.java | 20 +-
...yLoanRepaymentScheduleTransactionProcessor.java | 17 +-
...rLoanRepaymentScheduleTransactionProcessor.java | 18 +-
...rLoanRepaymentScheduleTransactionProcessor.java | 18 +-
...ILoanRepaymentScheduleTransactionProcessor.java | 18 +-
.../domain/AbstractLoanScheduleGenerator.java | 17 +-
...ymentScheduleTransactionProcessorCondition.java | 30 ++
...ymentScheduleTransactionProcessorCondition.java | 31 ++
...ymentScheduleTransactionProcessorCondition.java | 30 ++
...ymentScheduleTransactionProcessorCondition.java | 30 ++
...ymentScheduleTransactionProcessorCondition.java | 30 ++
.../starter/LoanAccountAutoStarter.java | 23 +-
...ymentScheduleTransactionProcessorCondition.java | 30 ++
...ymentScheduleTransactionProcessorCondition.java | 30 ++
...ymentScheduleTransactionProcessorCondition.java | 30 ++
.../src/main/resources/application.properties | 1 +
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../tenant/parts/0100_new_repayment_strategy.xml | 42 ++
.../LoanChargeEffectiveDueDateComparatorTest.java | 149 +++++++
.../src/test/resources/application-test.properties | 1 +
...DueDateRespectiveLoanRepaymentScheduleTest.java | 428 +++++++++++++++++++++
.../common/loans/LoanApplicationTestBuilder.java | 1 +
.../common/loans/LoanProductTestBuilder.java | 1 +
31 files changed, 1162 insertions(+), 131 deletions(-)
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<LoanCharge> {
+
+ 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<LoanCharge> loanFees = extractFeeCharges(charges);
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
@@ -462,7 +462,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
private Money processTransaction(final LoanTransaction loanTransaction,
final MonetaryCurrency currency,
- final List<LoanRepaymentScheduleInstallment> installments, Money
amountToProcess) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings);
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate,
- @SuppressWarnings("unused") final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments,
LoanTransaction loanTransaction, LocalDate transactionDate,
- Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
+ List<LoanRepaymentScheduleInstallment> installments,
LoanTransaction loanTransaction, Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> charges);
/**
* For normal on-time repayments.
*
* @param transactionMappings
* TODO
+ * @param charges
*/
protected abstract Money
handleTransactionThatIsOnTimePaymentOfInstallment(LoanRepaymentScheduleInstallment
currentInstallment,
LoanTransaction loanTransaction, Money
transactionAmountUnprocessed,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings);
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanTransaction>
transactionsPostDisbursement, final MonetaryCurrency currency,
- final List<LoanRepaymentScheduleInstallment> installments) {
+ final List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> 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<LoanRepaymentScheduleInstallment>
repaymentScheduleInstallments);
Money handleRepaymentSchedule(List<LoanTransaction>
transactionsPostDisbursement, MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments);
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate, final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed, final
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed, final
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java
similarity index 60%
copy from
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
copy to
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java
index b14158c23..7d5b9ec02 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/DueDateRespectiveLoanRepaymentScheduleTransactionProcessor.java
@@ -20,8 +20,11 @@ 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.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;
@@ -29,20 +32,18 @@ import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Abs
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
/**
- * Old style {@link 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 interest,
then principal.
- *
- * Other formulas exist on fineract where you can choose 'Declining-Balance
Interest Recalculation' which simply means,
- * recalculate the interest component based on the how much principal is
outstanding at a point in time; but this isnt
- * trying to model that option only the basic one for now.
+ * 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 FineractStyleLoanRepaymentScheduleTransactionProcessor extends
AbstractLoanRepaymentScheduleTransactionProcessor {
+public class DueDateRespectiveLoanRepaymentScheduleTransactionProcessor
extends AbstractLoanRepaymentScheduleTransactionProcessor {
- private static final String STRATEGY_CODE = "mifos-standard-strategy";
+ private static final String STRATEGY_CODE =
"due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy";
- private static final String STRATEGY_NAME = "Penalties, Fees, Interest,
Principal order";
+ private static final String STRATEGY_NAME = "Due penalty, fee, interest,
principal, In advance principal, penalty, fee, interest";
@Override
public String getCode() {
@@ -56,7 +57,7 @@ public class
FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int
currentInstallmentIndex,
- final List<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate, final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment =
installments.get(currentInstallmentIndex);
@@ -64,45 +65,46 @@ public class
FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
}
/**
- * For early/'in advance' repayments, pay off in the same way as on-time
payments, interest first then principal.
+ * For early/'in advance' repayments
*/
@Override
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final
LoanRepaymentScheduleInstallment currentInstallment,
- final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> charges) {
- return
handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, paymentInAdvance,
- transactionMappings);
+ return
handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, paymentInAdvance, transactionMappings,
+ charges);
}
/**
- * For late repayments, pay off in the same way as on-time payments,
interest first then principal.
+ * For late repayments
*/
@Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment(final
LoanRepaymentScheduleInstallment currentInstallment,
final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> charges) {
return
handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, transactionAmountUnprocessed,
- transactionMappings);
+ transactionMappings, charges);
}
/**
- * For normal on-time repayments, pays off interest first, then principal.
+ * For normal on-time repayments
*/
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final
LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money
transactionAmountUnprocessed,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> charges) {
final LocalDate transactionDate = loanTransaction.getTransactionDate();
+
final MonetaryCurrency currency =
transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
- Money principalPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money interestPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money feeChargesPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money penaltyChargesPortion =
Money.zero(transactionAmountRemaining.getCurrency());
+ 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,
@@ -128,18 +130,65 @@ public class
FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
}
loanTransaction.updateComponents(principalPortion,
interestPortion, feeChargesPortion, penaltyChargesPortion);
} else {
- penaltyChargesPortion =
currentInstallment.payPenaltyChargesComponent(transactionDate,
transactionAmountRemaining);
- transactionAmountRemaining =
transactionAmountRemaining.minus(penaltyChargesPortion);
-
- feeChargesPortion =
currentInstallment.payFeeChargesComponent(transactionDate,
transactionAmountRemaining);
- transactionAmountRemaining =
transactionAmountRemaining.minus(feeChargesPortion);
-
- interestPortion =
currentInstallment.payInterestComponent(transactionDate,
transactionAmountRemaining);
- transactionAmountRemaining =
transactionAmountRemaining.minus(interestPortion);
-
- principalPortion =
currentInstallment.payPrincipalComponent(transactionDate,
transactionAmountRemaining);
- transactionAmountRemaining =
transactionAmountRemaining.minus(principalPortion);
+ boolean ignoreDueDateCheck = false;
+ boolean rerun = false;
+
+ List<LoanCharge> 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())
{
@@ -162,10 +211,10 @@ public class
FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency =
transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed;
- Money principalPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money interestPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money feeChargesPortion =
Money.zero(transactionAmountRemaining.getCurrency());
- Money penaltyChargesPortion =
Money.zero(transactionAmountRemaining.getCurrency());
+ 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);
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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate, final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> charges) {
return
handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, transactionAmountUnprocessed,
- transactionMappings);
+ transactionMappings, charges);
}
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int
currentInstallmentIndex,
- final List<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate, final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LocalDate transactionDate, final Money transactionAmount) {
+ final List<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final LocalDate transactionDate, final Money paymentInAdvance,
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction, final Money paymentInAdvance,
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanRepaymentScheduleInstallment> installments, final
LoanTransaction loanTransaction,
- final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
+ final Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
+ Set<LoanCharge> 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<LoanTransactionToRepaymentScheduleMapping>
transactionMappings) {
+ final List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, Set<LoanCharge> 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<LoanTransaction> 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<LoanRepaymentScheduleInstallment> fetchRetainedInstallments(
+ private List<LoanRepaymentScheduleInstallment> fetchRetainedInstallments(
final List<LoanRepaymentScheduleInstallment>
repaymentScheduleInstallments, final LocalDate rescheduleFrom,
MonetaryCurrency currency) {
List<LoanRepaymentScheduleInstallment>
newRepaymentScheduleInstallments = new ArrayList<>();
@@ -2455,7 +2456,7 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
final LoanRepaymentScheduleTransactionProcessor
loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment>
newRepaymentScheduleInstallments, MonetaryCurrency currency,
final Map<LocalDate, Money> principalPortionMap,
LoanRepaymentScheduleInstallment installment,
- Collection<RecalculationDetail> applicableTransactions, Money
actualPrincipalPortion) {
+ Collection<RecalculationDetail> applicableTransactions, Money
actualPrincipalPortion, Set<LoanCharge> 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 @@
<include file="parts/0097_update_accounting_summary_table_reports.xml"
relativeToChangelogFile="true" />
<include file="parts/0098_update_transaction_summary_table_report.xml"
relativeToChangelogFile="true" />
<include
file="parts/0099_add_accrual_transaction_external_event_configuration.xml"
relativeToChangelogFile="true" />
+ <include file="parts/0100_new_repayment_strategy.xml"
relativeToChangelogFile="true" />
</databaseChangeLog>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <insert tableName="r_enum_value">
+ <column name="enum_name" value="loan_transaction_strategy_id"/>
+ <column name="enum_id" valueNumeric="8"/>
+ <column name="enum_message_property" value="Due penalty, fee,
interest, principal, In advance principal, penalty, fee, interest"/>
+ <column name="enum_value" value="Due penalty, fee, interest,
principal, In advance principal, penalty, fee, interest"/>
+ <column name="enum_type" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+ <changeSet id="2" author="fineract">
+ <insert tableName="ref_loan_transaction_processing_strategy">
+ <column name="id" valueNumeric="8"/>
+ <column name="code"
value="due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest-strategy"/>
+ <column name="name" value="Due penalty, fee, interest, principal,
In advance principal, penalty, fee, interest"/>
+ <column name="sort_order" valueNumeric="8"/>
+ </insert>
+ </changeSet>
+</databaseChangeLog>
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<LoanCharge> 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<LoanCharge> 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<LoanCharge> 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<String, Object> 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<String, Object> 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<String, Object> 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";