FINERACT-2114: Interest rate modification - Advanced payment allocation processor support
This commit is contained in:
committed by
Adam Saghy
parent
3e64cc1536
commit
94360c9f8a
+5
@@ -79,6 +79,11 @@ public final class DateUtils {
|
|||||||
return truncate == null ? now : now.truncatedTo(truncate);
|
return truncate == null ? now : now.truncatedTo(truncate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static OffsetDateTime getOffsetDateTimeOfTenantFromLocalDate(@NotNull final LocalDate date) {
|
||||||
|
return OffsetDateTime.of(date.atStartOfDay(), getOffsetDateTimeOfTenant().getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
public static LocalDateTime getLocalDateTimeOfSystem() {
|
public static LocalDateTime getLocalDateTimeOfSystem() {
|
||||||
return getLocalDateTimeOfSystem(null);
|
return getLocalDateTimeOfSystem(null);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -26,9 +26,11 @@ public enum LoanTermVariationType {
|
|||||||
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
|
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
|
||||||
DUE_DATE(4, "loanTermType.dueDate"), //
|
DUE_DATE(4, "loanTermType.dueDate"), //
|
||||||
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
|
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
|
||||||
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"), GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"), GRACE_ON_PRINCIPAL(8,
|
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment"), //
|
||||||
"loanTermType.graceOnPrincipal"), EXTEND_REPAYMENT_PERIOD(9,
|
GRACE_ON_INTEREST(7, "loanTermType.graceOnInterest"), //
|
||||||
"loanTermType.extendRepaymentPeriod"), INTEREST_RATE_FROM_INSTALLMENT(10, "loanTermType.interestRateFromInstallment");
|
GRACE_ON_PRINCIPAL(8, "loanTermType.graceOnPrincipal"), //
|
||||||
|
EXTEND_REPAYMENT_PERIOD(9, "loanTermType.extendRepaymentPeriod"), //
|
||||||
|
INTEREST_RATE_FROM_INSTALLMENT(10, "loanTermType.interestRateFromInstallment"); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|||||||
+1
-1
@@ -478,7 +478,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
&& disbursementDate.equals(loanRepaymentScheduleInstallment.getObligationsMetOnDate());
|
&& disbursementDate.equals(loanRepaymentScheduleInstallment.getObligationsMetOnDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isNotObligationsMet(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
|
protected boolean isNotObligationsMet(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
|
||||||
return !loanRepaymentScheduleInstallment.isObligationsMet() && loanRepaymentScheduleInstallment.getObligationsMetOnDate() == null;
|
return !loanRepaymentScheduleInstallment.isObligationsMet() && loanRepaymentScheduleInstallment.getObligationsMetOnDate() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
-15
@@ -63,6 +63,8 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -71,6 +73,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||||
@@ -169,8 +172,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
currentInstallment.updateObligationsMet(currency, disbursementDate);
|
currentInstallment.updateObligationsMet(currency, disbursementDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ChargeOrTransaction> chargeOrTransactions = createSortedChargesAndTransactionsList(loanTransactions, charges);
|
|
||||||
|
|
||||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
|
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
|
||||||
final Loan loan = loanTransactions.get(0).getLoan();
|
final Loan loan = loanTransactions.get(0).getLoan();
|
||||||
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||||
@@ -180,17 +181,26 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
||||||
changedTransactionDetail, scheduleModel);
|
changedTransactionDetail, scheduleModel);
|
||||||
|
|
||||||
|
LoanTermVariationsDataWrapper loanTermVariations = Optional
|
||||||
|
.ofNullable(loan.getActiveLoanTermVariations()).map(loanTermVariationsSet -> loanTermVariationsSet.stream()
|
||||||
|
.map(LoanTermVariations::toData).collect(Collectors.toCollection(ArrayList::new)))
|
||||||
|
.map(LoanTermVariationsDataWrapper::new).orElse(null);
|
||||||
|
List<ChangeOperation> changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges);
|
||||||
|
|
||||||
List<LoanTransaction> overpaidTransactions = new ArrayList<>();
|
List<LoanTransaction> overpaidTransactions = new ArrayList<>();
|
||||||
for (final ChargeOrTransaction chargeOrTransaction : chargeOrTransactions) {
|
for (final ChangeOperation changeOperation : changeOperations) {
|
||||||
if (chargeOrTransaction.isTransaction()) {
|
if (changeOperation.isInterestRateChange()) {
|
||||||
LoanTransaction transaction = chargeOrTransaction.getLoanTransaction().get();
|
final LoanTermVariationsData interestRateChange = changeOperation.getInterestRateChange().get();
|
||||||
|
processInterestRateChange(installments, interestRateChange, scheduleModel);
|
||||||
|
} else if (changeOperation.isTransaction()) {
|
||||||
|
LoanTransaction transaction = changeOperation.getLoanTransaction().get();
|
||||||
processSingleTransaction(transaction, ctx);
|
processSingleTransaction(transaction, ctx);
|
||||||
transaction = getProcessedTransaction(changedTransactionDetail, transaction);
|
transaction = getProcessedTransaction(changedTransactionDetail, transaction);
|
||||||
if (transaction.isOverPaid() && transaction.isRepaymentLikeType()) { // TODO CREDIT, DEBIT
|
if (transaction.isOverPaid() && transaction.isRepaymentLikeType()) { // TODO CREDIT, DEBIT
|
||||||
overpaidTransactions.add(transaction);
|
overpaidTransactions.add(transaction);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LoanCharge loanCharge = chargeOrTransaction.getLoanCharge().get();
|
LoanCharge loanCharge = changeOperation.getLoanCharge().get();
|
||||||
processSingleCharge(loanCharge, currency, installments, disbursementDate);
|
processSingleCharge(loanCharge, currency, installments, disbursementDate);
|
||||||
if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) {
|
if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) {
|
||||||
overpaidTransactions = processOverpaidTransactions(overpaidTransactions, currency, installments, charges,
|
overpaidTransactions = processOverpaidTransactions(overpaidTransactions, currency, installments, charges,
|
||||||
@@ -205,13 +215,37 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
createNewTransaction(oldTransaction, newTransaction, ctx);
|
createNewTransaction(oldTransaction, newTransaction, ctx);
|
||||||
}
|
}
|
||||||
recalculateInterestForDate(ThreadLocalContextUtil.getBusinessDate(), ctx);
|
recalculateInterestForDate(ThreadLocalContextUtil.getBusinessDate(), ctx);
|
||||||
List<LoanTransaction> txs = chargeOrTransactions.stream() //
|
List<LoanTransaction> txs = changeOperations.stream() //
|
||||||
.filter(ChargeOrTransaction::isTransaction) //
|
.filter(ChangeOperation::isTransaction) //
|
||||||
.map(e -> e.getLoanTransaction().get()).toList();
|
.map(e -> e.getLoanTransaction().get()).toList();
|
||||||
reprocessInstallments(disbursementDate, txs, installments, currency);
|
reprocessInstallments(disbursementDate, txs, installments, currency);
|
||||||
return Pair.of(changedTransactionDetail, scheduleModel);
|
return Pair.of(changedTransactionDetail, scheduleModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processInterestRateChange(final List<LoanRepaymentScheduleInstallment> installments,
|
||||||
|
final LoanTermVariationsData interestRateChange, final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||||
|
final LocalDate interestRateChangeSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
||||||
|
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
||||||
|
emiCalculator.changeInterestRate(scheduleModel, interestRateChangeSubmittedOnDate, newInterestRate);
|
||||||
|
processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processInterestRateChangeOnInstallments(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||||
|
final LocalDate interestRateChangeSubmittedOnDate, final List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
installments.stream() //
|
||||||
|
.filter(installment -> isNotObligationsMet(installment)
|
||||||
|
&& !interestRateChangeSubmittedOnDate.isAfter(installment.getDueDate()))
|
||||||
|
.forEach(installment -> updateInstallmentIfInterestPeriodPresent(scheduleModel, installment)); //
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInstallmentIfInterestPeriodPresent(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||||
|
final LoanRepaymentScheduleInstallment installment) {
|
||||||
|
emiCalculator.findRepaymentPeriod(scheduleModel, installment.getDueDate()).ifPresent(interestRepaymentPeriod -> {
|
||||||
|
installment.updateInterestCharged(interestRepaymentPeriod.getDueInterest().getAmount());
|
||||||
|
installment.updatePrincipal(interestRepaymentPeriod.getDuePrincipal().getAmount());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
||||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||||
@@ -736,17 +770,20 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private List<ChargeOrTransaction> createSortedChargesAndTransactionsList(List<LoanTransaction> loanTransactions,
|
private List<ChangeOperation> createSortedChangeList(final LoanTermVariationsDataWrapper loanTermVariations,
|
||||||
Set<LoanCharge> charges) {
|
final List<LoanTransaction> loanTransactions, final Set<LoanCharge> charges) {
|
||||||
List<ChargeOrTransaction> chargeOrTransactions = new ArrayList<>();
|
List<ChangeOperation> changeOperations = new ArrayList<>();
|
||||||
|
if (loanTermVariations != null && !loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
|
||||||
|
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
|
||||||
|
}
|
||||||
if (charges != null) {
|
if (charges != null) {
|
||||||
chargeOrTransactions.addAll(charges.stream().map(ChargeOrTransaction::new).toList());
|
changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList());
|
||||||
}
|
}
|
||||||
if (loanTransactions != null) {
|
if (loanTransactions != null) {
|
||||||
chargeOrTransactions.addAll(loanTransactions.stream().map(ChargeOrTransaction::new).toList());
|
changeOperations.addAll(loanTransactions.stream().map(ChangeOperation::new).toList());
|
||||||
}
|
}
|
||||||
Collections.sort(chargeOrTransactions);
|
Collections.sort(changeOperations);
|
||||||
return chargeOrTransactions;
|
return changeOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||||
|
|||||||
+37
-17
@@ -24,26 +24,40 @@ import java.time.OffsetDateTime;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
public class ChangeOperation implements Comparable<ChangeOperation> {
|
||||||
|
|
||||||
|
private final Optional<LoanTermVariationsData> interestRateChange;
|
||||||
private final Optional<LoanCharge> loanCharge;
|
private final Optional<LoanCharge> loanCharge;
|
||||||
private final Optional<LoanTransaction> loanTransaction;
|
private final Optional<LoanTransaction> loanTransaction;
|
||||||
|
|
||||||
public ChargeOrTransaction(LoanCharge loanCharge) {
|
public ChangeOperation(LoanCharge loanCharge) {
|
||||||
|
this.interestRateChange = Optional.empty();
|
||||||
this.loanCharge = Optional.of(loanCharge);
|
this.loanCharge = Optional.of(loanCharge);
|
||||||
this.loanTransaction = Optional.empty();
|
this.loanTransaction = Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChargeOrTransaction(LoanTransaction loanTransaction) {
|
public ChangeOperation(LoanTransaction loanTransaction) {
|
||||||
|
this.interestRateChange = Optional.empty();
|
||||||
this.loanTransaction = Optional.of(loanTransaction);
|
this.loanTransaction = Optional.of(loanTransaction);
|
||||||
this.loanCharge = Optional.empty();
|
this.loanCharge = Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChangeOperation(LoanTermVariationsData interestRateChange) {
|
||||||
|
this.interestRateChange = Optional.of(interestRateChange);
|
||||||
|
this.loanTransaction = Optional.empty();
|
||||||
|
this.loanCharge = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInterestRateChange() {
|
||||||
|
return interestRateChange.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTransaction() {
|
public boolean isTransaction() {
|
||||||
return loanTransaction.isPresent();
|
return loanTransaction.isPresent();
|
||||||
}
|
}
|
||||||
@@ -52,8 +66,18 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
return loanCharge.isPresent();
|
return loanCharge.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAccrualActivity() {
|
||||||
|
return isTransaction() && loanTransaction.get().isAccrualActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBackdatedCharge() {
|
||||||
|
return isCharge() && DateUtils.isBefore(loanCharge.get().getDueDate(), loanCharge.get().getSubmittedOnDate());
|
||||||
|
}
|
||||||
|
|
||||||
private LocalDate getEffectiveDate() {
|
private LocalDate getEffectiveDate() {
|
||||||
if (loanCharge.isPresent()) {
|
if (interestRateChange.isPresent()) {
|
||||||
|
return getSubmittedOnDate();
|
||||||
|
} else if (loanCharge.isPresent()) {
|
||||||
if (isBackdatedCharge()) {
|
if (isBackdatedCharge()) {
|
||||||
return loanCharge.get().getDueDate();
|
return loanCharge.get().getDueDate();
|
||||||
} else {
|
} else {
|
||||||
@@ -66,16 +90,10 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAccrualActivity() {
|
|
||||||
return isTransaction() && loanTransaction.get().isAccrualActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isBackdatedCharge() {
|
|
||||||
return isCharge() && DateUtils.isBefore(loanCharge.get().getDueDate(), loanCharge.get().getSubmittedOnDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDate getSubmittedOnDate() {
|
private LocalDate getSubmittedOnDate() {
|
||||||
if (loanCharge.isPresent()) {
|
if (interestRateChange.isPresent()) {
|
||||||
|
return interestRateChange.get().getTermVariationApplicableFrom();
|
||||||
|
} else if (loanCharge.isPresent()) {
|
||||||
return loanCharge.get().getSubmittedOnDate();
|
return loanCharge.get().getSubmittedOnDate();
|
||||||
} else if (loanTransaction.isPresent()) {
|
} else if (loanTransaction.isPresent()) {
|
||||||
return loanTransaction.get().getSubmittedOnDate();
|
return loanTransaction.get().getSubmittedOnDate();
|
||||||
@@ -85,7 +103,9 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OffsetDateTime getCreatedDateTime() {
|
private OffsetDateTime getCreatedDateTime() {
|
||||||
if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) {
|
if (interestRateChange.isPresent()) {
|
||||||
|
return DateUtils.getOffsetDateTimeOfTenantFromLocalDate(getSubmittedOnDate());
|
||||||
|
} else if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) {
|
||||||
return loanCharge.get().getCreatedDate().get();
|
return loanCharge.get().getCreatedDate().get();
|
||||||
} else if (loanTransaction.isPresent()) {
|
} else if (loanTransaction.isPresent()) {
|
||||||
return loanTransaction.get().getCreatedDateTime();
|
return loanTransaction.get().getCreatedDateTime();
|
||||||
@@ -96,16 +116,16 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
|
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
|
||||||
public int compareTo(@NotNull ChargeOrTransaction o) {
|
public int compareTo(@NotNull ChangeOperation o) {
|
||||||
int datePortion = DateUtils.compare(this.getEffectiveDate(), o.getEffectiveDate());
|
int datePortion = DateUtils.compare(this.getEffectiveDate(), o.getEffectiveDate());
|
||||||
if (datePortion == 0) {
|
if (datePortion == 0) {
|
||||||
boolean isAccrual = isAccrualActivity();
|
final boolean isAccrual = isAccrualActivity();
|
||||||
if (isAccrual != o.isAccrualActivity()) {
|
if (isAccrual != o.isAccrualActivity()) {
|
||||||
return isAccrual ? 1 : -1;
|
return isAccrual ? 1 : -1;
|
||||||
}
|
}
|
||||||
int submittedDate = DateUtils.compare(getSubmittedOnDate(), o.getSubmittedOnDate());
|
int submittedDate = DateUtils.compare(getSubmittedOnDate(), o.getSubmittedOnDate());
|
||||||
if (submittedDate == 0) {
|
if (submittedDate == 0) {
|
||||||
return DateUtils.compare(getCreatedDateTime(), o.getCreatedDateTime());
|
return DateUtils.compare(getCreatedDateTime(), o.getCreatedDateTime(), null);
|
||||||
}
|
}
|
||||||
return submittedDate;
|
return submittedDate;
|
||||||
}
|
}
|
||||||
+3
-3
@@ -22,9 +22,9 @@ import java.math.BigDecimal;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public record InterestRate(LocalDate effectiveFrom, //
|
public record InterestRate(//
|
||||||
LocalDate validFrom, //
|
LocalDate effectiveFrom, //
|
||||||
BigDecimal interestRate //
|
BigDecimal interestRate//
|
||||||
) implements Comparable<InterestRate> {
|
) implements Comparable<InterestRate> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+20
-10
@@ -23,8 +23,10 @@ import java.math.MathContext;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
@@ -36,7 +38,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
|
|||||||
public class ProgressiveLoanInterestScheduleModel {
|
public class ProgressiveLoanInterestScheduleModel {
|
||||||
|
|
||||||
private final List<RepaymentPeriod> repaymentPeriods;
|
private final List<RepaymentPeriod> repaymentPeriods;
|
||||||
private final List<InterestRate> interestRates;
|
private final TreeSet<InterestRate> interestRates;
|
||||||
private final LoanProductRelatedDetail loanProductRelatedDetail;
|
private final LoanProductRelatedDetail loanProductRelatedDetail;
|
||||||
private final Integer installmentAmountInMultiplesOf;
|
private final Integer installmentAmountInMultiplesOf;
|
||||||
private MathContext mc;
|
private MathContext mc;
|
||||||
@@ -44,17 +46,17 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail,
|
public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail,
|
||||||
Integer installmentAmountInMultiplesOf, MathContext mc) {
|
Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||||
this.repaymentPeriods = repaymentPeriods;
|
this.repaymentPeriods = repaymentPeriods;
|
||||||
this.interestRates = new ArrayList<>();
|
this.interestRates = new TreeSet<>(Collections.reverseOrder());
|
||||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final List<InterestRate> interestRates,
|
private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
|
||||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
|
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods);
|
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods);
|
||||||
this.interestRates = new ArrayList<>(interestRates);
|
this.interestRates = new TreeSet<>(interestRates);
|
||||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,15 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal findInterestRate(final LocalDate effectiveDate) {
|
private BigDecimal findInterestRate(final LocalDate effectiveDate) {
|
||||||
return interestRates.stream().filter(ir -> !ir.effectiveFrom().isAfter(effectiveDate)).map(InterestRate::interestRate).findFirst()
|
return interestRates.stream() //
|
||||||
.orElse(loanProductRelatedDetail.getAnnualNominalInterestRate());
|
.filter(ir -> !ir.effectiveFrom().isAfter(effectiveDate)) //
|
||||||
|
.map(InterestRate::interestRate) //
|
||||||
|
.findFirst() //
|
||||||
|
.orElse(loanProductRelatedDetail.getAnnualNominalInterestRate()); //
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInterestRate(final LocalDate newInterestEffectiveDate, final BigDecimal newInterestRate) {
|
||||||
|
interestRates.add(new InterestRate(newInterestEffectiveDate, newInterestRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<RepaymentPeriod> findRepaymentPeriod(final LocalDate repaymentPeriodDueDate) {
|
public Optional<RepaymentPeriod> findRepaymentPeriod(final LocalDate repaymentPeriodDueDate) {
|
||||||
@@ -128,11 +137,12 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
}
|
}
|
||||||
return repaymentPeriods.stream()//
|
return repaymentPeriods.stream()//
|
||||||
.filter(repaymentPeriod -> {
|
.filter(repaymentPeriod -> {
|
||||||
if (repaymentPeriod.getPrevious().isPresent()) {
|
final boolean isFirstPeriod = repaymentPeriod.getPrevious().isEmpty();
|
||||||
return balanceChangeDate.isAfter(repaymentPeriod.getFromDate())
|
if (isFirstPeriod) {
|
||||||
|
return !balanceChangeDate.isBefore(repaymentPeriod.getFromDate())
|
||||||
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
||||||
} else {
|
} else {
|
||||||
return !balanceChangeDate.isBefore(repaymentPeriod.getFromDate())
|
return balanceChangeDate.isAfter(repaymentPeriod.getFromDate())
|
||||||
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
||||||
}
|
}
|
||||||
})//
|
})//
|
||||||
@@ -183,7 +193,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money getZero() {
|
public Money getZero() {
|
||||||
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -127,8 +127,12 @@ public class RepaymentPeriod {
|
|||||||
return getEmi().minus(getCalculatedDueInterest(), mc);
|
return getEmi().minus(getCalculatedDueInterest(), mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getTotalPaidAmount() {
|
||||||
|
return getPaidPrincipal().plus(getPaidInterest());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFullyPaid() {
|
public boolean isFullyPaid() {
|
||||||
return getEmi().isEqualTo(getPaidPrincipal().plus(getPaidInterest()));
|
return getEmi().isEqualTo(getTotalPaidAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getDueInterest() {
|
public Money getDueInterest() {
|
||||||
|
|||||||
+30
-36
@@ -35,7 +35,6 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -61,27 +60,19 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||||
private final EMICalculator emiCalculator;
|
private final EMICalculator emiCalculator;
|
||||||
|
|
||||||
|
public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
|
||||||
|
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData, mc);
|
||||||
|
return LoanSchedulePlan.from(generate(mc, loanApplicationTerms, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||||
|
|
||||||
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
||||||
// generate list of proposed schedule due dates
|
|
||||||
LocalDate loanEndDate = scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
|
|
||||||
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
|
||||||
LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
|
|
||||||
.fetchLoanTermDueDateVariationsData(loanEndDate);
|
|
||||||
if (lastDueDateVariation != null) {
|
|
||||||
loanEndDate = lastDueDateVariation.getDateValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine the total charges due at time of disbursement
|
// determine the total charges due at time of disbursement
|
||||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||||
|
|
||||||
// setup variables for tracking important facts required for loan
|
|
||||||
// schedule generation.
|
|
||||||
|
|
||||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||||
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
|
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
|
||||||
? loanApplicationTerms.getExpectedDisbursementDate()
|
? loanApplicationTerms.getExpectedDisbursementDate()
|
||||||
@@ -94,6 +85,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
// set and handled separately after all installments generated
|
// set and handled separately after all installments generated
|
||||||
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
||||||
|
|
||||||
|
// generate list of proposed schedule due dates
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
|
||||||
periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
||||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
|
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
|
||||||
@@ -102,29 +94,17 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
|
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
|
||||||
|
|
||||||
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
|
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
|
||||||
|
final List<DisbursementData> disbursementDataList = getSortedDisbursementList(loanApplicationTerms);
|
||||||
final ArrayList<DisbursementData> disbursementDataList = new ArrayList<>(loanApplicationTerms.getDisbursementDatas());
|
|
||||||
disbursementDataList.sort(Comparator.comparing(DisbursementData::disbursementDate));
|
|
||||||
|
|
||||||
for (LoanScheduleModelRepaymentPeriod repaymentPeriod : expectedRepaymentPeriods) {
|
for (LoanScheduleModelRepaymentPeriod repaymentPeriod : expectedRepaymentPeriods) {
|
||||||
scheduleParams.setPeriodStartDate(repaymentPeriod.getFromDate());
|
scheduleParams.setPeriodStartDate(repaymentPeriod.getFromDate());
|
||||||
scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate());
|
scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate());
|
||||||
|
// in same repayment period the logic firstly applies interest rate changes and just after the disbursements
|
||||||
|
applyInterestRateChangesOnPeriod(loanApplicationTerms, repaymentPeriod, interestScheduleModel);
|
||||||
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
|
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
|
||||||
chargesDueAtTimeOfDisbursement, false, mc);
|
chargesDueAtTimeOfDisbursement, false, mc);
|
||||||
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
|
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
|
||||||
|
|
||||||
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
|
||||||
for (var interestRateChange : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) {
|
|
||||||
final LocalDate interestRateSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
|
||||||
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
|
||||||
if (interestRateSubmittedOnDate.isAfter(repaymentPeriod.getFromDate())
|
|
||||||
&& !interestRateSubmittedOnDate.isAfter(repaymentPeriod.getDueDate())) {
|
|
||||||
emiCalculator.changeInterestRate(interestScheduleModel, interestRateSubmittedOnDate, newInterestRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emiCalculator.findRepaymentPeriod(interestScheduleModel, repaymentPeriod.getDueDate()).ifPresent(interestRepaymentPeriod -> {
|
emiCalculator.findRepaymentPeriod(interestScheduleModel, repaymentPeriod.getDueDate()).ifPresent(interestRepaymentPeriod -> {
|
||||||
final Money principalDue = interestRepaymentPeriod.getDuePrincipal();
|
final Money principalDue = interestRepaymentPeriod.getDuePrincipal();
|
||||||
final Money interestDue = interestRepaymentPeriod.getDueInterest();
|
final Money interestDue = interestRepaymentPeriod.getDueInterest();
|
||||||
@@ -166,10 +146,24 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
|
private List<DisbursementData> getSortedDisbursementList(LoanApplicationTerms loanApplicationTerms) {
|
||||||
|
final List<DisbursementData> disbursementDataList = new ArrayList<>(loanApplicationTerms.getDisbursementDatas());
|
||||||
|
disbursementDataList.sort(Comparator.comparing(DisbursementData::disbursementDate));
|
||||||
|
return disbursementDataList;
|
||||||
|
}
|
||||||
|
|
||||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData, mc);
|
private void applyInterestRateChangesOnPeriod(final LoanApplicationTerms loanApplicationTerms,
|
||||||
return LoanSchedulePlan.from(generate(mc, loanApplicationTerms, null, null));
|
final LoanScheduleModelRepaymentPeriod repaymentPeriod, final ProgressiveLoanInterestScheduleModel interestScheduleModel) {
|
||||||
|
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
||||||
|
for (var interestRateChange : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) {
|
||||||
|
final LocalDate interestRateSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
||||||
|
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
||||||
|
if (interestRateSubmittedOnDate.isAfter(repaymentPeriod.getFromDate())
|
||||||
|
&& !interestRateSubmittedOnDate.isAfter(repaymentPeriod.getDueDate())) {
|
||||||
|
emiCalculator.changeInterestRate(interestScheduleModel, interestRateSubmittedOnDate, newInterestRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTerms loanApplicationTerms) {
|
private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTerms loanApplicationTerms) {
|
||||||
@@ -181,10 +175,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms,
|
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final List<DisbursementData> disbursementDataList,
|
||||||
final ArrayList<DisbursementData> disbursementDataList, final LoanScheduleParams scheduleParams,
|
final LoanScheduleParams scheduleParams, final ProgressiveLoanInterestScheduleModel interestScheduleModel,
|
||||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List<LoanScheduleModelPeriod> periods,
|
final List<LoanScheduleModelPeriod> periods, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||||
final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate, final MathContext mc) {
|
final boolean includeDisbursementsAfterMaturityDate, final MathContext mc) {
|
||||||
|
|
||||||
for (DisbursementData disbursementData : disbursementDataList) {
|
for (DisbursementData disbursementData : disbursementDataList) {
|
||||||
final LocalDate disbursementDate = disbursementData.disbursementDate();
|
final LocalDate disbursementDate = disbursementData.disbursementDate();
|
||||||
|
|||||||
+24
-15
@@ -81,16 +81,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
@Override
|
@Override
|
||||||
public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
|
public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
|
||||||
final Money disbursedAmount) {
|
final Money disbursedAmount) {
|
||||||
scheduleModel
|
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.getZero())
|
||||||
.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount,
|
|
||||||
Money.zero(disbursedAmount.getCurrency(), scheduleModel.mc()))
|
|
||||||
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(
|
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(
|
||||||
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel));
|
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate getEffectiveRepaymentDueDate(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
private LocalDate getEffectiveRepaymentDueDate(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||||
final RepaymentPeriod changedRepaymentPeriod, final LocalDate disbursementDueDate) {
|
final RepaymentPeriod changedRepaymentPeriod, final LocalDate operationDueDate) {
|
||||||
final boolean isRelatedToNextRepaymentPeriod = changedRepaymentPeriod.getDueDate().isEqual(disbursementDueDate);
|
final boolean isRelatedToNextRepaymentPeriod = changedRepaymentPeriod.getDueDate().isEqual(operationDueDate);
|
||||||
if (isRelatedToNextRepaymentPeriod) {
|
if (isRelatedToNextRepaymentPeriod) {
|
||||||
final Optional<RepaymentPeriod> nextRepaymentPeriod = scheduleModel.repaymentPeriods().stream()
|
final Optional<RepaymentPeriod> nextRepaymentPeriod = scheduleModel.repaymentPeriods().stream()
|
||||||
.filter(repaymentPeriod -> changedRepaymentPeriod.equals(repaymentPeriod.getPrevious().orElse(null))).findFirst();
|
.filter(repaymentPeriod -> changedRepaymentPeriod.equals(repaymentPeriod.getPrevious().orElse(null))).findFirst();
|
||||||
@@ -106,14 +104,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
@Override
|
@Override
|
||||||
public void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate newInterestSubmittedOnDate,
|
public void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate newInterestSubmittedOnDate,
|
||||||
final BigDecimal newInterestRate) {
|
final BigDecimal newInterestRate) {
|
||||||
// TODO: impl
|
final LocalDate interestRateChangeEffectiveDate = newInterestSubmittedOnDate.minusDays(1);
|
||||||
|
scheduleModel.addInterestRate(interestRateChangeEffectiveDate, newInterestRate);
|
||||||
|
scheduleModel
|
||||||
|
.changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.getZero(),
|
||||||
|
scheduleModel.getZero())
|
||||||
|
.ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors(
|
||||||
|
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
|
public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
|
||||||
Money balanceCorrectionAmount) {
|
Money balanceCorrectionAmount) {
|
||||||
final Money zeroAmount = Money.zero(balanceCorrectionAmount.getCurrency());
|
scheduleModel
|
||||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, zeroAmount, balanceCorrectionAmount)
|
.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.getZero(), balanceCorrectionAmount)
|
||||||
.ifPresent(repaymentPeriod -> {
|
.ifPresent(repaymentPeriod -> {
|
||||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||||
calculateOutstandingBalance(scheduleModel);
|
calculateOutstandingBalance(scheduleModel);
|
||||||
@@ -186,10 +190,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||||
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
|
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
|
||||||
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||||
// TODO: optimalize
|
|
||||||
calculateOutstandingBalance(scheduleModel);
|
calculateOutstandingBalance(scheduleModel);
|
||||||
calculateEMIOnPeriods(relatedRepaymentPeriods, scheduleModel);
|
calculateEMIOnPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||||
// TODO: optimalize
|
|
||||||
calculateOutstandingBalance(scheduleModel);
|
calculateOutstandingBalance(scheduleModel);
|
||||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||||
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods);
|
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods);
|
||||||
@@ -229,7 +231,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
.isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc));
|
.isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc));
|
||||||
|
|
||||||
if (shouldBeAdjusted) {
|
if (shouldBeAdjusted) {
|
||||||
Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc);
|
long uncountablePeriods = relatedRepaymentPeriods.stream().filter(rp -> originalEmi.isLessThan(rp.getTotalPaidAmount()))
|
||||||
|
.count();
|
||||||
|
Money adjustment = emiDifference.dividedBy(Math.max(1, numberOfRelatedPeriods - uncountablePeriods), mc);
|
||||||
Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel,
|
Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel,
|
||||||
originalEmi.plus(adjustment, mc));
|
originalEmi.plus(adjustment, mc));
|
||||||
if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) {
|
if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) {
|
||||||
@@ -238,7 +242,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
|
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
|
||||||
final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(mc);
|
final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(mc);
|
||||||
newScheduleModel.repaymentPeriods().forEach(period -> {
|
newScheduleModel.repaymentPeriods().forEach(period -> {
|
||||||
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) {
|
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
|
||||||
|
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) {
|
||||||
period.setEmi(adjustedEqualMonthlyInstallmentValue);
|
period.setEmi(adjustedEqualMonthlyInstallmentValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -393,7 +398,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
|
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
|
||||||
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
|
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
|
||||||
|
|
||||||
repaymentPeriods.forEach(period -> period.setEmi(finalEqualMonthlyInstallment));
|
repaymentPeriods.forEach(period -> {
|
||||||
|
if (!finalEqualMonthlyInstallment.isLessThan(period.getTotalPaidAmount())) {
|
||||||
|
period.setEmi(finalEqualMonthlyInstallment);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||||
@@ -571,7 +580,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
return interestRate//
|
return interestRate//
|
||||||
.multiply(interestFractionPerPeriod, mc)//
|
.multiply(interestFractionPerPeriod, mc)//
|
||||||
.multiply(actualDaysInPeriod, mc)//
|
.multiply(actualDaysInPeriod, mc)//
|
||||||
.divide(calculatedDaysInPeriod, mc);//
|
.divide(calculatedDaysInPeriod, mc).setScale(mc.getPrecision(), mc.getRoundingMode());//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -585,7 +594,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
return interestRate//
|
return interestRate//
|
||||||
.multiply(interestFractionPerPeriod, mc)//
|
.multiply(interestFractionPerPeriod, mc)//
|
||||||
.multiply(actualDaysInPeriod, mc)//
|
.multiply(actualDaysInPeriod, mc)//
|
||||||
.divide(calculatedDaysInPeriod, mc);//
|
.divide(calculatedDaysInPeriod, mc).setScale(mc.getPrecision(), mc.getRoundingMode());//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
public class ChangeOperationTest {
|
||||||
|
|
||||||
|
// DateUtils.getOffsetDateTimeOfTenantFromLocalDate
|
||||||
|
private static final MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void init() {
|
||||||
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getTenant)
|
||||||
|
.thenReturn(new FineractPlatformTenant(null, null, null, "Europe/Budapest", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void tearDown() {
|
||||||
|
threadLocalContextUtil.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareToEqual() {
|
||||||
|
ChangeOperation interestChange = createInterestRateChange("2023-10-17");
|
||||||
|
ChangeOperation charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
ChangeOperation transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
Assertions.assertTrue(interestChange.compareTo(transaction) < 0);
|
||||||
|
Assertions.assertTrue(interestChange.compareTo(charge) < 0);
|
||||||
|
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
||||||
|
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareToEqualBackdatedCharge() {
|
||||||
|
ChangeOperation interestChange = createInterestRateChange("2023-10-17");
|
||||||
|
ChangeOperation charge = createCharge("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
ChangeOperation transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
Assertions.assertTrue(interestChange.compareTo(transaction) > 0);
|
||||||
|
Assertions.assertTrue(interestChange.compareTo(charge) > 0);
|
||||||
|
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
||||||
|
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareToCreatedDateTime() {
|
||||||
|
ChangeOperation charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
|
||||||
|
ChangeOperation transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||||
|
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareToSubmittedOnDate() {
|
||||||
|
ChangeOperation charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
ChangeOperation transaction = createTransaction("2023-10-17", "2023-10-16", "2023-10-17T10:15:30+01:00");
|
||||||
|
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||||
|
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorEffectiveDate() {
|
||||||
|
ChangeOperation charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
ChangeOperation transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
||||||
|
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorOnDifferentSubmittedDay() {
|
||||||
|
ChangeOperation cot1 = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
||||||
|
ChangeOperation cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:16:30+01:00");
|
||||||
|
ChangeOperation cot3 = createCharge("2023-10-17", "2023-10-18", "2023-10-18T10:14:30+01:00");
|
||||||
|
Collection<List<ChangeOperation>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||||
|
List<ChangeOperation> expected = List.of(cot1, cot3, cot2);
|
||||||
|
for (List<ChangeOperation> permutation : permutations) {
|
||||||
|
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorOnSameDayBackdatedCharge() {
|
||||||
|
ChangeOperation cot1 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
||||||
|
ChangeOperation cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
||||||
|
ChangeOperation cot3 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
||||||
|
Collection<List<ChangeOperation>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||||
|
List<ChangeOperation> expected = List.of(cot1, cot3, cot2);
|
||||||
|
for (List<ChangeOperation> permutation : permutations) {
|
||||||
|
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorOnSameDay() {
|
||||||
|
ChangeOperation cot1 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
||||||
|
ChangeOperation cot2 = createTransaction("2023-10-19", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
||||||
|
ChangeOperation cot3 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
||||||
|
Collection<List<ChangeOperation>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
||||||
|
List<ChangeOperation> expected = List.of(cot1, cot3, cot2);
|
||||||
|
for (List<ChangeOperation> permutation : permutations) {
|
||||||
|
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeOperation createInterestRateChange(String submittedDate) {
|
||||||
|
LoanTermVariationsData interestRateChange = Mockito.mock(LoanTermVariationsData.class);
|
||||||
|
Mockito.when(interestRateChange.getTermVariationApplicableFrom()).thenReturn(LocalDate.parse(submittedDate));
|
||||||
|
return new ChangeOperation(interestRateChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeOperation createCharge(String effectiveDate, String submittedDate, String creationDateTime) {
|
||||||
|
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||||
|
Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
|
||||||
|
Mockito.when(charge.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
||||||
|
Mockito.when(charge.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.parse(creationDateTime)));
|
||||||
|
return new ChangeOperation(charge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeOperation createTransaction(String transactionDate, String submittedDate, String creationDateTime) {
|
||||||
|
LoanTransaction transaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
Mockito.when(transaction.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
||||||
|
Mockito.when(transaction.getTransactionDate()).thenReturn(LocalDate.parse(transactionDate));
|
||||||
|
Mockito.when(transaction.getCreatedDateTime()).thenReturn(OffsetDateTime.parse(creationDateTime));
|
||||||
|
return new ChangeOperation(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
-127
@@ -1,127 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
public class ChargeOrTransactionTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompareToEqual() {
|
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
|
||||||
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompareToEqualBackdatedCharge() {
|
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
ChargeOrTransaction transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
Assertions.assertTrue(charge.compareTo(transaction) == 0);
|
|
||||||
Assertions.assertTrue(transaction.compareTo(charge) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompareToCreatedDateTime() {
|
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:31+01:00");
|
|
||||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
|
||||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompareToSubmittedOnDate() {
|
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
ChargeOrTransaction transaction = createTransaction("2023-10-17", "2023-10-16", "2023-10-17T10:15:30+01:00");
|
|
||||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
|
||||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testComparatorEffectiveDate() {
|
|
||||||
ChargeOrTransaction charge = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
ChargeOrTransaction transaction = createTransaction("2023-10-16", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
Assertions.assertTrue(charge.compareTo(transaction) > 0);
|
|
||||||
Assertions.assertTrue(transaction.compareTo(charge) < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testComparatorOnDifferentSubmittedDay() {
|
|
||||||
ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-17", "2023-10-17T10:15:30+01:00");
|
|
||||||
ChargeOrTransaction cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:16:30+01:00");
|
|
||||||
ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-18", "2023-10-18T10:14:30+01:00");
|
|
||||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
|
||||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
|
||||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
|
||||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testComparatorOnSameDayBackdatedCharge() {
|
|
||||||
ChargeOrTransaction cot1 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
|
||||||
ChargeOrTransaction cot2 = createTransaction("2023-10-17", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
|
||||||
ChargeOrTransaction cot3 = createCharge("2023-10-17", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
|
||||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
|
||||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
|
||||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
|
||||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testComparatorOnSameDay() {
|
|
||||||
ChargeOrTransaction cot1 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:31+01:00");
|
|
||||||
ChargeOrTransaction cot2 = createTransaction("2023-10-19", "2023-10-19", "2023-10-19T10:15:33+01:00");
|
|
||||||
ChargeOrTransaction cot3 = createCharge("2023-10-24", "2023-10-19", "2023-10-19T10:15:32+01:00");
|
|
||||||
Collection<List<ChargeOrTransaction>> permutations = Collections2.permutations(List.of(cot1, cot2, cot3));
|
|
||||||
List<ChargeOrTransaction> expected = List.of(cot1, cot3, cot2);
|
|
||||||
for (List<ChargeOrTransaction> permutation : permutations) {
|
|
||||||
Assertions.assertEquals(expected, permutation.stream().sorted().toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChargeOrTransaction createCharge(String effectiveDate, String submittedDate, String creationDateTime) {
|
|
||||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
|
||||||
Mockito.when(charge.getDueDate()).thenReturn(LocalDate.parse(effectiveDate));
|
|
||||||
Mockito.when(charge.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
|
||||||
Mockito.when(charge.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.parse(creationDateTime)));
|
|
||||||
return new ChargeOrTransaction(charge);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChargeOrTransaction createTransaction(String transactionDate, String submittedDate, String creationDateTime) {
|
|
||||||
LoanTransaction transaction = Mockito.mock(LoanTransaction.class);
|
|
||||||
Mockito.when(transaction.getSubmittedOnDate()).thenReturn(LocalDate.parse(submittedDate));
|
|
||||||
Mockito.when(transaction.getTransactionDate()).thenReturn(LocalDate.parse(transactionDate));
|
|
||||||
Mockito.when(transaction.getCreatedDateTime()).thenReturn(OffsetDateTime.parse(creationDateTime));
|
|
||||||
return new ChargeOrTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+169
-131
@@ -44,7 +44,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Timeout;
|
import org.junit.jupiter.api.Timeout;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -95,13 +94,12 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final BigDecimal daysInPeriod = BigDecimal.valueOf(DateUtils.getDifferenceInDays(period.getFromDate(), period.getDueDate()));
|
final BigDecimal daysInPeriod = BigDecimal.valueOf(DateUtils.getDifferenceInDays(period.getFromDate(), period.getDueDate()));
|
||||||
final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate()));
|
final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate()));
|
||||||
final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate()));
|
final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate()));
|
||||||
final BigDecimal rateFactor = emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear,
|
return emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear, daysInPeriod,
|
||||||
daysInPeriod, daysInPeriod, mc);
|
daysInPeriod, mc);
|
||||||
return rateFactor.setScale(12, MoneyHelper.getRoundingMode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRateFactorByRepaymentEveryMonthMethod_DayInYear365_DaysInMonthActual() {
|
public void test_rateFactorByRepaymentEveryMonthMethod_DayInYear365_DaysInMonthActual() {
|
||||||
// Given
|
// Given
|
||||||
final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
|
final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
|
||||||
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
||||||
@@ -116,7 +114,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRateFactorByRepaymentEveryMonthMethod_DayInYearActual_DaysInMonthActual() {
|
public void test_rateFactorByRepaymentEveryMonthMethod_DayInYearActual_DaysInMonthActual() {
|
||||||
// Given
|
// Given
|
||||||
final DaysInYearType daysInYearType = DaysInYearType.ACTUAL;
|
final DaysInYearType daysInYearType = DaysInYearType.ACTUAL;
|
||||||
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
||||||
@@ -132,7 +130,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFnValueFunction_RepayEvery1Month_DayInYear365_DaysInMonthActual() {
|
public void test_fnValueFunction_RepayEvery1Month_DayInYear365_DaysInMonthActual() {
|
||||||
// Given
|
// Given
|
||||||
final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
|
final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
|
||||||
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
|
||||||
@@ -159,7 +157,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculator_generateInterestScheduleModel() {
|
public void test_generateInterestScheduleModel() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
@@ -184,7 +182,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Timeout(1) // seconds
|
@Timeout(1) // seconds
|
||||||
public void testEMICalculation_performance() {
|
public void test_emi_calculator_performance() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -223,19 +221,19 @@ class ProgressiveEMICalculatorTest {
|
|||||||
List<RepaymentPeriod> repaymentPeriods = interestSchedule.repaymentPeriods();
|
List<RepaymentPeriod> repaymentPeriods = interestSchedule.repaymentPeriods();
|
||||||
for (int i = 0; i < repaymentPeriods.size(); i++) {
|
for (int i = 0; i < repaymentPeriods.size(); i++) {
|
||||||
final RepaymentPeriod repaymentPeriod = repaymentPeriods.get(i);
|
final RepaymentPeriod repaymentPeriod = repaymentPeriods.get(i);
|
||||||
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getDuePrincipal().getAmount()));
|
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getDuePrincipal()));
|
||||||
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getDueInterest().getAmount()));
|
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getDueInterest()));
|
||||||
if (i == repaymentPeriods.size() - 1) {
|
if (i == repaymentPeriods.size() - 1) {
|
||||||
Assertions.assertEquals(0.0, toDouble(repaymentPeriod.getOutstandingLoanBalance().getAmount()));
|
Assertions.assertEquals(0.0, toDouble(repaymentPeriod.getOutstandingLoanBalance()));
|
||||||
} else {
|
} else {
|
||||||
Assertions.assertEquals(8.65, toDouble(repaymentPeriod.getEmi().getAmount()));
|
Assertions.assertEquals(8.65, toDouble(repaymentPeriod.getEmi()));
|
||||||
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getOutstandingLoanBalance().getAmount()));
|
Assertions.assertTrue(0 < toDouble(repaymentPeriod.getOutstandingLoanBalance()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_CheckEmiButNewEmiNotBetterThanOriginal() {
|
public void test_emiAdjustment_newCalculatedEmiNotBetterThanOriginal() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -272,7 +270,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -283,7 +281,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -296,7 +294,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
||||||
@@ -309,7 +307,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_multi_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_multi_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -356,7 +354,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_reschedule() {
|
public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -367,7 +365,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -380,7 +378,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
||||||
@@ -392,9 +390,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("till interest rate change got implemented")
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_reschedule_interest_on0201_4per() {
|
public void test_reschedule_interest_on0201_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -405,7 +402,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("7");
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -420,10 +417,10 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
final BigDecimal interestRateNewValue = new BigDecimal("4");
|
final BigDecimal interestRateNewValue = BigDecimal.valueOf(4.0);
|
||||||
final LocalDate interestChangeDate = LocalDate.of(2024, 2, 2);
|
final LocalDate interestChangeDate = LocalDate.of(2024, 2, 2);
|
||||||
emiCalculator.changeInterestRate(interestSchedule, interestChangeDate, interestRateNewValue);
|
emiCalculator.changeInterestRate(interestSchedule, interestChangeDate, interestRateNewValue);
|
||||||
|
|
||||||
@@ -436,9 +433,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
checkPeriod(interestSchedule, 5, 0, 16.89, 0.003333333333, 0.06, 16.83, 0.0);
|
checkPeriod(interestSchedule, 5, 0, 16.89, 0.003333333333, 0.06, 16.83, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("till interest rate change got implemented")
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_reschedule_interest_on0215_4per() {
|
public void test_reschedule_interest_on0201_2nd_EMI_not_changeable_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -449,7 +445,54 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("7");
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
|
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||||
|
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
|
||||||
|
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
|
||||||
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||||
|
|
||||||
|
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
|
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
emiCalculator.payInterest(interestModel, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1), toMoney(0.58));
|
||||||
|
emiCalculator.payPrincipal(interestModel, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1), toMoney(16.43));
|
||||||
|
|
||||||
|
emiCalculator.payPrincipal(interestModel, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 2, 1), toMoney(16.90));
|
||||||
|
|
||||||
|
final BigDecimal interestRateNewValue = BigDecimal.valueOf(4.0);
|
||||||
|
final LocalDate interestChangeDate = LocalDate.of(2024, 2, 2);
|
||||||
|
emiCalculator.changeInterestRate(interestModel, interestChangeDate, interestRateNewValue);
|
||||||
|
|
||||||
|
checkPeriod(interestModel, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 66.67);
|
||||||
|
checkPeriod(interestModel, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 66.67);
|
||||||
|
checkPeriod(interestModel, 1, 0, 17.01, 0.003333333333, 0.22, 0.0, 17.01, 66.56);
|
||||||
|
checkPeriod(interestModel, 2, 0, 16.85, 0.003333333333, 0.22, 0.44, 16.41, 50.15);
|
||||||
|
checkPeriod(interestModel, 3, 0, 16.85, 0.003333333333, 0.17, 16.68, 33.47);
|
||||||
|
checkPeriod(interestModel, 4, 0, 16.85, 0.003333333333, 0.11, 16.74, 16.73);
|
||||||
|
checkPeriod(interestModel, 5, 0, 16.79, 0.003333333333, 0.06, 16.73, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_reschedule_interest_on0215_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
|
expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
|
||||||
|
expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
|
||||||
|
expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
||||||
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -464,14 +507,15 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
final BigDecimal interestRateNewValue = new BigDecimal("4");
|
final BigDecimal interestRateNewValue = BigDecimal.valueOf(4.0);
|
||||||
final LocalDate interestChangeDate = LocalDate.of(2024, 2, 15);
|
final LocalDate interestChangeDate = LocalDate.of(2024, 2, 15);
|
||||||
emiCalculator.changeInterestRate(interestSchedule, interestChangeDate, interestRateNewValue);
|
emiCalculator.changeInterestRate(interestSchedule, interestChangeDate, interestRateNewValue);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||||
|
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 0.58, 16.43, 83.57);
|
||||||
checkPeriod(interestSchedule, 1, 0, 16.90, 0.002614942529, 0.22, 0.37, 16.53, 67.04);
|
checkPeriod(interestSchedule, 1, 0, 16.90, 0.002614942529, 0.22, 0.37, 16.53, 67.04);
|
||||||
checkPeriod(interestSchedule, 1, 1, 16.90, 0.001839080460, 0.15, 0.37, 16.53, 67.04);
|
checkPeriod(interestSchedule, 1, 1, 16.90, 0.001839080460, 0.15, 0.37, 16.53, 67.04);
|
||||||
checkPeriod(interestSchedule, 2, 0, 16.90, 0.003333333333, 0.22, 16.68, 50.36);
|
checkPeriod(interestSchedule, 2, 0, 16.90, 0.003333333333, 0.22, 16.68, 50.36);
|
||||||
@@ -484,7 +528,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
* This test case tests a period early and late repayment with balance correction
|
* This test case tests a period early and late repayment with balance correction
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_add_balance_correction_on0215() {
|
public void test_balance_correction_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -495,7 +539,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("7");
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -510,37 +554,36 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
// schedule 1st period 1st day
|
// schedule 1st period 1st day
|
||||||
PayableDetails payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1),
|
PayableDetails payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1),
|
||||||
LocalDate.of(2024, 1, 1));
|
LocalDate.of(2024, 1, 1));
|
||||||
Assertions.assertEquals(100, toDouble(payableDetails.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(100, toDouble(payableDetails.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(17.01, toDouble(payableDetails.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(17.01, toDouble(payableDetails.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.0, toDouble(payableDetails.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.0, toDouble(payableDetails.getPayableInterest()));
|
||||||
|
|
||||||
// schedule 2nd period last day
|
// schedule 2nd period last day
|
||||||
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
||||||
Assertions.assertEquals(83.57, toDouble(payableDetails.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(83.57, toDouble(payableDetails.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.52, toDouble(payableDetails.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.52, toDouble(payableDetails.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.49, toDouble(payableDetails.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.49, toDouble(payableDetails.getPayableInterest()));
|
||||||
|
|
||||||
// pay off a period with balance correction
|
// pay off a period with balance correction
|
||||||
final LocalDate op1stCorrectionPeriodDueDate = LocalDate.of(2024, 3, 1);
|
final LocalDate op1stCorrectionPeriodDueDate = LocalDate.of(2024, 3, 1);
|
||||||
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
|
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
|
||||||
final Money op1stCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(16.77));
|
final Money op1stCorrectionAmount = toMoney(16.77);
|
||||||
|
|
||||||
// get remaining balance and dues for a date
|
// get remaining balance and dues for a date
|
||||||
final PayableDetails repaymentDetails1st = emiCalculator.getPayableDetails(interestSchedule, op1stCorrectionPeriodDueDate,
|
final PayableDetails repaymentDetails1st = emiCalculator.getPayableDetails(interestSchedule, op1stCorrectionPeriodDueDate,
|
||||||
op1stCorrectionDate);
|
op1stCorrectionDate);
|
||||||
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getPayableInterest()));
|
||||||
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, op1stCorrectionPeriodDueDate, op1stCorrectionDate, op1stCorrectionAmount);
|
emiCalculator.payPrincipal(interestSchedule, op1stCorrectionPeriodDueDate, op1stCorrectionDate, op1stCorrectionAmount);
|
||||||
emiCalculator.payInterest(interestSchedule, op1stCorrectionPeriodDueDate, op1stCorrectionDate,
|
emiCalculator.payInterest(interestSchedule, op1stCorrectionPeriodDueDate, op1stCorrectionDate, toMoney(0.24));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(0.24)));
|
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
||||||
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 16.77, 66.80);
|
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 16.77, 66.80);
|
||||||
@@ -553,18 +596,17 @@ class ProgressiveEMICalculatorTest {
|
|||||||
// totally pay off another period with balance correction
|
// totally pay off another period with balance correction
|
||||||
final LocalDate op2ndCorrectionPeriodDueDate = LocalDate.of(2024, 4, 1);
|
final LocalDate op2ndCorrectionPeriodDueDate = LocalDate.of(2024, 4, 1);
|
||||||
final LocalDate op2ndCorrectionDate = LocalDate.of(2024, 3, 1);
|
final LocalDate op2ndCorrectionDate = LocalDate.of(2024, 3, 1);
|
||||||
final Money op2ndCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(16.42));
|
final Money op2ndCorrectionAmount = toMoney(16.42);
|
||||||
|
|
||||||
// get remaining balance and dues for a date
|
// get remaining balance and dues for a date
|
||||||
final PayableDetails repaymentDetails2st = emiCalculator.getPayableDetails(interestSchedule, op2ndCorrectionPeriodDueDate,
|
final PayableDetails repaymentDetails2st = emiCalculator.getPayableDetails(interestSchedule, op2ndCorrectionPeriodDueDate,
|
||||||
op2ndCorrectionDate);
|
op2ndCorrectionDate);
|
||||||
Assertions.assertEquals(66.80, toDouble(repaymentDetails2st.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(66.80, toDouble(repaymentDetails2st.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.81, toDouble(repaymentDetails2st.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.81, toDouble(repaymentDetails2st.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.20, toDouble(repaymentDetails2st.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.20, toDouble(repaymentDetails2st.getPayableInterest()));
|
||||||
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, op2ndCorrectionPeriodDueDate, op2ndCorrectionDate, op2ndCorrectionAmount);
|
emiCalculator.payPrincipal(interestSchedule, op2ndCorrectionPeriodDueDate, op2ndCorrectionDate, op2ndCorrectionAmount);
|
||||||
emiCalculator.payInterest(interestSchedule, op2ndCorrectionPeriodDueDate, op2ndCorrectionDate,
|
emiCalculator.payInterest(interestSchedule, op2ndCorrectionPeriodDueDate, op2ndCorrectionDate, toMoney(0.49));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(0.49)));
|
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
||||||
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 16.77, 50.38);
|
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 16.77, 50.38);
|
||||||
@@ -578,17 +620,17 @@ class ProgressiveEMICalculatorTest {
|
|||||||
LocalDate periodDueDate = LocalDate.of(2024, 7, 1);
|
LocalDate periodDueDate = LocalDate.of(2024, 7, 1);
|
||||||
LocalDate payDate = LocalDate.of(2024, 7, 1);
|
LocalDate payDate = LocalDate.of(2024, 7, 1);
|
||||||
final PayableDetails repaymentDetails3rd = emiCalculator.getPayableDetails(interestSchedule, periodDueDate, payDate);
|
final PayableDetails repaymentDetails3rd = emiCalculator.getPayableDetails(interestSchedule, periodDueDate, payDate);
|
||||||
Assertions.assertEquals(16.75, toDouble(repaymentDetails3rd.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(16.75, toDouble(repaymentDetails3rd.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.75, toDouble(repaymentDetails3rd.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.75, toDouble(repaymentDetails3rd.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.1, toDouble(repaymentDetails3rd.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.1, toDouble(repaymentDetails3rd.getPayableInterest()));
|
||||||
|
|
||||||
// check numbers after the last period due date
|
// check numbers after the last period due date
|
||||||
periodDueDate = LocalDate.of(2024, 7, 1);
|
periodDueDate = LocalDate.of(2024, 7, 1);
|
||||||
payDate = LocalDate.of(2024, 7, 15);
|
payDate = LocalDate.of(2024, 7, 15);
|
||||||
final PayableDetails repaymentDetails4th = emiCalculator.getPayableDetails(interestSchedule, periodDueDate, payDate);
|
final PayableDetails repaymentDetails4th = emiCalculator.getPayableDetails(interestSchedule, periodDueDate, payDate);
|
||||||
Assertions.assertEquals(16.75, toDouble(repaymentDetails4th.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(16.75, toDouble(repaymentDetails4th.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.75, toDouble(repaymentDetails4th.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.75, toDouble(repaymentDetails4th.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.1, toDouble(repaymentDetails4th.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.1, toDouble(repaymentDetails4th.getPayableInterest()));
|
||||||
|
|
||||||
// balance update on the last period, check the right interest interval split
|
// balance update on the last period, check the right interest interval split
|
||||||
emiCalculator.addBalanceCorrection(interestSchedule, LocalDate.of(2024, 6, 10), Money.of(monetaryCurrency, BigDecimal.ZERO));
|
emiCalculator.addBalanceCorrection(interestSchedule, LocalDate.of(2024, 6, 10), Money.of(monetaryCurrency, BigDecimal.ZERO));
|
||||||
@@ -601,7 +643,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_payoff_on0215() {
|
public void test_payoff_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -612,7 +654,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("7");
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -627,51 +669,43 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
// partially pay off a period with balance correction
|
// partially pay off a period with balance correction
|
||||||
final LocalDate op1stCorrectionPeriodDueDate = LocalDate.of(2024, 3, 1);
|
final LocalDate op1stCorrectionPeriodDueDate = LocalDate.of(2024, 3, 1);
|
||||||
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
|
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
|
||||||
final Money op1stCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(15.0));
|
final Money op1stCorrectionAmount = toMoney(15.0);
|
||||||
|
|
||||||
// get remaining balance and dues for a date
|
// get remaining balance and dues for a date
|
||||||
final PayableDetails repaymentDetails1st = emiCalculator.getPayableDetails(interestSchedule, op1stCorrectionPeriodDueDate,
|
final PayableDetails repaymentDetails1st = emiCalculator.getPayableDetails(interestSchedule, op1stCorrectionPeriodDueDate,
|
||||||
op1stCorrectionDate);
|
op1stCorrectionDate);
|
||||||
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getPayableInterest()));
|
||||||
|
|
||||||
PayableDetails details = null;
|
PayableDetails details = null;
|
||||||
// check getPayableDetails forcast
|
// check getPayableDetails forcast
|
||||||
details = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
details = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
||||||
Assertions.assertEquals(83.57, toDouble(details.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(83.57, toDouble(details.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.52, toDouble(details.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.52, toDouble(details.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.49, toDouble(details.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.49, toDouble(details.getPayableInterest()));
|
||||||
|
|
||||||
// apply balance change and check again
|
// apply balance change and check again
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), op1stCorrectionDate, op1stCorrectionAmount);
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), op1stCorrectionDate, op1stCorrectionAmount);
|
||||||
details = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
details = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
||||||
Assertions.assertEquals(83.57, toDouble(details.getOutstandingBalance().getAmount()));
|
Assertions.assertEquals(83.57, toDouble(details.getOutstandingBalance()));
|
||||||
Assertions.assertEquals(16.52, toDouble(details.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.52, toDouble(details.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.49, toDouble(details.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.49, toDouble(details.getPayableInterest()));
|
||||||
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 15),
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 15), toMoney(1.43));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(1.43)));
|
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 15), toMoney(0.58));
|
||||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 15),
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 2, 15), toMoney(16.77));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(0.58)));
|
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 2, 15), toMoney(0.24));
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 2, 15),
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 2, 15), toMoney(17.01));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(16.77)));
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 2, 15), toMoney(17.01));
|
||||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 2, 15),
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 2, 15), toMoney(17.01));
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(0.24)));
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 2, 15), toMoney(15.77));
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 2, 15),
|
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(17.01)));
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 2, 15),
|
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(17.01)));
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 2, 15),
|
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(17.01)));
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 2, 15),
|
|
||||||
Money.of(monetaryCurrency, BigDecimal.valueOf(15.77)));
|
|
||||||
|
|
||||||
// check periods in model
|
// check periods in model
|
||||||
|
|
||||||
@@ -685,7 +719,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_payoff_on0115() {
|
public void test_payoff_on0115_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -696,7 +730,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("7");
|
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -717,13 +751,13 @@ class ProgressiveEMICalculatorTest {
|
|||||||
// get remaining balance and dues on due date
|
// get remaining balance and dues on due date
|
||||||
PayableDetails payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1),
|
PayableDetails payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1),
|
||||||
LocalDate.of(2024, 2, 1));
|
LocalDate.of(2024, 2, 1));
|
||||||
Assertions.assertEquals(16.43, toDouble(payableDetails.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.43, toDouble(payableDetails.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.58, toDouble(payableDetails.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.58, toDouble(payableDetails.getPayableInterest()));
|
||||||
|
|
||||||
// check numbers on payoff date
|
// check numbers on payoff date
|
||||||
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15));
|
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15));
|
||||||
Assertions.assertEquals(16.75, toDouble(payableDetails.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(16.75, toDouble(payableDetails.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.26, toDouble(payableDetails.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.26, toDouble(payableDetails.getPayableInterest()));
|
||||||
|
|
||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15), toMoney(16.75));
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15), toMoney(16.75));
|
||||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15), toMoney(0.26));
|
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 15), toMoney(0.26));
|
||||||
@@ -738,8 +772,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 1, 15), toMoney(17.01));
|
emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 1, 15), toMoney(17.01));
|
||||||
|
|
||||||
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 7, 1));
|
payableDetails = emiCalculator.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 7, 1));
|
||||||
Assertions.assertEquals(15.21, toDouble(payableDetails.getPayablePrincipal().getAmount()));
|
Assertions.assertEquals(15.21, toDouble(payableDetails.getPayablePrincipal()));
|
||||||
Assertions.assertEquals(0.5, toDouble(payableDetails.getPayableInterest().getAmount()));
|
Assertions.assertEquals(0.5, toDouble(payableDetails.getPayableInterest()));
|
||||||
|
|
||||||
// check periods in model
|
// check periods in model
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.26, 16.75, 15.21);
|
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.26, 16.75, 15.21);
|
||||||
@@ -765,7 +799,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_multiDisbursedAmt300InSamePeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_multiDisbursedAmt300InSamePeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -776,7 +810,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -789,7 +823,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
Money disbursedAmount = toMoney(100);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
||||||
@@ -800,7 +834,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
|
checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
|
||||||
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
||||||
|
|
||||||
disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(200));
|
disbursedAmount = toMoney(200.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 51.33, 0.0, 0.0, 2.02, 49.31, 250.69);
|
checkPeriod(interestSchedule, 0, 0, 51.33, 0.0, 0.0, 2.02, 49.31, 250.69);
|
||||||
@@ -814,7 +848,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_multiDisbursedAmt200InDifferentPeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_multiDisbursedAmt200InDifferentPeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -825,7 +859,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -838,7 +872,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
||||||
@@ -849,7 +883,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
|
checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
|
||||||
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
|
||||||
|
|
||||||
disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 2, 15), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 2, 15), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66);
|
||||||
@@ -863,7 +897,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_multiDisbursedAmt150InSamePeriod_dayInYears360_daysInMonth30_repayEvery1Month_backdated_disbursement() {
|
public void test_multiDisbursedAmt150InSamePeriod_dayInYears360_daysInMonth30_repayEvery1Month_backdated_disbursement() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
@@ -874,7 +908,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -887,14 +921,14 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
|
||||||
|
|
||||||
disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(50));
|
disbursedAmount = toMoney(50.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
||||||
|
|
||||||
// add disbursement on same date
|
// add disbursement on same date
|
||||||
disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(25));
|
disbursedAmount = toMoney(25.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 29.94, 0.001019591398, 0.00, 1.15, 28.79, 146.21);
|
checkPeriod(interestSchedule, 0, 0, 29.94, 0.001019591398, 0.00, 1.15, 28.79, 146.21);
|
||||||
@@ -908,7 +942,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month() {
|
public void test_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||||
repayment(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1, 12)),
|
repayment(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1, 12)),
|
||||||
@@ -918,7 +952,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 1)),
|
repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 1)),
|
||||||
repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 1)));
|
repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -931,7 +965,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.80, 16.33, 83.67);
|
checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.80, 16.33, 83.67);
|
||||||
@@ -944,7 +978,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt1000_NoInterest_repayEvery1Month() {
|
public void test_disbursedAmt1000_NoInterest_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||||
repayment(2, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)),
|
repayment(2, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)),
|
||||||
@@ -952,7 +986,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
repayment(4, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)),
|
repayment(4, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)),
|
||||||
repayment(5, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
repayment(5, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("0");
|
final BigDecimal interestRate = BigDecimal.ZERO;
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -965,7 +999,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
|
final Money disbursedAmount = toMoney(1000.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 250.0, 0.0, 0.0, 250.0, 750.0);
|
checkPeriod(interestSchedule, 0, 0, 250.0, 0.0, 0.0, 250.0, 750.0);
|
||||||
@@ -975,7 +1009,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery1Week() {
|
public void test_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery1Week() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||||
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 8)),
|
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 8)),
|
||||||
@@ -985,7 +1019,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
repayment(5, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 5)),
|
repayment(5, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 5)),
|
||||||
repayment(6, LocalDate.of(2024, 2, 5), LocalDate.of(2024, 2, 12)));
|
repayment(6, LocalDate.of(2024, 2, 5), LocalDate.of(2024, 2, 12)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -998,7 +1032,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 16.77, 0.0, 0.0, 0.18, 16.59, 83.41);
|
checkPeriod(interestSchedule, 0, 0, 16.77, 0.0, 0.0, 0.18, 16.59, 83.41);
|
||||||
@@ -1011,14 +1045,14 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery2Week() {
|
public void test_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery2Week() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||||
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 15)),
|
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 15)),
|
||||||
repayment(2, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 29)),
|
repayment(2, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 29)),
|
||||||
repayment(3, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 12)));
|
repayment(3, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 12)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -1031,7 +1065,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 33.57, 0.0, 0.0, 0.36, 33.21, 66.79);
|
checkPeriod(interestSchedule, 0, 0, 33.57, 0.0, 0.0, 0.36, 33.21, 66.79);
|
||||||
@@ -1041,7 +1075,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
|
public void test_disbursedAmt100_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||||
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)),
|
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)),
|
||||||
@@ -1051,7 +1085,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
repayment(5, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 16)),
|
repayment(5, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 16)),
|
||||||
repayment(6, LocalDate.of(2024, 3, 16), LocalDate.of(2024, 3, 31)));
|
repayment(6, LocalDate.of(2024, 3, 16), LocalDate.of(2024, 3, 31)));
|
||||||
|
|
||||||
final BigDecimal interestRate = new BigDecimal("9.4822");
|
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||||
@@ -1064,7 +1098,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
|
|
||||||
checkPeriod(interestSchedule, 0, 0, 16.90, 0.0, 0.0, 0.40, 16.50, 83.50);
|
checkPeriod(interestSchedule, 0, 0, 16.90, 0.0, 0.0, 0.40, 16.50, 83.50);
|
||||||
@@ -1105,16 +1139,20 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final RepaymentPeriod repaymentPeriod = interestScheduleModel.repaymentPeriods().get(repaymentIdx);
|
final RepaymentPeriod repaymentPeriod = interestScheduleModel.repaymentPeriods().get(repaymentIdx);
|
||||||
final InterestPeriod interestPeriod = repaymentPeriod.getInterestPeriods().get(interestIdx);
|
final InterestPeriod interestPeriod = repaymentPeriod.getInterestPeriods().get(interestIdx);
|
||||||
|
|
||||||
Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEmi().getAmount()));
|
Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEmi()));
|
||||||
Assertions.assertEquals(rateFactor, toDouble(applyMathContext(interestPeriod.getRateFactor())));
|
Assertions.assertEquals(rateFactor, toDouble(applyMathContext(interestPeriod.getRateFactor())));
|
||||||
Assertions.assertEquals(interestDue, toDouble(interestPeriod.getCalculatedDueInterest().getAmount()));
|
Assertions.assertEquals(interestDue, toDouble(interestPeriod.getCalculatedDueInterest()));
|
||||||
Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getDueInterest().getAmount()));
|
Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getDueInterest()));
|
||||||
Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getDuePrincipal().getAmount()));
|
Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getDuePrincipal()));
|
||||||
Assertions.assertEquals(remaingBalance, toDouble(repaymentPeriod.getOutstandingLoanBalance().getAmount()));
|
Assertions.assertEquals(remaingBalance, toDouble(repaymentPeriod.getOutstandingLoanBalance()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double toDouble(final Money value) {
|
||||||
|
return value == null ? 0.0 : toDouble(value.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double toDouble(final BigDecimal value) {
|
private static double toDouble(final BigDecimal value) {
|
||||||
return value == null ? 0 : value.doubleValue();
|
return value == null ? 0.0 : value.doubleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BigDecimal applyMathContext(final BigDecimal value) {
|
private static BigDecimal applyMathContext(final BigDecimal value) {
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChargeOrTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChangeOperation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -145,6 +145,6 @@ public class LoanReAgingValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean transactionHappenedAfterOther(LoanTransaction transaction, LoanTransaction otherTransaction) {
|
private boolean transactionHappenedAfterOther(LoanTransaction transaction, LoanTransaction otherTransaction) {
|
||||||
return new ChargeOrTransaction(transaction).compareTo(new ChargeOrTransaction(otherTransaction)) > 0;
|
return new ChangeOperation(transaction).compareTo(new ChangeOperation(otherTransaction)) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanReAmortizationApiConsta
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChargeOrTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChangeOperation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -130,6 +130,6 @@ public class LoanReAmortizationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean transactionHappenedAfterOther(LoanTransaction transaction, LoanTransaction otherTransaction) {
|
private boolean transactionHappenedAfterOther(LoanTransaction transaction, LoanTransaction otherTransaction) {
|
||||||
return new ChargeOrTransaction(transaction).compareTo(new ChargeOrTransaction(otherTransaction)) > 0;
|
return new ChangeOperation(transaction).compareTo(new ChangeOperation(otherTransaction)) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+119
@@ -83,6 +83,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@@ -5213,6 +5214,124 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UC14-: Advanced payment allocation with Interest, Interest Recalculation Enabled
|
||||||
|
// Interest Calculation Period Type: daily
|
||||||
|
// Use original EMI and apply disbursement and change Interest Rate
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Create a Loan product with Adv. Payment. Alloc. and with 7% Interest, 360/30, 1 repayment per month
|
||||||
|
// 2. Submit Loan and approve
|
||||||
|
// 3. Disburse
|
||||||
|
// 4. Validate Repayment Schedule
|
||||||
|
// 5. Reschedule Interest Rate to 4%
|
||||||
|
// 6. Validate Repayment Schedule
|
||||||
|
@Test
|
||||||
|
public void uc147d() {
|
||||||
|
final String operationDate = "1 January 2024";
|
||||||
|
runAt(operationDate, () -> {
|
||||||
|
BigDecimal interestRatePerPeriod = BigDecimal.valueOf(7.0);
|
||||||
|
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
|
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestRateFrequencyType(YEARS)//
|
||||||
|
.daysInMonthType(DaysInMonthType.DAYS_30)//
|
||||||
|
.daysInYearType(DaysInYearType.DAYS_360)//
|
||||||
|
.numberOfRepayments(6)//
|
||||||
|
.repaymentEvery(1)//
|
||||||
|
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
|
||||||
|
.repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())//
|
||||||
|
.enableDownPayment(false)//
|
||||||
|
.allowPartialPeriodInterestCalcualtion(null)//
|
||||||
|
.enableAutoRepaymentForDownPayment(null)//
|
||||||
|
.isInterestRecalculationEnabled(true)//
|
||||||
|
.interestRecalculationCompoundingMethod(0)//
|
||||||
|
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||||
|
.multiDisburseLoan(true)//
|
||||||
|
.disallowExpectedDisbursements(true)//
|
||||||
|
.allowApprovedDisbursedAmountsOverApplied(true)//
|
||||||
|
.overAppliedCalculationType("flat")//
|
||||||
|
.overAppliedNumber(10000)//
|
||||||
|
.maxTrancheCount(10)//
|
||||||
|
.outstandingLoanBalance(10000.0)//
|
||||||
|
.installmentAmountInMultiplesOf(null)//
|
||||||
|
.rescheduleStrategyMethod(LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.getValue())//
|
||||||
|
.recalculationRestFrequencyType(1);//
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||||
|
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 100.0, 6);
|
||||||
|
|
||||||
|
applicationRequest = applicationRequest.numberOfRepayments(6)//
|
||||||
|
.loanTermFrequency(6)//
|
||||||
|
.loanTermFrequencyType(2)//
|
||||||
|
.interestRatePerPeriod(interestRatePerPeriod)//
|
||||||
|
.interestCalculationPeriodType(DAYS)//
|
||||||
|
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||||
|
.repaymentEvery(1)//
|
||||||
|
.repaymentFrequencyType(2)//
|
||||||
|
.maxOutstandingLoanBalance(BigDecimal.valueOf(10000.0))//
|
||||||
|
;//
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()//
|
||||||
|
.approvedLoanAmount(BigDecimal.valueOf(100))//
|
||||||
|
.approvedOnDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()//
|
||||||
|
.transactionAmount(BigDecimal.valueOf(100.0))//
|
||||||
|
.actualDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 102.05, 0.0, 100.0, 0.0, null);
|
||||||
|
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 16.43, 0.0, 16.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0.0,
|
||||||
|
0.58, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1), 16.52, 0.0, 16.52, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.49, 0.0,
|
||||||
|
0.49, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 4, 1), 16.62, 0.0, 16.62, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.39, 0.0,
|
||||||
|
0.39, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 5, 1), 16.72, 0.0, 16.72, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.29, 0.0,
|
||||||
|
0.29, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 6, 1), 16.81, 0.0, 16.81, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.20, 0.0,
|
||||||
|
0.20, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 7, 1), 16.90, 0.0, 16.90, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.10, 0.0,
|
||||||
|
0.10, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
assertEquals(loanDetails.getNumberOfRepayments(), 6);
|
||||||
|
|
||||||
|
updateBusinessDate("1 February 2024");
|
||||||
|
loanTransactionHelper.makeRepayment("1 February 2024", 17.01f, loanResponse.getLoanId().intValue());
|
||||||
|
|
||||||
|
updateBusinessDate("14 February 2024");
|
||||||
|
PostCreateRescheduleLoansResponse rescheduleLoansResponse = loanRescheduleRequestHelper//
|
||||||
|
.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()//
|
||||||
|
.loanId(loanDetails.getId())//
|
||||||
|
.rescheduleReasonId(1L)//
|
||||||
|
.rescheduleFromDate("15 February 2024").dateFormat(DATETIME_PATTERN).locale("en")//
|
||||||
|
.submittedOnDate("14 February 2024")//
|
||||||
|
.newInterestRate(BigDecimal.valueOf(4.0)));//
|
||||||
|
|
||||||
|
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(), //
|
||||||
|
new PostUpdateRescheduleLoansRequest()//
|
||||||
|
.approvedOnDate("14 February 2024").locale("en").dateFormat(DATETIME_PATTERN));//
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 84.5, 17.01, 83.57, 16.43, null);
|
||||||
|
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 16.43, 16.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0.58,
|
||||||
|
0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1), 16.53, 0.0, 16.53, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.37, 0.0,
|
||||||
|
0.37, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 4, 1), 16.68, 0.0, 16.68, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 0.0,
|
||||||
|
0.22, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 5, 1), 16.73, 0.0, 16.73, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.17, 0.0,
|
||||||
|
0.17, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 6, 1), 16.79, 0.0, 16.79, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.11, 0.0,
|
||||||
|
0.11, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 7, 1), 16.84, 0.0, 16.84, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06, 0.0,
|
||||||
|
0.06, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// uc148a: Advanced payment allocation, with Interest Recalculation in Loan Product and Adjust last, unpaid period
|
// uc148a: Advanced payment allocation, with Interest Recalculation in Loan Product and Adjust last, unpaid period
|
||||||
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
// 1. Create a Loan product with Adv. Pment. Alloc. with Interest Recalculation enabled and Adjust last, unpaid
|
// 1. Create a Loan product with Adv. Pment. Alloc. with Interest Recalculation enabled and Adjust last, unpaid
|
||||||
|
|||||||
Reference in New Issue
Block a user