From 15838e5406ecda2457d9310b01cc3de241a57389 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Mon, 31 Jul 2023 14:57:08 +0200 Subject: [PATCH] FINERACT-1905: Same day repayment + charge handling improvement --- .../portfolio/loanaccount/domain/Loan.java | 2 +- .../loanaccount/domain/LoanCharge.java | 4 +- .../loanaccount/domain/LoanTransaction.java | 8 +- .../module/loan/module-changelog-master.xml | 27 + .../1001_add_audit_fields_to_loan_charge.xml | 77 ++ ...RepaymentScheduleTransactionProcessor.java | 6 +- ...RepaymentScheduleTransactionProcessor.java | 200 ++--- ...RepaymentScheduleTransactionProcessor.java | 198 ++--- .../domain/AbstractLoanScheduleGenerator.java | 2 +- ...ymentScheduleTransactionProcessorTest.java | 796 ++++++++--------- ...ymentScheduleTransactionProcessorTest.java | 814 ++++++++---------- .../ClientLoanIntegrationTest.java | 339 +++++++- 12 files changed, 1318 insertions(+), 1155 deletions(-) create mode 100644 fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml create mode 100644 fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1001_add_audit_fields_to_loan_charge.xml diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 7d78c4a1a..154c821a6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -5741,7 +5741,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { final List copyTransactions = new ArrayList<>(); if (!allNonContraTransactionsPostDisbursement.isEmpty()) { for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { - copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); + copyTransactions.add(LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction)); } loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), copyTransactions, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index f0fa27b9a..0eb0c553b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -43,7 +43,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; @@ -61,7 +61,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; @Entity @Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") }) -public class LoanCharge extends AbstractPersistableCustom { +public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom { @ManyToOne(optional = false) @JoinColumn(name = "loan_id", referencedColumnName = "id", nullable = false) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 9a2116c56..ed78c84e7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -269,11 +269,13 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom { return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId); } - public static LoanTransaction copyTransactionProperties(final LoanTransaction loanTransaction) { - return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, - loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, + public static LoanTransaction copyTransactionPropertiesForReprocessing(final LoanTransaction loanTransaction) { + LoanTransaction loanTransactionCopy = new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, + loanTransaction.dateOf, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion, loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion, loanTransaction.reversed, loanTransaction.paymentDetail, loanTransaction.externalId); + loanTransactionCopy.setCreatedDate(loanTransaction.getCreatedDateTime()); + return loanTransactionCopy; } public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate, diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml new file mode 100644 index 000000000..77aacf807 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1001_add_audit_fields_to_loan_charge.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1001_add_audit_fields_to_loan_charge.xml new file mode 100644 index 000000000..041e25d47 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1001_add_audit_fields_to_loan_charge.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select count(*) from m_loan_charge + + + + + + + select count(*) from m_loan_charge + + + + + + + select count(*) from m_loan_charge + + + + + diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 039da3468..0f21e9093 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -175,7 +175,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen * For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) * has changed.
**/ - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction); // Reset derived component of new loan transaction and // re-process transaction @@ -214,7 +214,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction, MonetaryCurrency currency, List installments) { - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction); newLoanTransaction.resetDerivedComponents(); // determine how much is outstanding total and breakdown for principal, interest and charges Money principalPortion = Money.zero(currency); @@ -280,7 +280,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen if (loanTransaction.getId() == null) { return; } - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction); List mergedList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail); Money overpaidAmount = calculateOverpaidAmount(loanTransaction, mergedList, installments, currency); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java index f9e038652..92ee4a598 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java @@ -61,9 +61,81 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio */ @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, - List transactionMappings, Set charges) { + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + /** + * For late repayments + */ + @Override + protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + + /** + * For normal on-time repayments + */ + @Override + protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings, Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + + @Override + protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { + // TODO - KW - dont do anything with loan over-payment for now + } + + @Override + protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltyChargesPortion = Money.zero(currency); + + principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + + if (transactionAmountRemaining.isGreaterThanZero()) { + interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } + + private static Money doTransactionAmountAllocation(LoanRepaymentScheduleInstallment currentInstallment, LoanTransaction loanTransaction, + Money paymentInAdvance, List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = paymentInAdvance.getCurrency(); @@ -103,6 +175,8 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio List orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid) .filter(loanCharge -> loanCharge.getEffectiveDueDate() == null || !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) + .filter(loanCharge -> loanCharge.getCreatedDate().isEmpty() || loanTransaction.getCreatedDate().isEmpty() + || !loanCharge.getCreatedDate().get().isAfter(loanTransaction.getCreatedDate().get())) .sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList(); Money calculatedPenaltyCharge = Money.zero(currency); Money calculatedFeeCharge = Money.zero(currency); @@ -165,126 +239,4 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio } return transactionAmountRemaining; } - - /** - * For late repayments - */ - @Override - protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, - final Money transactionAmountUnprocessed, List transactionMappings, - Set charges) { - - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, - transactionMappings, charges); - } - - /** - * For normal on-time repayments - */ - @Override - protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings, Set charges) { - - final LocalDate transactionDate = loanTransaction.getTransactionDate(); - - final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money principalPortion = Money.zero(currency); - Money interestPortion = Money.zero(currency); - Money feeChargesPortion = Money.zero(currency); - Money penaltyChargesPortion = Money.zero(currency); - - if (loanTransaction.isChargesWaiver()) { - penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, - loanTransaction.getPenaltyChargesPortion(currency)); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - - feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, - loanTransaction.getFeeChargesPortion(currency)); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - - } else if (loanTransaction.isInterestWaiver()) { - interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); - - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } else if (loanTransaction.isChargePayment()) { - if (loanTransaction.isPenaltyPayment()) { - penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - } else { - feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - } - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } else { - Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion); - penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion); - - Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion); - feeChargesPortion = feeChargesPortion.add(subFeePortion); - - Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion); - interestPortion = interestPortion.add(subInterestPortion); - - Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion); - principalPortion = principalPortion.add(subPrincipalPortion); - - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } - if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { - transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, - principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); - } - return transactionAmountRemaining; - } - - @Override - protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { - // TODO - KW - dont do anything with loan over-payment for now - } - - @Override - protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { - - final LocalDate transactionDate = loanTransaction.getTransactionDate(); - final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money principalPortion = Money.zero(currency); - Money interestPortion = Money.zero(currency); - Money feeChargesPortion = Money.zero(currency); - Money penaltyChargesPortion = Money.zero(currency); - - principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); - - if (transactionAmountRemaining.isGreaterThanZero()) { - interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); - } - - if (transactionAmountRemaining.isGreaterThanZero()) { - feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - } - - if (transactionAmountRemaining.isGreaterThanZero()) { - penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - } - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { - transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, - principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); - } - return transactionAmountRemaining; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java index 6a8bf1e6a..1c1abc011 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java @@ -61,8 +61,81 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio */ @Override protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, final Money inAdvancePayment, + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + + /** + * For late repayments + */ + @Override + protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + + /** + * For normal on-time repayments + */ + @Override + protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, List transactionMappings, Set charges) { + return doTransactionAmountAllocation(currentInstallment, loanTransaction, transactionAmountUnprocessed, transactionMappings, + charges); + } + + @Override + protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { + // TODO - KW - dont do anything with loan over-payment for now + } + + @Override + protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltyChargesPortion = Money.zero(currency); + + principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + + if (transactionAmountRemaining.isGreaterThanZero()) { + interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } + + private static Money doTransactionAmountAllocation(LoanRepaymentScheduleInstallment currentInstallment, LoanTransaction loanTransaction, + Money inAdvancePayment, List transactionMappings, Set charges) { final LocalDate transactionDate = loanTransaction.getTransactionDate(); final MonetaryCurrency currency = inAdvancePayment.getCurrency(); @@ -103,6 +176,8 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio List orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid) .filter(loanCharge -> loanCharge.getEffectiveDueDate() == null || !loanCharge.getEffectiveDueDate().isAfter(transactionDate)) + .filter(loanCharge -> loanCharge.getCreatedDate().isEmpty() || loanTransaction.getCreatedDate().isEmpty() + || loanCharge.getCreatedDate().get().isBefore(loanTransaction.getCreatedDate().get())) .sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList(); Money calculatedPenaltyCharge = Money.zero(currency); Money calculatedFeeCharge = Money.zero(currency); @@ -168,125 +243,4 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio } return transactionAmountRemaining; } - - /** - * For late repayments - */ - @Override - protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final List installments, final LoanTransaction loanTransaction, final Money latePayment, - List transactionMappings, Set charges) { - - return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, latePayment, transactionMappings, - charges); - } - - /** - * For normal on-time repayments - */ - @Override - protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final LoanTransaction loanTransaction, final Money onTimePayment, - List transactionMappings, Set charges) { - - final LocalDate transactionDate = loanTransaction.getTransactionDate(); - - final MonetaryCurrency currency = onTimePayment.getCurrency(); - Money transactionAmountRemaining = onTimePayment; - Money principalPortion = Money.zero(currency); - Money interestPortion = Money.zero(currency); - Money feeChargesPortion = Money.zero(currency); - Money penaltyChargesPortion = Money.zero(currency); - - if (loanTransaction.isChargesWaiver()) { - penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, - loanTransaction.getPenaltyChargesPortion(currency)); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - - feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, - loanTransaction.getFeeChargesPortion(currency)); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - - } else if (loanTransaction.isInterestWaiver()) { - interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); - - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } else if (loanTransaction.isChargePayment()) { - if (loanTransaction.isPenaltyPayment()) { - penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - } else { - feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - } - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } else { - Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subPenaltyPortion); - penaltyChargesPortion = penaltyChargesPortion.add(subPenaltyPortion); - - Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subInterestPortion); - interestPortion = interestPortion.add(subInterestPortion); - - Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subPrincipalPortion); - principalPortion = principalPortion.add(subPrincipalPortion); - - Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(subFeePortion); - feeChargesPortion = feeChargesPortion.add(subFeePortion); - - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - } - if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { - transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, - principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); - } - return transactionAmountRemaining; - } - - @Override - protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { - // TODO - KW - dont do anything with loan over-payment for now - } - - @Override - protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, - final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, - List transactionMappings) { - - final LocalDate transactionDate = loanTransaction.getTransactionDate(); - final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money principalPortion = Money.zero(currency); - Money interestPortion = Money.zero(currency); - Money feeChargesPortion = Money.zero(currency); - Money penaltyChargesPortion = Money.zero(currency); - - principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); - - if (transactionAmountRemaining.isGreaterThanZero()) { - interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); - } - - if (transactionAmountRemaining.isGreaterThanZero()) { - feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); - } - - if (transactionAmountRemaining.isGreaterThanZero()) { - penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); - } - loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); - if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { - transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, - principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); - } - return transactionAmountRemaining; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index f6193dfe2..ca1db29b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -2098,7 +2098,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener for (LoanTransaction loanTransaction : transactions) { if (loanTransaction.isPaymentTransaction()) { recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), - LoanTransaction.copyTransactionProperties(loanTransaction))); + LoanTransaction.copyTransactionPropertiesForReprocessing(loanTransaction))); } } final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java index f24a7d7bd..94b83af34 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java @@ -18,15 +18,17 @@ */ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; @@ -43,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -63,15 +66,12 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio private final LocalDate firstInstallmentDueDate = LocalDate.of(2023, 7, 31); private final LocalDate lateDate = firstInstallmentDueDate.plusDays(1); private final Money zero = Money.zero(MONETARY_CURRENCY); - private final Money one = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(1)); + private final Money one = Money.of(MONETARY_CURRENCY, BigDecimal.ONE); private final Money two = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(2)); private final Money three = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(3)); private final Money four = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(4)); private final Money five = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); private final Money six = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(6)); - private final Money seven = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(7)); - private final Money eight = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(8)); - private final Money nine = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(9)); private final Money ten = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(10)); private final Money eleven = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(11)); private DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor underTest; @@ -111,28 +111,12 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 5 is outstanding for this - // installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -142,160 +126,91 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio Mockito.when(charges.stream()).thenReturn(Stream.empty()); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 10 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but only 5 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(ten)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void inAdvancePaymentOfFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but no outstanding - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but no only 5 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void inAdvancePaymentOfPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but no outstanding - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but only 5 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void inAdvancePaymentOfPrincipalAndPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 5 is outstanding for this - // installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(zero)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 7 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 7, but only 6 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 1, and 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // In advance with value of 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(zero)); // Principal 3, interest 0, fee 1, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(six)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -305,29 +220,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 7 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 7, but only 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 3, but only 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(three)); - // In advance with value of 1, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(one)); - // Full amount as this is the last of due and first of in advance, but 0 is outstanding for this installment, so - // 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(zero)); // Principal 3, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(one), refEq(two), refEq(four)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPrincipal() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(2)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -337,19 +238,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but 3 is outstanding for this installment - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(two)); // Principal 2, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(zero), refEq(zero)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPrincipalAndPartialPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(4)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -359,24 +256,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 1 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 1, and 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(one)); - // In advance with value of 0, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(zero)); // Principal 3, interest 0, fee 0, penalty 1 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(zero), refEq(one)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPrincipalAndPenaltyAndPartialFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(8)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -386,26 +274,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(eight)); - // In advance with value of 5, but only 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 1, and 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // In advance with value of 0, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(zero)); // Principal 3, interest 0, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(four)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPrincipalAndPenaltyAndFeeAndPartialInterest() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -415,37 +292,14 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // Full amount as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 7, but only 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 3, but only 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(three)); - // In advance with value of 1, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(one)); // Principal 3, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(one), refEq(two), refEq(four)); } @Test public void duePaymentOfPenaltyAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.plusDays(1)); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, two, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -456,39 +310,14 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated two - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(two)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // With value of 8 as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(eight)); - // In advance with value of 5, and 2 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 3, but only 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(three)); - // In advance with value of 1, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(one)); // Principal 3, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(one), refEq(two), refEq(four)); } @Test public void duePaymentOfPenaltyAndFeeAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1)); - Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(one); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, two, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate.minusDays(1), false, one, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -499,39 +328,14 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated two - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(two)); - // Calculated one - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // With value of 7 as this is the last of due and first of in advance, but only 3 is outstanding for this - // installment, so 4 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 4, and 2 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(four)); - // In advance with value of 2, but only 1 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(two)); - // In advance with value of 1, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(one)); // Principal 3, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(one), refEq(two), refEq(four)); } @Test public void duePaymentOfHigherPenaltyAndHigherFeeAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1)); - Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, eleven, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate.minusDays(1), false, eleven, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -542,39 +346,96 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated eleven, overridden by Unprocessed (ten) - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // Calculated eleven, overridden by Unprocessed (six) - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(six)); - // In advance with value of 4, and 2 is outstanding of principal - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 2, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(two)); - // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 3, interest 0, fee 0, penalty 1 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(zero), refEq(one)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 0, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(four)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + // ON TIME @Test public void onTimePaymentOfPrincipal() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(ten)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -583,119 +444,91 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio public void onTimePaymentOfInterest() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void onTimePaymentOfFee() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void onTimePaymentOfPenalty() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void onTimePaymentOfPrincipalAndPenalty() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFee() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 6 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 2, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(two)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(two)); // Principal 2, interest 0, fee 2, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(two), refEq(six)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -705,21 +538,16 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(six)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(two)); // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPenalty() { - Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(1)); + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.ONE); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); @@ -728,20 +556,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 1, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 0, interest 0, fee 0, penalty 1 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(one)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndPartialFee() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -751,20 +574,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 5, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 0, interest 0, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(one), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndFeeAndPartialInterest() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(7)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -774,20 +592,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 7, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(seven)); - // Unprocessed: 3, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(three)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(one)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 0, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(one), refEq(two), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndFeeAndInterestAndPartialPrincipal() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -797,38 +610,99 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(six)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(two)); // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentToDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 0, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(four), refEq(zero)); + } + + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentToDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 3, interest 0, fee 1, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(zero)); + } + + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentToDate, ExternalId.empty())); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 1, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(four), refEq(zero)); + } + + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentToDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentToDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentToDate, ExternalId.empty())); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 1, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(four), refEq(zero)); + } + // LATE @Test public void latePaymentOfPrincipal() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(ten)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -837,119 +711,91 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio public void latePaymentOfInterest() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void latePaymentOfFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void latePaymentOfPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void latePaymentOfPrincipalAndPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 6 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(four)); - // Unprocessed: 2, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(two)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(two)); // Principal 2, interest 0, fee 2, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(two), refEq(six)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -959,21 +805,16 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(six)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(two)); // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPenalty() { - Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(1)); + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.ONE); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); @@ -982,20 +823,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 1, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(zero)); // Principal 0, interest 0, fee 0, penalty 1 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(one)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndPartialFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -1005,20 +841,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 5, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(five)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(zero)); // Principal 0, interest 0, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(one), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndFeeAndPartialInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(7)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -1028,20 +859,15 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 7, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(seven)); - // Unprocessed: 3, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(three)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(one)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(zero)); // Principal 0, interest 1, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(one), refEq(two), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndFeeAndInterestAndPartialPrincipal() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -1051,15 +877,95 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(six)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(two)); // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 0, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(four), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 3, interest 0, fee 1, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(four), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 4, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(four), refEq(zero)); + } + + @NotNull + private LoanCharge createLoanCharge(LocalDate dueDate, boolean isPenalty, Money amount, OffsetDateTime createdDate) { + LoanCharge loanCharge = Mockito.mock(LoanCharge.class); + Mockito.lenient().when(loanCharge.isActive()).thenReturn(true); + Mockito.lenient().when(loanCharge.isNotFullyPaid()).thenReturn(true); + Mockito.lenient().when(loanCharge.getEffectiveDueDate()).thenReturn(dueDate); + Mockito.lenient().when(loanCharge.isPenaltyCharge()).thenReturn(isPenalty); + if (createdDate != null) { + Mockito.lenient().when(loanCharge.getCreatedDate()).thenReturn(Optional.of(createdDate)); + } + Mockito.lenient().when(loanCharge.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(amount); + return loanCharge; + } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java index f8cfc49f3..bc63ce162 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java @@ -18,15 +18,17 @@ */ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; @@ -43,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -69,9 +72,6 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio private final Money four = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(4)); private final Money five = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); private final Money six = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(6)); - private final Money seven = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(7)); - private final Money eight = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(8)); - private final Money nine = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(9)); private final Money ten = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(10)); private final Money eleven = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(11)); @@ -112,24 +112,12 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 5, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but 0 is outstanding for this installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -139,142 +127,91 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio Mockito.when(charges.stream()).thenReturn(Stream.empty()); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but only 5 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but 0 is outstanding for this installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void inAdvancePaymentOfFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, but no outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but 0 is outstanding for this installment, so 10 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 10, but no only 5 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(ten)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void inAdvancePaymentOfPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, but only 5 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 5, but no outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but 0 is outstanding for this installment, so 5 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but no outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void inAdvancePaymentOfPrincipalAndPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, and 5 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // In advance with value of 5, but only 5 is outstanding for this installment, so 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, but only 6 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 4, but no outstanding - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(four)); - // In advance with value of 4, but only 3 is outstanding for this installment, so 1 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 1, and 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); // Principal 3, interest 0, fee 1, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(six)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -284,25 +221,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 10, but only 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 6, but only 2 is outstanding - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(six)); - // In advance with value of 4, but only 3 is outstanding for this installment, so 1 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 1, and 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // Principal 3, interest 1, fee 2, penalty 4 + // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPenalty() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(2)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -311,18 +238,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 2, but 4 is outstanding for this installment - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(two)); // Principal 0, interest 0, fee 0, penalty 2 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(two)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndPartialInterest() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -331,24 +255,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 5, and 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(five)); - // In advance with value of 1, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(one)); - // In advance with value of 0, and 3 is outstanding for this installment, so 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); // Principal 0, interest 1, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(one), refEq(zero), refEq(four)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPartialPrincipal() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); + LoanCharge loanCharge1 = createLoanCharge(lateDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(7)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -357,63 +272,14 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 7, and 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 3, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(three)); - // In advance with value of 1, and 3 is outstanding for this installment, so 0 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(one)); - // Calculated zero - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); // Principal 1, interest 2, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(two), refEq(zero), refEq(four)); } @Test public void inAdvancePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPrincipalAndPartialFee() { - Mockito.when(charges.stream()).thenReturn(Stream.empty()); - Money transactionAmount = ten; - LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); - LoanTransaction loanTransaction = Mockito - .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); - underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, - transactionMappings, charges); - - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(zero)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // In advance with value of 10, and 4 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // In advance with value of 6, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(six)); - // In advance with value of 4, and 3 is outstanding for this installment, so 1 is unprocessed - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 1, and outstanding is 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // Principal 3, interest 1, fee 1, penalty 4 - Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); - } - - @Test - public void duePaymentOfPenaltyAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.plusDays(1)); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -423,39 +289,31 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); + // Principal 3, interest 2, fee 1, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); + } - // Calculated two - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(two)); - // Calculated zero - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(zero)); - // In advance with value of 8, and 2 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(eight)); - // In advance with value of 6, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(six)); - // In advance with value of 4, and 3 is outstanding of principal - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(four)); - // In advance with value of 1, but only 2 is outstanding of fee - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(one)); + @Test + public void duePaymentOfPenaltyAsInAdvanceTransaction() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, two, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = ten; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } @Test public void duePaymentOfPenaltyAndFeeAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1)); - Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(one); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, two, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate.minusDays(1), false, one, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -465,37 +323,14 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Calculated two - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(two)); - // Calculated one - Mockito.verify(installment, Mockito.times(2)).payFeeChargesComponent(eq(transactionDate), refEq(one)); - // In advance with value of 7, and 2 is outstanding of penalty - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(seven)); - // In advance with value of 5, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(five)); - // In advance with value of 3, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(three)); // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } @Test public void duePaymentOfHigherPenaltyAndHigherFeeAsInAdvanceTransaction() { - - LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge1.isActive()).thenReturn(true); - Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate); - Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true); - Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven); - LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class); - Mockito.when(loanCharge2.isActive()).thenReturn(true); - Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true); - Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false); - Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1)); - Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven); - + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, eleven, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate.minusDays(1), false, eleven, null); Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito @@ -505,40 +340,95 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Calculated eleven, overriden by unprocessed - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(transactionDate), refEq(ten)); - // Calculated eleven, overriden by unprocessed - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), refEq(six)); - // In advance with value of 4, and 2 is outstanding of interest - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(transactionDate), refEq(four)); - // In advance with value of 2, and 2 is outstanding of principal - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(transactionDate), refEq(two)); - // Principal 2, interest 2, fee 2, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(two), refEq(four)); } + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + + @Test + public void inAdvancePaymentOfPrincipalAndPenaltyWherePenaltyIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, one, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, true, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, transactionDate, ExternalId.empty())); + underTest.handleTransactionThatIsPaymentInAdvanceOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 1, interest 0, fee 0, penalty 4 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(one), refEq(zero), refEq(zero), refEq(four)); + } + // ON TIME @Test public void onTimePaymentOfPrincipal() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -547,119 +437,86 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio public void onTimePaymentOfInterest() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void onTimePaymentOfFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void onTimePaymentOfPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void onTimePaymentOfPrincipalAndPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 5, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 0, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 6 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 4, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(one)); // Principal 3, interest 0, fee 1, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(six)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -668,22 +525,16 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(six)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(one)); // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPenalty() { - Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(1)); + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.ONE); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); @@ -691,21 +542,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 1, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 0, interest 0, fee 0, penalty 1 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(one)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndPartialInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -714,21 +559,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 5, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(five)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(one)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 0, interest 1, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(one), refEq(zero), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPartialPrincipal() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(8)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -737,21 +576,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 8, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(eight)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(two)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(zero)); // Principal 2, interest 2, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(zero), refEq(four)); } @Test public void onTimePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPrincipalAndPartialFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -760,39 +593,98 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(firstInstallmentDueDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(firstInstallmentDueDate), refEq(six)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(firstInstallmentDueDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(firstInstallmentDueDate), refEq(one)); // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentDueDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentDueDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(2L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 2, interest 0, fee 2, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(two), refEq(zero)); + } + + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentDueDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentDueDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 3, interest 0, fee 1, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(zero)); + } + + @Test + public void onTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentDueDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentDueDate, false, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(2L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 2, interest 0, fee 3, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(three), refEq(zero)); + } + + @Test + public void ontTimePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(firstInstallmentDueDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(firstInstallmentDueDate, false, three, + OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(2L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, firstInstallmentDueDate, ExternalId.empty())); + underTest.handleTransactionThatIsOnTimePaymentOfInstallment(installment, loanTransaction, transactionAmount, transactionMappings, + charges); + + // Principal 2, interest 0, fee 3, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(three), refEq(zero)); + } + // LATE @Test public void latePaymentOfPrincipal() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(five)); // Principal 5, interest 0, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(zero)); } @@ -801,119 +693,86 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio public void latePaymentOfInterest() { Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.valueOf(5L), BigDecimal.ZERO, BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(five)); // Principal 0, interest 5, fee 0, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(five), refEq(zero), refEq(zero)); } @Test public void latePaymentOfFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, false, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.valueOf(5L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(ten)); // Principal 0, interest 0, fee 5, penalty 0 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(five), refEq(zero)); } @Test public void latePaymentOfPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito - .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(0L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(five)); // Principal 0, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(five)); } @Test public void latePaymentOfPrincipalAndPenalty() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, five, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(5L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(0L), BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.valueOf(5L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 5, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(five)); - // Unprocessed: 5, outstanding: 5 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(five)); - // Unprocessed: 0, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(zero)); // Principal 5, interest 0, fee 0, penalty 5 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(five), refEq(zero), refEq(zero), refEq(five)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, six, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), - BigDecimal.valueOf(0L), BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); + BigDecimal.ZERO, BigDecimal.valueOf(2L), BigDecimal.valueOf(6L), false, null, BigDecimal.ZERO)); LoanTransaction loanTransaction = Mockito .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 6 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 4, outstanding: 0 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(four)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(one)); // Principal 3, interest 0, fee 1, penalty 6 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(six)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -922,22 +781,16 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(six)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(one)); // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPartialPenalty() { - Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(1)); + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.ONE); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), BigDecimal.valueOf(2L), BigDecimal.valueOf(2L), BigDecimal.valueOf(4L), false, null, BigDecimal.ZERO)); @@ -945,21 +798,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 1, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(one)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(zero)); // Principal 0, interest 0, fee 0, penalty 1 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), refEq(zero), refEq(one)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndPartialInterest() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(5)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -968,21 +815,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 5, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(five)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(one)); - // Unprocessed: 0, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(zero)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(zero)); // Principal 0, interest 1, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(zero), refEq(one), refEq(zero), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPartialPrincipal() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(8)); LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -991,21 +832,15 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 8, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(eight)); - // Unprocessed: 4, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(four)); - // Unprocessed: 2, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(two)); - // Unprocessed: 0, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(zero)); // Principal 2, interest 2, fee 0, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(two), refEq(zero), refEq(four)); } @Test public void latePaymentOfPrincipalAndPenaltyAndFeeAndInterestButNotEnoughOnlyForPenaltyAndInterestAndPrincipalAndPartialFee() { + LoanCharge loanCharge1 = createLoanCharge(transactionDate, true, four, null); + LoanCharge loanCharge2 = createLoanCharge(transactionDate, false, two, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); Money transactionAmount = ten; LoanRepaymentScheduleInstallment installment = Mockito .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), @@ -1014,16 +849,95 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, transactionMappings, charges); - - // Unprocessed: 10, outstanding: 4 - Mockito.verify(installment, Mockito.times(1)).payPenaltyChargesComponent(eq(lateDate), refEq(ten)); - // Unprocessed: 6, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payInterestComponent(eq(lateDate), refEq(six)); - // Unprocessed: 4, outstanding: 3 - Mockito.verify(installment, Mockito.times(1)).payPrincipalComponent(eq(lateDate), refEq(four)); - // Unprocessed: 1, outstanding: 2 - Mockito.verify(installment, Mockito.times(1)).payFeeChargesComponent(eq(lateDate), refEq(one)); // Principal 3, interest 2, fee 1, penalty 4 Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(two), refEq(one), refEq(four)); } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButLaterThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 1, ZoneOffset.UTC))); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 3, interest 0, fee 1, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(one), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButEarlierThanRepayment() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = four; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(2L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + Mockito.when(loanTransaction.getCreatedDate()).thenReturn(Optional.of(OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 3, ZoneOffset.UTC))); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 2, interest 0, fee 2, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(two), refEq(zero), refEq(two), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoChargeCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, one, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, three, null); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 3, interest 0, fee 2, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(two), refEq(zero)); + } + + @Test + public void latePaymentOfPrincipalAndFeeWhereFeeIsSameDayButNoTransactionCreatedDate() { + LoanCharge loanCharge1 = createLoanCharge(lateDate, false, three, null); + LoanCharge loanCharge2 = createLoanCharge(lateDate, false, one, OffsetDateTime.of(2023, 1, 1, 1, 1, 1, 2, ZoneOffset.UTC)); + Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2)); + Money transactionAmount = five; + LoanRepaymentScheduleInstallment installment = Mockito + .spy(new LoanRepaymentScheduleInstallment(loan, 1, firstInstallmentToDate, firstInstallmentDueDate, BigDecimal.valueOf(3L), + BigDecimal.ZERO, BigDecimal.valueOf(4L), BigDecimal.ZERO, false, null, BigDecimal.ZERO)); + LoanTransaction loanTransaction = Mockito + .spy(LoanTransaction.repayment(office, transactionAmount, null, lateDate, ExternalId.empty())); + underTest.handleTransactionThatIsALateRepaymentOfInstallment(installment, null, loanTransaction, transactionAmount, + transactionMappings, charges); + + // Principal 3, interest 0, fee 2, penalty 0 + Mockito.verify(loanTransaction, Mockito.times(1)).updateComponents(refEq(three), refEq(zero), refEq(two), refEq(zero)); + } + + @NotNull + private LoanCharge createLoanCharge(LocalDate dueDate, boolean isPenalty, Money amount, OffsetDateTime createdDate) { + LoanCharge loanCharge = Mockito.mock(LoanCharge.class); + Mockito.lenient().when(loanCharge.isActive()).thenReturn(true); + Mockito.lenient().when(loanCharge.isNotFullyPaid()).thenReturn(true); + Mockito.lenient().when(loanCharge.getEffectiveDueDate()).thenReturn(dueDate); + Mockito.lenient().when(loanCharge.isPenaltyCharge()).thenReturn(isPenalty); + if (createdDate != null) { + Mockito.lenient().when(loanCharge.getCreatedDate()).thenReturn(Optional.of(createdDate)); + } + Mockito.lenient().when(loanCharge.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(amount); + return loanCharge; + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 77c2ece77..ed8a3afe5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -6802,7 +6802,8 @@ public class ClientLoanIntegrationTest { final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); - PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY); final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges(new PostChargesRequest().penalty(true).amount(10.0) @@ -6862,6 +6863,337 @@ public class ClientLoanIntegrationTest { } } + @Test + public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeBeforeRepayment() { + try { + List dueDateRelatedRepaymentStrategies = Arrays.asList( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, + LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-----------------------------------NEW CLIENT-----------------------------------------"); + final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); + final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); + LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + repaymentStrategyCode); + final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); + LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); + PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges( + new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue()) + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) + .chargeAppliesTo(1).locale("en").active(true)); + LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------"); + final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual( + clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023", + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDisbursementUUID = UUID.randomUUID().toString(); + PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID)); + Long loanId = disbursedLoanResult.getResourceId(); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-------------------------------ADD CHARGES-------------------------------------------"); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN) + .locale("en").amount(10.0).dueDate("10 January 2023")); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String firstRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023") + .locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID)); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 0.0, 9.0, 0.0); + } + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false); + } + } + + @Test + public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeAfterRepayment() { + try { + List dueDateRelatedRepaymentStrategies = Arrays.asList( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, + LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-----------------------------------NEW CLIENT-----------------------------------------"); + final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); + final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); + LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + repaymentStrategyCode); + final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); + LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); + PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges( + new PostChargesRequest().penalty(false).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue()) + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) + .chargeAppliesTo(1).locale("en").active(true)); + LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------"); + final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual( + clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023", + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDisbursementUUID = UUID.randomUUID().toString(); + PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID)); + Long loanId = disbursedLoanResult.getResourceId(); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String firstRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023") + .locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID)); + LOG.info("-------------------------------ADD CHARGES-------------------------------------------"); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN) + .locale("en").amount(10.0).dueDate("10 January 2023")); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + } + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false); + } + } + + @Test + public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeBeforeRepaymentOnInstallmentDueDate() { + try { + List dueDateRelatedRepaymentStrategies = Arrays.asList( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, + LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) { + LOG.info("-----------------------------------REPAYMENT SCEDULE CODE : {}-----------------------------------------", + repaymentStrategyCode); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-----------------------------------NEW CLIENT-----------------------------------------"); + final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); + final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); + LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + repaymentStrategyCode); + final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); + LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); + PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges( + new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue()) + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) + .chargeAppliesTo(1).locale("en").active(true)); + LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------"); + final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual( + clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023", + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDisbursementUUID = UUID.randomUUID().toString(); + PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID)); + Long loanId = disbursedLoanResult.getResourceId(); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("31 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-------------------------------ADD CHARGES-------------------------------------------"); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN) + .locale("en").amount(10.0).dueDate("31 January 2023")); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String firstRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("31 January 2023") + .locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID)); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 0.0, 9.0, 0.0); + } + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false); + } + } + + @Test + public void testLoanTransactionOrderAndRepaymentAllocationWithSameDayChargeAfterRepaymentOnInstallmentDueDate() { + try { + List dueDateRelatedRepaymentStrategies = Arrays.asList( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, + LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) { + LOG.info("-----------------------------------REPAYMENT SCEDULE CODE : {}-----------------------------------------", + repaymentStrategyCode); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-----------------------------------NEW CLIENT-----------------------------------------"); + final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); + final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); + LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + repaymentStrategyCode); + final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); + LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); + PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges( + new PostChargesRequest().penalty(true).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue()) + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) + .chargeAppliesTo(1).locale("en").active(true)); + LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------"); + final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual( + clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023", + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDisbursementUUID = UUID.randomUUID().toString(); + PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID)); + Long loanId = disbursedLoanResult.getResourceId(); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("31 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String firstRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("31 January 2023") + .locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID)); + LOG.info("-------------------------------ADD CHARGES-------------------------------------------"); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN) + .locale("en").amount(10.0).dueDate("31 January 2023")); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + } + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false); + } + } + + @Test + public void testLoanTransactionOrderAndRepaymentAllocationMultipleActions() { + try { + List dueDateRelatedRepaymentStrategies = Arrays.asList( + LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY, + LoanProductTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + for (String repaymentStrategyCode : dueDateRelatedRepaymentStrategies) { + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", true); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.TRUE); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("01 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-----------------------------------NEW CLIENT-----------------------------------------"); + final PostClientsRequest newClient = createRandomClientWithDate("01 January 2023"); + final PostClientsResponse clientResponse = CLIENT_HELPER.createClient(newClient); + LOG.info("-----------------------------------NEW LOAN PRODUCT-----------------------------------------"); + PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct( + repaymentStrategyCode); + final PostLoanProductsResponse loanProductResponse = LOAN_PRODUCT_HELPER.createLoanProduct(loanProductsRequest); + LOG.info("-----------------------------------CREATE CHARGES-----------------------------------------"); + PostChargesResponse penaltyCharge = CHARGES_HELPER.createCharges( + new PostChargesRequest().penalty(false).amount(10.0).chargeCalculationType(ChargeCalculationType.FLAT.getValue()) + .chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) + .chargeAppliesTo(1).locale("en").active(true)); + LOG.info("-----------------------------------SUBMIT LOAN-----------------------------------------"); + final PostLoansResponse loanApplicationResult = applyForLoanApplicationForOnePeriod30DaysLongNoInterestPeriodicAccrual( + clientResponse.getResourceId(), loanProductResponse.getResourceId(), "01 January 2023", + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY); + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + PostLoansLoanIdResponse approvedLoanResult = LOAN_TRANSACTION_HELPER.approveLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDisbursementUUID = UUID.randomUUID().toString(); + PostLoansLoanIdResponse disbursedLoanResult = LOAN_TRANSACTION_HELPER.disburseLoan(loanApplicationResult.getResourceId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en").externalId(loanDisbursementUUID)); + Long loanId = disbursedLoanResult.getResourceId(); + BUSINESS_DATE_HELPER.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) + .date("07 January 2023").dateFormat(DATETIME_PATTERN).locale("en")); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String firstRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse firstRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023") + .locale("en").transactionAmount(9.0).externalId(firstRepaymentUUID)); + LOG.info("-------------------------------ADD CHARGES-------------------------------------------"); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = LOAN_TRANSACTION_HELPER.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN) + .locale("en").amount(10.0).dueDate("07 January 2023")); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String secondRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse secondRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("06 January 2023") + .locale("en").transactionAmount(9.0).externalId(secondRepaymentUUID)); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, secondRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + LOG.info("-------------------------------DO A PARTIAL REPAYMENT-------------------------------------------"); + String thirdRepaymentUUID = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse thirdRepaymentResult = LOAN_TRANSACTION_HELPER.makeLoanRepayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("07 January 2023") + .locale("en").transactionAmount(9.0).externalId(thirdRepaymentUUID)); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, thirdRepaymentResult.getResourceExternalId(), 9.0, 0.0, 0.0, 9.0, 0.0, 0.0); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, secondRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + LOG.info("-------------------------------CHECK LOAN TRANSACTION ALLOCATION-------------------------------------------"); + checkLoanTransactionAllocation(loanId, firstRepaymentResult.getResourceExternalId(), 9.0, 9.0, 0.0, 0.0, 0.0, 0.0); + } + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC, RESPONSE_SPEC, Boolean.FALSE); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(REQUEST_SPEC, RESPONSE_SPEC, "42", false); + } + } + + private void checkLoanTransactionAllocation(Long loanId, String transactionExternalId, double amount, double principalPortion, + double interestPortion, double feePortion, double penaltyPortion, double overpaymentPortion) { + GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails = LOAN_TRANSACTION_HELPER.getLoanTransactionDetails(loanId, + transactionExternalId); + assertEquals(amount, transactionDetails.getAmount()); + assertEquals(principalPortion, transactionDetails.getPrincipalPortion()); + assertEquals(interestPortion, transactionDetails.getInterestPortion()); + assertEquals(feePortion, transactionDetails.getFeeChargesPortion()); + assertEquals(penaltyPortion, transactionDetails.getPenaltyChargesPortion()); + assertEquals(overpaymentPortion, transactionDetails.getOverpaymentPortion()); + } + private void checkLoanTransactionOrder(Long loanId, String... transactionUUIDs) { LOG.info("-------------------------------CHECK LOAN TRANSACTION ORDER-------------------------------------------"); GetLoansLoanIdResponse loanDetailsResult = LOAN_TRANSACTION_HELPER.getLoanDetails(loanId); @@ -8023,7 +8355,7 @@ public class ClientLoanIntegrationTest { .principal(BigDecimal.valueOf(1000.0)).loanType("individual")); } - private PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() { + private PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(String repaymentStrategyCode) { return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))// .shortName(Utils.uniqueRandomStringGenerator("", 4))// .description("Loan Product Description")// @@ -8050,8 +8382,7 @@ public class ClientLoanIntegrationTest { .interestType(0)// .isEqualAmortization(false)// .interestCalculationPeriodType(1)// - .transactionProcessingStrategyCode( - LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY)// + .transactionProcessingStrategyCode(repaymentStrategyCode)// .daysInYearType(1)// .daysInMonthType(1)// .canDefineInstallmentAmount(true)//