This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 8803c554f FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)
8803c554f is described below
commit 8803c554f54bd97f4b7fd7b24e58720ad4671361
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Mar 1 11:55:36 2024 +0100
FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)
---
.../portfolio/loanaccount/domain/Loan.java | 33 +-
.../loanaccount/domain/LoanTransaction.java | 15 +-
.../domain/LoanAccountDomainServiceJpa.java | 4 +-
.../service/LoanReadPlatformServiceImpl.java | 2 +-
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 25 +-
.../integrationtests/BaseLoanIntegrationTest.java | 11 +-
.../LoanRepaymentScheduleWithDownPaymentTest.java | 578 +++++++++++++--------
7 files changed, 428 insertions(+), 240 deletions(-)
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 f069466f6..deeb280ce 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
@@ -2967,8 +2967,24 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
Money downPaymentMoney = Money.of(getCurrency(),
MathUtil.percentageOf(disbursementTransaction.getAmount(),
disbursedAmountPercentageForDownPayment, 19));
- Money adjustedDownPaymentMoney = MathUtil
-
.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
+ Money adjustedDownPaymentMoney = switch
(getLoanProductRelatedDetail().getLoanScheduleType()) {
+ // For Cumulative loan: To check whether the loan was overpaid
when the disbursement happened and to get the
+ // proper amount after the disbursement we are using two balances:
+ // 1. Whether the loan is still overpaid after the disbursement,
+ // 2. if the loan is not overpaid anymore after the disbursement,
but was it more overpaid than the
+ // calculated down-payment amount?
+ case CUMULATIVE -> {
+ if (getTotalOverpaidAsMoney().isGreaterThanZero()) {
+ yield Money.zero(getCurrency());
+ }
+ yield
MathUtil.negativeToZero(downPaymentMoney.minus(MathUtil.negativeToZero(disbursementTransaction
+
.getAmount(getCurrency()).minus(disbursementTransaction.getOutstandingLoanBalanceMoney(getCurrency())))));
+ }
+ // For Progressive loan: Disbursement transaction portion balances
are enough to see whether the overpayment
+ // amount was more than the calculated down-payment amount
+ case PROGRESSIVE ->
+
MathUtil.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
+ };
if (adjustedDownPaymentMoney.isGreaterThanZero()) {
LoanTransaction downPaymentTransaction =
LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null,
disbursedOn,
externalId);
@@ -3602,7 +3618,11 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
final LocalDate currentTransactionDate =
loanTransaction.getTransactionDate();
for (final LoanTransaction previousTransaction : loanTransactions) {
if (!previousTransaction.isDisbursement() &&
previousTransaction.isNotReversed()
- && !DateUtils.isAfter(currentTransactionDate,
previousTransaction.getTransactionDate())) {
+ && (DateUtils.isBefore(currentTransactionDate,
previousTransaction.getTransactionDate())
+ || (DateUtils.isEqual(currentTransactionDate,
previousTransaction.getTransactionDate())
+ && ((loanTransaction.getId() == null &&
previousTransaction.getId() == null)
+ || (loanTransaction.getId() !=
null && (previousTransaction.getId() == null
+ ||
loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) {
isChronologicallyLatestRepaymentOrWaiver = false;
break;
}
@@ -5958,7 +5978,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
if (loanTransaction.isDisbursement() ||
loanTransaction.isIncomePosting()) {
outstanding =
outstanding.plus(loanTransaction.getAmount(getCurrency()))
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
-
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (loanTransaction.isChargeback() ||
loanTransaction.isCreditBalanceRefund()) {
Money transactionOutstanding =
loanTransaction.getPrincipalPortion(getCurrency());
if
(!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
@@ -5977,8 +5997,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
}
}
outstanding = outstanding.plus(transactionOutstanding);
-
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
-
+
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else {
if (this.loanInterestRecalculationDetails != null
&&
this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
@@ -5987,7 +6006,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
} else {
outstanding =
outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
}
-
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
+
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
}
}
}
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 f14835273..960ef6138 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
@@ -46,6 +46,7 @@ import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -147,14 +148,16 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom {
penaltyChargesPortion, overPaymentPortion, reversed,
paymentDetail, externalId);
}
- public static LoanTransaction disbursement(final Office office, final
Money amount, final PaymentDetail paymentDetail,
+ public static LoanTransaction disbursement(final Loan loan, final Money
amount, final PaymentDetail paymentDetail,
final LocalDate disbursementDate, final ExternalId externalId,
final Money loanTotalOverpaid) {
// We need to set the overpayment amount because it could happen the
transaction got saved before the proper
// portion calculation and side effect would be reverse-replay
- Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ?
loanTotalOverpaid : amount;
- LoanTransaction disbursement = new LoanTransaction(null, office,
LoanTransactionType.DISBURSEMENT, paymentDetail,
+ LoanTransaction disbursement = new LoanTransaction(null,
loan.getOffice(), LoanTransactionType.DISBURSEMENT, paymentDetail,
amount.getAmount(), disbursementDate, externalId);
- disbursement.setOverPayments(overPaymentPortion);
+ if
(LoanScheduleType.PROGRESSIVE.equals(loan.getLoanProductRelatedDetail().getLoanScheduleType()))
{
+ Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid)
? loanTotalOverpaid : amount;
+ disbursement.setOverPayments(overPaymentPortion);
+ }
return disbursement;
}
@@ -911,6 +914,10 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom {
return outstandingLoanBalance;
}
+ public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency
currency) {
+ return Money.of(currency, this.outstandingLoanBalance);
+ }
+
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 8039a6479..d6c542e28 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -534,8 +534,8 @@ public class LoanAccountDomainServiceJpa implements
LoanAccountDomainService {
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money amount = Money.of(loan.getCurrency(), transactionAmount);
- LoanTransaction disbursementTransaction =
LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail,
transactionDate,
- txnExternalId, loan.getTotalOverpaidAsMoney());
+ LoanTransaction disbursementTransaction =
LoanTransaction.disbursement(loan, amount, paymentDetail, transactionDate,
txnExternalId,
+ loan.getTotalOverpaidAsMoney());
// Subtract Previous loan outstanding balance from netDisbursalAmount
loan.deductFromNetDisbursalAmount(transactionAmount);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 6d6643f9d..9b0be1385 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1715,7 +1715,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
public Collection<DisbursementData> retrieveLoanDisbursementDetails(final
Long loanId) {
final LoanDisbursementDetailMapper rm = new
LoanDisbursementDetailMapper(sqlGenerator);
final String sql = "select " + rm.schema()
- + " where dd.loan_id=? and dd.is_reversed=false group by
dd.id, lc.amount_waived_derived order by
dd.expected_disburse_date,dd.disbursedon_date";
+ + " where dd.loan_id=? and dd.is_reversed=false group by
dd.id, lc.amount_waived_derived order by
dd.expected_disburse_date,dd.disbursedon_date,dd.id";
return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 479370353..42d4ff675 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -435,8 +435,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
} else {
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
- disbursementTransaction =
LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail,
- actualDisbursementDate, txnExternalId,
loan.getTotalOverpaidAsMoney());
+ disbursementTransaction = LoanTransaction.disbursement(loan,
amountToDisburse, paymentDetail, actualDisbursementDate,
+ txnExternalId, loan.getTotalOverpaidAsMoney());
disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction);
}
@@ -461,6 +461,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
if (disbursementTransaction != null) {
loanTransactionRepository.saveAndFlush(disbursementTransaction);
}
+ if (changedTransactionDetail != null) {
+ for (final Map.Entry<Long, LoanTransaction> mapEntry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
mapEntry.getValue());
+ }
+ // Trigger transaction replayed event
+
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
+ }
if (loan.isAutoRepaymentForDownPaymentEnabled()) {
// updating linked savings account for auto down payment
transaction for disbursement to savings account
if (isAccountTransfer &&
loan.shouldCreateStandingInstructionAtDisbursement()) {
@@ -490,14 +498,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
}
}
if (!changes.isEmpty()) {
- if (changedTransactionDetail != null) {
- for (final Map.Entry<Long, LoanTransaction> mapEntry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
-
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
mapEntry.getValue());
- }
- // Trigger transaction replayed event
-
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
- }
+
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final String noteText =
command.stringValueOfParameterNamed("note");
@@ -563,8 +564,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new
LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
}
- loan.updateLoanSummaryAndStatus();
-
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loan.getId()) //
@@ -751,7 +750,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
} else {
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
- LoanTransaction disbursementTransaction =
LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
+ LoanTransaction disbursementTransaction =
LoanTransaction.disbursement(loan, disburseAmount, paymentDetail,
actualDisbursementDate, txnExternalId,
loan.getTotalOverpaidAsMoney());
disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 0d74fc161..fc4aaf202 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -159,7 +159,6 @@ public abstract class BaseLoanIntegrationTest {
.includeInBorrowerCycle(false)//
.currencyCode("USD")//
.digitsAfterDecimal(2)//
- .inMultiplesOf(0)//
.installmentAmountInMultiplesOf(1)//
.useBorrowerCycle(false)//
.minPrincipal(100.0)//
@@ -643,7 +642,14 @@ public abstract class BaseLoanIntegrationTest {
protected TransactionExt transaction(double amount, String type, String
date, double outstandingAmount, double principalPortion,
double interestPortion, double feePortion, double penaltyPortion,
double unrecognizedIncomePortion, double overpaymentPortion) {
return new TransactionExt(amount, type, date, outstandingAmount,
principalPortion, interestPortion, feePortion, penaltyPortion,
- unrecognizedIncomePortion, overpaymentPortion);
+ unrecognizedIncomePortion, overpaymentPortion, false);
+ }
+
+ protected TransactionExt transaction(double amount, String type, String
date, double outstandingAmount, double principalPortion,
+ double interestPortion, double feePortion, double penaltyPortion,
double unrecognizedIncomePortion, double overpaymentPortion,
+ boolean reversed) {
+ return new TransactionExt(amount, type, date, outstandingAmount,
principalPortion, interestPortion, feePortion, penaltyPortion,
+ unrecognizedIncomePortion, overpaymentPortion, reversed);
}
protected Installment installment(double principalAmount, Boolean
completed, String dueDate) {
@@ -766,6 +772,7 @@ public abstract class BaseLoanIntegrationTest {
Double penaltyPortion;
Double unrecognizedPortion;
Double overpaymentPortion;
+ Boolean reversed;
}
@ToString
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
index 2db85269b..d9cf82d3c 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java
@@ -18,37 +18,33 @@
*/
package org.apache.fineract.integrationtests;
-import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.DATETIME_PATTERN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import io.restassured.builder.RequestSpecBuilder;
-import io.restassured.builder.ResponseSpecBuilder;
-import io.restassured.http.ContentType;
-import io.restassured.specification.RequestSpecification;
-import io.restassured.specification.ResponseSpecification;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
-import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -59,44 +55,19 @@ import
org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
-import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
-import
org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
-import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
-import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-@ExtendWith(LoanTestLifecycleExtension.class)
-public class LoanRepaymentScheduleWithDownPaymentTest {
-
- private ResponseSpecification responseSpec;
- private RequestSpecification requestSpec;
- private LoanTransactionHelper loanTransactionHelper;
- private ClientHelper clientHelper;
- private AccountHelper accountHelper;
- private JournalEntryHelper journalEntryHelper;
-
- @BeforeEach
- public void setup() {
- Utils.initializeRESTAssured();
- requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
- requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
- responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
- loanTransactionHelper = new LoanTransactionHelper(requestSpec,
responseSpec);
- clientHelper = new ClientHelper(requestSpec, responseSpec);
- accountHelper = new AccountHelper(requestSpec, responseSpec);
- journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
- }
+
+public class LoanRepaymentScheduleWithDownPaymentTest extends
BaseLoanIntegrationTest {
@Test
public void loanRepaymentScheduleWithSimpleDisbursementAndDownPayment() {
@@ -1235,165 +1206,407 @@ public class LoanRepaymentScheduleWithDownPaymentTest
{
}
@Test
- public void downPaymentOnOverpaidLoan() {
- try {
-
- // Set business date
+ public void downPaymentOnOverpaidProgressiveLoan() {
+ runAt("03 March 2023", () -> {
LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+ final PostLoanProductsRequest loanProductsRequest =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
+
+ PostLoanProductsResponse loanProductsResponse =
loanTransactionHelper.createLoanProduct(loanProductsRequest);
+
+ String disbursementDateStr = DateUtils.format(disbursementDate,
DATETIME_PATTERN);
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(new
PostLoansRequest().clientId(client.getResourceId())
+
.productId(loanProductsResponse.getResourceId()).loanType("individual").locale("en").dateFormat(DATETIME_PATTERN)
+
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+ .maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
+
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()).expectedDisbursementDate(disbursementDateStr)
+
.dateFormat(DATETIME_PATTERN).submittedOnDate(disbursementDateStr).repaymentFrequencyType(0).repaymentEvery(30)
+
.numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0).principal(BigDecimal.valueOf(1000))
+
.loanType("individual").maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
+
+ loanTransactionHelper.approveLoan(loanResponse.getResourceId(),
+ new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+ .approvedOnDate(disbursementDateStr).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new
PostLoansLoanIdRequest().actualDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+ assertTrue(loanDetails.getStatus().getActive());
+
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(750.0, 0.0, 750.0, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
+ verifyJournalEntries(loanResponse.getResourceId(),
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
+ journalEntry(1000.0, suspenseClearingAccount, "CREDIT"), //
+ journalEntry(250.0, loansReceivableAccount, "CREDIT"), //
+ journalEntry(250.0, suspenseClearingAccount, "DEBIT") //
+ );
+
+
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(), new
PostLoansLoanIdTransactionsRequest()
+ .dateFormat("dd MMMM yyyy").transactionDate("03 March
2023").locale("en").transactionAmount(800.0));
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(750.0, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
+ );
+ assertTrue(loanDetails.getStatus().getOverpaid());
+ assertEquals(50.0, loanDetails.getTotalOverpaid());
+
+ // second disbursement
+ disbursementDate = LocalDate.of(2023, 3, 5);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
- // Accounts oof periodic accrual
- final Account assetAccount = accountHelper.createAssetAccount();
- final Account incomeAccount = accountHelper.createIncomeAccount();
- final Account expenseAccount =
accountHelper.createExpenseAccount();
- final Account overpaymentAccount =
accountHelper.createLiabilityAccount();
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(20.00)).locale("en"));
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(765.0, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 30.0) //
+ );
+ assertTrue(loanDetails.getStatus().getOverpaid());
+ assertEquals(30.0, loanDetails.getTotalOverpaid());
- // Loan ExternalId
- String loanExternalIdStr = UUID.randomUUID().toString();
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(30.00)).locale("en"));
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+ installment(787.5, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
- // down-payment configuration
- Boolean enableDownPayment = true;
- BigDecimal disbursedAmountPercentageForDownPayment =
BigDecimal.valueOf(25);
- Boolean enableAutoRepaymentForDownPayment = true;
+ assertTrue(loanDetails.getStatus().getClosedObligationsMet());
+ assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
+ assertEquals(null, loanDetails.getTotalOverpaid());
- final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ PostLoansLoanIdTransactionsResponse repayment =
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(),
+ new PostLoansLoanIdTransactionsRequest().dateFormat("dd
MMMM yyyy").transactionDate("05 March 2023").locale("en")
+ .transactionAmount(1.0));
- // Loan Product creation with down-payment configuration
- final GetLoanProductsProductIdResponse
getLoanProductsProductResponse =
createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting(
- loanTransactionHelper, enableDownPayment, "25",
enableAutoRepaymentForDownPayment, assetAccount, incomeAccount,
- expenseAccount, overpaymentAccount);
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 1.0) //
+ );
+ assertTrue(loanDetails.getStatus().getOverpaid());
+ assertEquals(1.0, loanDetails.getTotalOverpaid());
- assertNotNull(getLoanProductsProductResponse);
- assertEquals(enableDownPayment,
getLoanProductsProductResponse.getEnableDownPayment());
- assertEquals(0,
getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
- .compareTo(disbursedAmountPercentageForDownPayment));
- assertEquals(enableAutoRepaymentForDownPayment,
getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(40.00)).locale("en"));
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(40.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+ installment(10.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 1.0), //
+ transaction(40.0, "Disbursement", "05 March 2023", 39.0,
0.0, 0.0, 0.0, 0.0, 0.0, 1.0), //
+ transaction(9.0, "Down Payment", "05 March 2023", 30.0,
9.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
- final Integer loanId =
createLoanAccountWithAdvancedPaymentAllocation(clientId,
getLoanProductsProductResponse.getId(),
- loanExternalIdStr);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
- // Retrieve Loan with loanId
+
loanTransactionHelper.reverseLoanTransaction(repayment.getLoanId(),
repayment.getResourceId(),
+ new
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05
March 2023")
+ .transactionAmount(0.0).locale("en"));
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(40.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+ installment(10.0, 0.0, 1.0, false, "05 March 2023"), //
+ installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(1.0, "Repayment", "05 March 2023", 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 1.0, true), //
+ transaction(40.0, "Disbursement", "05 March 2023", 40.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(9.0, "Down Payment", "05 March 2023", 31.0,
9.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
- // verify down-payment details for Loan
- assertNotNull(loanDetails);
- assertEquals(enableDownPayment,
loanDetails.getEnableDownPayment());
- assertEquals(0,
loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
- assertEquals(enableAutoRepaymentForDownPayment,
loanDetails.getEnableAutoRepaymentForDownPayment());
+ assertTrue(loanDetails.getStatus().getActive());
+ assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
+ });
+ }
- // first disbursement
- loanTransactionHelper.disburseLoanWithTransactionAmount("03 March
2023", loanId, "1000");
+ @Test
+ public void downPaymentOnOverpaidCumulativeLoan() {
+ runAt("03 March 2023", () -> {
+ LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
- // verify down-payment transaction created
- checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f,
0.0f, loanId);
+ PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
- // verify journal entries for down-payment
- journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
"03 March 2023",
- new JournalEntry(250,
JournalEntry.TransactionType.CREDIT));
- journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
"03 March 2023",
- new JournalEntry(250, JournalEntry.TransactionType.DEBIT));
+ final PostLoanProductsRequest loanProductsRequest =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
+
.loanScheduleType(LoanScheduleType.CUMULATIVE.name()).paymentAllocation(null)
+ .transactionProcessingStrategyCode(
+
DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
- // verify installment details
- assertEquals(LocalDate.of(2023, 3, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate());
- assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding());
- assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
- assertEquals(LocalDate.of(2023, 3, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
- assertEquals(250.0,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
- assertEquals(true,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod());
- assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
- assertEquals(LocalDate.of(2023, 4, 2),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
- assertEquals(750.0,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
- assertEquals(false,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod());
+ PostLoanProductsResponse loanProductsResponse =
loanTransactionHelper.createLoanProduct(loanProductsRequest);
- loanTransactionHelper.makeLoanRepayment((long) loanId, new
PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
- .transactionDate("03 March
2023").locale("en").transactionAmount(800.0));
+ String disbursementDateStr = DateUtils.format(disbursementDate,
DATETIME_PATTERN);
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(new
PostLoansRequest().clientId(client.getResourceId())
+
.productId(loanProductsResponse.getResourceId()).loanType("individual").locale("en").dateFormat(DATETIME_PATTERN)
+
.amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0)
+ .maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
+ .transactionProcessingStrategyCode(
+
DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
+
.expectedDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN).submittedOnDate(disbursementDateStr)
+
.repaymentFrequencyType(0).repaymentEvery(30).numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0)
+
.principal(BigDecimal.valueOf(1000)).loanType("individual").maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
- // verify down-payment details for Loan
+ loanTransactionHelper.approveLoan(loanResponse.getResourceId(),
+ new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+ .approvedOnDate(disbursementDateStr).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new
PostLoansLoanIdRequest().actualDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+ assertTrue(loanDetails.getStatus().getActive());
+
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(750.0, 0.0, 750.0, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
+ verifyJournalEntries(loanResponse.getResourceId(),
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
+ journalEntry(1000.0, suspenseClearingAccount, "CREDIT"), //
+ journalEntry(250.0, loansReceivableAccount, "CREDIT"), //
+ journalEntry(250.0, suspenseClearingAccount, "DEBIT") //
+ );
+
+ String externalId = UUID.randomUUID().toString();
+
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(),
+ new PostLoansLoanIdTransactionsRequest().dateFormat("dd
MMMM yyyy").transactionDate("03 March 2023").locale("en")
+ .transactionAmount(800.0).externalId(externalId));
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(750.0, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
750.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
+ );
assertTrue(loanDetails.getStatus().getOverpaid());
assertEquals(50.0, loanDetails.getTotalOverpaid());
// second disbursement
-
disbursementDate = LocalDate.of(2023, 3, 5);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
- loanTransactionHelper.disburseLoanWithTransactionAmount("05 March
2023", loanId, "20");
-
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
-
-
assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement());
- assertEquals(1000.0,
loanDetails.getTransactions().get(0).getAmount());
- assertEquals("loanTransactionType.downPayment",
loanDetails.getTransactions().get(1).getType().getCode());
- assertEquals(250.0,
loanDetails.getTransactions().get(1).getAmount());
-
assertTrue(loanDetails.getTransactions().get(2).getType().getRepayment());
- assertEquals(800.0,
loanDetails.getTransactions().get(2).getAmount());
-
assertTrue(loanDetails.getTransactions().get(3).getType().getDisbursement());
- assertEquals(20.0,
loanDetails.getTransactions().get(3).getAmount());
- assertEquals(0.0,
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
- assertEquals(4, loanDetails.getTransactions().size());
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(20.00)).locale("en"));
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(765.0, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
770.0, 0.0, 0.0, 0.0, 0.0, 30.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
assertTrue(loanDetails.getStatus().getOverpaid());
assertEquals(30.0, loanDetails.getTotalOverpaid());
- loanTransactionHelper.disburseLoanWithTransactionAmount("05 March
2023", loanId, "30");
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
-
assertTrue(loanDetails.getTransactions().get(4).getType().getDisbursement());
- assertEquals(30.0,
loanDetails.getTransactions().get(4).getAmount());
- assertEquals(0.0,
loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
- assertEquals(5, loanDetails.getTransactions().size());
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(30.00)).locale("en"));
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+ installment(787.5, 0.0, 0.0, true, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
800.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
assertEquals(null, loanDetails.getTotalOverpaid());
- PostLoansLoanIdTransactionsResponse repayment =
loanTransactionHelper.makeLoanRepayment((long) loanId,
- new PostLoansLoanIdTransactionsRequest().dateFormat("dd
MMMM yyyy").transactionDate("05 March 2023").locale("en")
- .transactionAmount(1.0));
-
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
- assertTrue(loanDetails.getStatus().getOverpaid());
- assertEquals(1.0, loanDetails.getTotalOverpaid());
-
- loanTransactionHelper.disburseLoanWithTransactionAmount("05 March
2023", loanId, "40");
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
-
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
- assertEquals(1.0,
loanDetails.getTransactions().get(5).getAmount());
-
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
- assertEquals(40.0,
loanDetails.getTransactions().get(6).getAmount());
- assertEquals(39.0,
loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
- assertEquals("loanTransactionType.downPayment",
loanDetails.getTransactions().get(7).getType().getCode());
- assertEquals(9.0,
loanDetails.getTransactions().get(7).getAmount());
- assertEquals(30.0,
loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
- assertEquals(8, loanDetails.getTransactions().size());
+ loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("05
March 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(40.00)).locale("en"));
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(40.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
+ installment(10.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(817.5, 0.0, 30.0, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
800.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(20.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(40.0, "Disbursement", "05 March 2023", 40.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(10.0, "Down Payment", "05 March 2023", 30.0,
10.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
assertTrue(loanDetails.getStatus().getActive());
assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
-
loanTransactionHelper.reverseLoanTransaction(repayment.getLoanId(),
repayment.getResourceId(),
+
loanTransactionHelper.reverseLoanTransaction(loanResponse.getLoanId(),
externalId,
new
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05
March 2023")
.transactionAmount(0.0).locale("en"));
- loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
-
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
- assertEquals(1.0,
loanDetails.getTransactions().get(5).getAmount());
-
assertTrue(loanDetails.getTransactions().get(5).getManuallyReversed());
-
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
- assertEquals(40.0,
loanDetails.getTransactions().get(6).getAmount());
- assertEquals(40.0,
loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
- assertEquals("loanTransactionType.downPayment",
loanDetails.getTransactions().get(7).getType().getCode());
- assertEquals(9.0,
loanDetails.getTransactions().get(7).getAmount());
- assertEquals(31.0,
loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
- assertEquals(8, loanDetails.getTransactions().size());
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
+ // Verify Repayment Schedule
+ verifyRepaymentSchedule(loanResponse.getResourceId(), //
+ installment(1000.0, null, "03 March 2023"), //
+ installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
+ installment(20.0, null, "05 March 2023"), //
+ installment(30.0, null, "05 March 2023"), //
+ installment(40.0, null, "05 March 2023"), //
+ installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
+ installment(7.5, 0.0, 2.5, false, "05 March 2023"), //
+ installment(10.0, 0.0, 10.0, false, "05 March 2023"), //
+ installment(817.5, 0.0, 817.5, false, "02 April 2023") //
+ );
+ // verify transactions
+ verifyTransactions(loanResponse.getResourceId(), //
+ transaction(1000.0, "Disbursement", "03 March 2023",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(250.0, "Down Payment", "03 March 2023", 750.0,
250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(800.0, "Repayment", "03 March 2023", 0.0,
800.0, 0.0, 0.0, 0.0, 0.0, 0.0, true), //
+ transaction(20.0, "Disbursement", "05 March 2023", 770.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(30.0, "Disbursement", "05 March 2023", 800.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(40.0, "Disbursement", "05 March 2023", 840.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(10.0, "Down Payment", "05 March 2023", 830.0,
10.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
assertTrue(loanDetails.getStatus().getActive());
- assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
- } finally {
- GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
- }
+ assertEquals(830.0,
loanDetails.getSummary().getTotalOutstanding());
+ });
}
private void checkNoDownPaymentTransaction(final Integer loanID) {
@@ -1470,63 +1683,6 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
return loanTransactionHelper.getLoanProduct(loanProductId);
}
- private Integer createLoanAccountWithAdvancedPaymentAllocation(final
Integer clientID, final Long loanProductID,
- final String externalId) {
-
- String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30")
-
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
-
.withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays()
-
.withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments()
-
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03
March 2023")
- .withSubmittedOnDate("03 March
2023").withLoanType("individual").withExternalId(externalId)
- .build(clientID.toString(), loanProductID.toString(), null);
-
- final Integer loanId =
loanTransactionHelper.getLoanId(loanApplicationJSON);
- loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId,
null);
- return loanId;
- }
-
- private GetLoanProductsProductIdResponse
createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting(
- LoanTransactionHelper loanTransactionHelper, Boolean
enableDownPayment, String disbursedAmountPercentageForDownPayment,
- boolean enableAutoRepaymentForDownPayment, final Account...
accounts) {
- final String loanProductJSON = new
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
-
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL).withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
-
.addAdvancedPaymentAllocation(createDefaultPaymentAllocation()).withRepaymentAfterEvery("1").withNumberOfRepayments("1")
-
.withRepaymentTypeAsMonth().withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths()
-
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
-
.withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30")
- .withDaysInYear("365").withMoratorium("0",
"0").withMultiDisburse().withDisallowExpectedDisbursements(true)
- .withEnableDownPayment(enableDownPayment,
disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment)
- .build(null);
- final Integer loanProductId =
loanTransactionHelper.getLoanProductId(loanProductJSON);
- return loanTransactionHelper.getLoanProduct(loanProductId);
- }
-
- private List<PaymentAllocationOrder>
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
- AtomicInteger integer = new AtomicInteger(1);
- return Arrays.stream(paymentAllocationTypes).map(pat -> {
- PaymentAllocationOrder paymentAllocationOrder = new
PaymentAllocationOrder();
- paymentAllocationOrder.setPaymentAllocationRule(pat.name());
- paymentAllocationOrder.setOrder(integer.getAndIncrement());
- return paymentAllocationOrder;
- }).toList();
- }
-
- private AdvancedPaymentData createDefaultPaymentAllocation() {
- AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
- advancedPaymentData.setTransactionType("DEFAULT");
-
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
-
- List<PaymentAllocationOrder> paymentAllocationOrders =
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
- PaymentAllocationType.PAST_DUE_FEE,
PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.PAST_DUE_INTEREST,
- PaymentAllocationType.DUE_PENALTY,
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
- PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
- PaymentAllocationType.IN_ADVANCE_PRINCIPAL,
PaymentAllocationType.IN_ADVANCE_INTEREST);
-
- advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
- return advancedPaymentData;
- }
-
private Integer createApproveAndDisburseLoanAccount(final Integer
clientID, final Long loanProductID, final String externalId) {
String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")