FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)
This commit is contained in:
+26
-7
@@ -2967,8 +2967,24 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
Money downPaymentMoney = Money.of(getCurrency(),
|
Money downPaymentMoney = Money.of(getCurrency(),
|
||||||
MathUtil.percentageOf(disbursementTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19));
|
MathUtil.percentageOf(disbursementTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19));
|
||||||
|
|
||||||
Money adjustedDownPaymentMoney = MathUtil
|
Money adjustedDownPaymentMoney = switch (getLoanProductRelatedDetail().getLoanScheduleType()) {
|
||||||
.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
|
// 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()) {
|
if (adjustedDownPaymentMoney.isGreaterThanZero()) {
|
||||||
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null, disbursedOn,
|
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null, disbursedOn,
|
||||||
externalId);
|
externalId);
|
||||||
@@ -3602,7 +3618,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
|
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
|
||||||
for (final LoanTransaction previousTransaction : loanTransactions) {
|
for (final LoanTransaction previousTransaction : loanTransactions) {
|
||||||
if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()
|
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;
|
isChronologicallyLatestRepaymentOrWaiver = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -5958,7 +5978,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
||||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
||||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
||||||
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
|
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
|
||||||
Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
|
Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
|
||||||
if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
|
if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
|
||||||
@@ -5977,8 +5997,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
outstanding = outstanding.plus(transactionOutstanding);
|
outstanding = outstanding.plus(transactionOutstanding);
|
||||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (this.loanInterestRecalculationDetails != null
|
if (this.loanInterestRecalculationDetails != null
|
||||||
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
|
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
|
||||||
@@ -5987,7 +6006,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
} else {
|
} else {
|
||||||
outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
|
outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
|
||||||
}
|
}
|
||||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -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.account.data.AccountTransferData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
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.loanproduct.service.LoanEnumerations;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
|
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||||
@@ -147,14 +148,16 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
|
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) {
|
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
|
// 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
|
// portion calculation and side effect would be reverse-replay
|
||||||
Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ? loanTotalOverpaid : amount;
|
LoanTransaction disbursement = new LoanTransaction(null, loan.getOffice(), LoanTransactionType.DISBURSEMENT, paymentDetail,
|
||||||
LoanTransaction disbursement = new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail,
|
|
||||||
amount.getAmount(), disbursementDate, externalId);
|
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;
|
return disbursement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,6 +914,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return outstandingLoanBalance;
|
return outstandingLoanBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
|
||||||
|
return Money.of(currency, this.outstandingLoanBalance);
|
||||||
|
}
|
||||||
|
|
||||||
public PaymentDetail getPaymentDetail() {
|
public PaymentDetail getPaymentDetail() {
|
||||||
return this.paymentDetail;
|
return this.paymentDetail;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -534,8 +534,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
final Money amount = Money.of(loan.getCurrency(), transactionAmount);
|
final Money amount = Money.of(loan.getCurrency(), transactionAmount);
|
||||||
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate,
|
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan, amount, paymentDetail, transactionDate, txnExternalId,
|
||||||
txnExternalId, loan.getTotalOverpaidAsMoney());
|
loan.getTotalOverpaidAsMoney());
|
||||||
|
|
||||||
// Subtract Previous loan outstanding balance from netDisbursalAmount
|
// Subtract Previous loan outstanding balance from netDisbursalAmount
|
||||||
loan.deductFromNetDisbursalAmount(transactionAmount);
|
loan.deductFromNetDisbursalAmount(transactionAmount);
|
||||||
|
|||||||
+1
-1
@@ -1715,7 +1715,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
public Collection<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
|
public Collection<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
|
||||||
final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper(sqlGenerator);
|
final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper(sqlGenerator);
|
||||||
final String sql = "select " + rm.schema()
|
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
|
return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-13
@@ -435,8 +435,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
} else {
|
} else {
|
||||||
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
||||||
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
||||||
disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail,
|
disbursementTransaction = LoanTransaction.disbursement(loan, amountToDisburse, paymentDetail, actualDisbursementDate,
|
||||||
actualDisbursementDate, txnExternalId, loan.getTotalOverpaidAsMoney());
|
txnExternalId, loan.getTotalOverpaidAsMoney());
|
||||||
disbursementTransaction.updateLoan(loan);
|
disbursementTransaction.updateLoan(loan);
|
||||||
loan.addLoanTransaction(disbursementTransaction);
|
loan.addLoanTransaction(disbursementTransaction);
|
||||||
}
|
}
|
||||||
@@ -461,6 +461,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (disbursementTransaction != null) {
|
if (disbursementTransaction != null) {
|
||||||
loanTransactionRepository.saveAndFlush(disbursementTransaction);
|
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()) {
|
if (loan.isAutoRepaymentForDownPaymentEnabled()) {
|
||||||
// updating linked savings account for auto down payment transaction for disbursement to savings account
|
// updating linked savings account for auto down payment transaction for disbursement to savings account
|
||||||
if (isAccountTransfer && loan.shouldCreateStandingInstructionAtDisbursement()) {
|
if (isAccountTransfer && loan.shouldCreateStandingInstructionAtDisbursement()) {
|
||||||
@@ -490,14 +498,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!changes.isEmpty()) {
|
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);
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
final String noteText = command.stringValueOfParameterNamed("note");
|
final String noteText = command.stringValueOfParameterNamed("note");
|
||||||
@@ -563,8 +564,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
loan.updateLoanSummaryAndStatus();
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withCommandId(command.commandId()) //
|
.withCommandId(command.commandId()) //
|
||||||
.withEntityId(loan.getId()) //
|
.withEntityId(loan.getId()) //
|
||||||
@@ -751,7 +750,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
} else {
|
} else {
|
||||||
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
||||||
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
||||||
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail,
|
LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan, disburseAmount, paymentDetail,
|
||||||
actualDisbursementDate, txnExternalId, loan.getTotalOverpaidAsMoney());
|
actualDisbursementDate, txnExternalId, loan.getTotalOverpaidAsMoney());
|
||||||
disbursementTransaction.updateLoan(loan);
|
disbursementTransaction.updateLoan(loan);
|
||||||
loan.addLoanTransaction(disbursementTransaction);
|
loan.addLoanTransaction(disbursementTransaction);
|
||||||
|
|||||||
+9
-2
@@ -159,7 +159,6 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
.includeInBorrowerCycle(false)//
|
.includeInBorrowerCycle(false)//
|
||||||
.currencyCode("USD")//
|
.currencyCode("USD")//
|
||||||
.digitsAfterDecimal(2)//
|
.digitsAfterDecimal(2)//
|
||||||
.inMultiplesOf(0)//
|
|
||||||
.installmentAmountInMultiplesOf(1)//
|
.installmentAmountInMultiplesOf(1)//
|
||||||
.useBorrowerCycle(false)//
|
.useBorrowerCycle(false)//
|
||||||
.minPrincipal(100.0)//
|
.minPrincipal(100.0)//
|
||||||
@@ -643,7 +642,14 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
protected TransactionExt transaction(double amount, String type, String date, double outstandingAmount, double principalPortion,
|
protected TransactionExt transaction(double amount, String type, String date, double outstandingAmount, double principalPortion,
|
||||||
double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, double overpaymentPortion) {
|
double interestPortion, double feePortion, double penaltyPortion, double unrecognizedIncomePortion, double overpaymentPortion) {
|
||||||
return new TransactionExt(amount, type, date, outstandingAmount, principalPortion, interestPortion, feePortion, penaltyPortion,
|
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) {
|
protected Installment installment(double principalAmount, Boolean completed, String dueDate) {
|
||||||
@@ -766,6 +772,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
Double penaltyPortion;
|
Double penaltyPortion;
|
||||||
Double unrecognizedPortion;
|
Double unrecognizedPortion;
|
||||||
Double overpaymentPortion;
|
Double overpaymentPortion;
|
||||||
|
Boolean reversed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToString
|
@ToString
|
||||||
|
|||||||
+364
-208
@@ -18,37 +18,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
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.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.GetDelinquencyBucketsResponse;
|
||||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdSummary;
|
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.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.PostLoansLoanIdChargesResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
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.PutLoanProductsProductIdRequest;
|
||||||
import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
|
import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
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.SchedulerJobHelper;
|
||||||
import org.apache.fineract.integrationtests.common.Utils;
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
import org.apache.fineract.integrationtests.common.accounting.Account;
|
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.JournalEntry;
|
||||||
import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
|
|
||||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
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.loans.LoanTransactionHelper;
|
||||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
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.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.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
|
|
||||||
@ExtendWith(LoanTestLifecycleExtension.class)
|
public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegrationTest {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loanRepaymentScheduleWithSimpleDisbursementAndDownPayment() {
|
public void loanRepaymentScheduleWithSimpleDisbursementAndDownPayment() {
|
||||||
@@ -1235,140 +1206,176 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void downPaymentOnOverpaidLoan() {
|
public void downPaymentOnOverpaidProgressiveLoan() {
|
||||||
try {
|
runAt("03 March 2023", () -> {
|
||||||
|
|
||||||
// Set business date
|
|
||||||
LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
|
LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
|
||||||
|
|
||||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate);
|
|
||||||
|
|
||||||
// Accounts oof periodic accrual
|
final PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
final Account assetAccount = accountHelper.createAssetAccount();
|
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
|
||||||
final Account incomeAccount = accountHelper.createIncomeAccount();
|
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
|
||||||
final Account expenseAccount = accountHelper.createExpenseAccount();
|
|
||||||
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
|
||||||
|
|
||||||
// Loan ExternalId
|
PostLoanProductsResponse loanProductsResponse = loanTransactionHelper.createLoanProduct(loanProductsRequest);
|
||||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
// down-payment configuration
|
String disbursementDateStr = DateUtils.format(disbursementDate, DATETIME_PATTERN);
|
||||||
Boolean enableDownPayment = true;
|
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(client.getResourceId())
|
||||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
.productId(loanProductsResponse.getResourceId()).loanType("individual").locale("en").dateFormat(DATETIME_PATTERN)
|
||||||
Boolean enableAutoRepaymentForDownPayment = true;
|
.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)));
|
||||||
|
|
||||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
loanTransactionHelper.approveLoan(loanResponse.getResourceId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate(disbursementDateStr).locale("en"));
|
||||||
|
|
||||||
// Loan Product creation with down-payment configuration
|
loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
|
||||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting(
|
new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDateStr).dateFormat(DATETIME_PATTERN)
|
||||||
loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, incomeAccount,
|
.transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
|
||||||
expenseAccount, overpaymentAccount);
|
|
||||||
|
|
||||||
assertNotNull(getLoanProductsProductResponse);
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
|
||||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
|
||||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
|
||||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
|
||||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
|
||||||
|
|
||||||
final Integer loanId = createLoanAccountWithAdvancedPaymentAllocation(clientId, getLoanProductsProductResponse.getId(),
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
loanExternalIdStr);
|
|
||||||
|
|
||||||
// Retrieve Loan with loanId
|
// 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") //
|
||||||
|
);
|
||||||
|
|
||||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat("dd MMMM yyyy").transactionDate("03 March 2023").locale("en").transactionAmount(800.0));
|
||||||
|
|
||||||
// verify down-payment details for Loan
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
|
||||||
assertNotNull(loanDetails);
|
// Verify Repayment Schedule
|
||||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
verifyRepaymentSchedule(loanResponse.getResourceId(), //
|
||||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
installment(1000.0, null, "03 March 2023"), //
|
||||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
|
||||||
|
installment(750.0, 0.0, 0.0, true, "02 April 2023") //
|
||||||
// first disbursement
|
);
|
||||||
loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000");
|
// verify transactions
|
||||||
|
verifyTransactions(loanResponse.getResourceId(), //
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
transaction(1000.0, "Disbursement", "03 March 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
// verify down-payment transaction created
|
transaction(250.0, "Down Payment", "03 March 2023", 750.0, 250.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f, 0.0f, loanId);
|
transaction(800.0, "Repayment", "03 March 2023", 0.0, 750.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
|
||||||
|
);
|
||||||
// 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));
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
loanTransactionHelper.makeLoanRepayment((long) loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
|
||||||
.transactionDate("03 March 2023").locale("en").transactionAmount(800.0));
|
|
||||||
|
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
|
||||||
// verify down-payment details for Loan
|
|
||||||
assertTrue(loanDetails.getStatus().getOverpaid());
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
assertEquals(50.0, loanDetails.getTotalOverpaid());
|
assertEquals(50.0, loanDetails.getTotalOverpaid());
|
||||||
|
|
||||||
// second disbursement
|
// second disbursement
|
||||||
|
|
||||||
disbursementDate = LocalDate.of(2023, 3, 5);
|
disbursementDate = LocalDate.of(2023, 3, 5);
|
||||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate);
|
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate);
|
||||||
loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "20");
|
|
||||||
|
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("05 March 2023").dateFormat(DATETIME_PATTERN)
|
||||||
assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement());
|
.transactionAmount(BigDecimal.valueOf(20.00)).locale("en"));
|
||||||
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());
|
|
||||||
|
|
||||||
|
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());
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
assertEquals(30.0, loanDetails.getTotalOverpaid());
|
assertEquals(30.0, loanDetails.getTotalOverpaid());
|
||||||
|
|
||||||
loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "30");
|
loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
new PostLoansLoanIdRequest().actualDisbursementDate("05 March 2023").dateFormat(DATETIME_PATTERN)
|
||||||
assertTrue(loanDetails.getTransactions().get(4).getType().getDisbursement());
|
.transactionAmount(BigDecimal.valueOf(30.00)).locale("en"));
|
||||||
assertEquals(30.0, loanDetails.getTransactions().get(4).getAmount());
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
|
||||||
assertEquals(0.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
|
|
||||||
assertEquals(5, loanDetails.getTransactions().size());
|
// 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) //
|
||||||
|
);
|
||||||
|
|
||||||
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
|
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
|
||||||
assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
|
assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
assertEquals(null, loanDetails.getTotalOverpaid());
|
assertEquals(null, loanDetails.getTotalOverpaid());
|
||||||
|
|
||||||
PostLoansLoanIdTransactionsResponse repayment = loanTransactionHelper.makeLoanRepayment((long) loanId,
|
PostLoansLoanIdTransactionsResponse repayment = loanTransactionHelper.makeLoanRepayment(loanResponse.getResourceId(),
|
||||||
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("05 March 2023").locale("en")
|
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("05 March 2023").locale("en")
|
||||||
.transactionAmount(1.0));
|
.transactionAmount(1.0));
|
||||||
|
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
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());
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
assertEquals(1.0, loanDetails.getTotalOverpaid());
|
assertEquals(1.0, loanDetails.getTotalOverpaid());
|
||||||
|
|
||||||
loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "40");
|
loanTransactionHelper.disburseLoan(loanResponse.getResourceId(),
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
new PostLoansLoanIdRequest().actualDisbursementDate("05 March 2023").dateFormat(DATETIME_PATTERN)
|
||||||
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
|
.transactionAmount(BigDecimal.valueOf(40.00)).locale("en"));
|
||||||
assertEquals(1.0, loanDetails.getTransactions().get(5).getAmount());
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
|
||||||
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
|
// Verify Repayment Schedule
|
||||||
assertEquals(40.0, loanDetails.getTransactions().get(6).getAmount());
|
verifyRepaymentSchedule(loanResponse.getResourceId(), //
|
||||||
assertEquals(39.0, loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
|
installment(1000.0, null, "03 March 2023"), //
|
||||||
assertEquals("loanTransactionType.downPayment", loanDetails.getTransactions().get(7).getType().getCode());
|
installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
|
||||||
assertEquals(9.0, loanDetails.getTransactions().get(7).getAmount());
|
installment(20.0, null, "05 March 2023"), //
|
||||||
assertEquals(30.0, loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
|
installment(30.0, null, "05 March 2023"), //
|
||||||
assertEquals(8, loanDetails.getTransactions().size());
|
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) //
|
||||||
|
);
|
||||||
|
|
||||||
assertTrue(loanDetails.getStatus().getActive());
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
|
assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
@@ -1377,23 +1384,229 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
|
|||||||
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05 March 2023")
|
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05 March 2023")
|
||||||
.transactionAmount(0.0).locale("en"));
|
.transactionAmount(0.0).locale("en"));
|
||||||
|
|
||||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getResourceId());
|
||||||
assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment());
|
// Verify Repayment Schedule
|
||||||
assertEquals(1.0, loanDetails.getTransactions().get(5).getAmount());
|
verifyRepaymentSchedule(loanResponse.getResourceId(), //
|
||||||
assertTrue(loanDetails.getTransactions().get(5).getManuallyReversed());
|
installment(1000.0, null, "03 March 2023"), //
|
||||||
assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement());
|
installment(250.0, 0.0, 0.0, true, "03 March 2023"), //
|
||||||
assertEquals(40.0, loanDetails.getTransactions().get(6).getAmount());
|
installment(20.0, null, "05 March 2023"), //
|
||||||
assertEquals(40.0, loanDetails.getTransactions().get(6).getOutstandingLoanBalance());
|
installment(30.0, null, "05 March 2023"), //
|
||||||
assertEquals("loanTransactionType.downPayment", loanDetails.getTransactions().get(7).getType().getCode());
|
installment(40.0, null, "05 March 2023"), //
|
||||||
assertEquals(9.0, loanDetails.getTransactions().get(7).getAmount());
|
installment(5.0, 0.0, 0.0, true, "05 March 2023"), //
|
||||||
assertEquals(31.0, loanDetails.getTransactions().get(7).getOutstandingLoanBalance());
|
installment(7.5, 0.0, 0.0, true, "05 March 2023"), //
|
||||||
assertEquals(8, loanDetails.getTransactions().size());
|
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) //
|
||||||
|
);
|
||||||
|
|
||||||
assertTrue(loanDetails.getStatus().getActive());
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
|
assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
} finally {
|
});
|
||||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
public void downPaymentOnOverpaidCumulativeLoan() {
|
||||||
|
runAt("03 March 2023", () -> {
|
||||||
|
LocalDate disbursementDate = LocalDate.of(2023, 3, 3);
|
||||||
|
|
||||||
|
PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
|
||||||
|
final PostLoanProductsRequest loanProductsRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
|
.installmentAmountInMultiplesOf(null).enableDownPayment(true).enableAutoRepaymentForDownPayment(true)
|
||||||
|
.loanScheduleType(LoanScheduleType.CUMULATIVE.name()).paymentAllocation(null)
|
||||||
|
.transactionProcessingStrategyCode(
|
||||||
|
DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
|
||||||
|
.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(
|
||||||
|
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)));
|
||||||
|
|
||||||
|
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.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.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());
|
||||||
|
|
||||||
|
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(loanResponse.getLoanId(), externalId,
|
||||||
|
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05 March 2023")
|
||||||
|
.transactionAmount(0.0).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, 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(830.0, loanDetails.getSummary().getTotalOutstanding());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkNoDownPaymentTransaction(final Integer loanID) {
|
private void checkNoDownPaymentTransaction(final Integer loanID) {
|
||||||
@@ -1470,63 +1683,6 @@ public class LoanRepaymentScheduleWithDownPaymentTest {
|
|||||||
return loanTransactionHelper.getLoanProduct(loanProductId);
|
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) {
|
private Integer createApproveAndDisburseLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) {
|
||||||
|
|
||||||
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
||||||
|
|||||||
Reference in New Issue
Block a user