FINERACT-1839: Reversal on the loan account after CBR / Chargeback
This commit is contained in:
committed by
Arnold Gálovics
parent
d7acfc1e5d
commit
0a2db35f8d
+41
-27
@@ -20,6 +20,8 @@ package org.apache.fineract.portfolio.delinquency.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
@@ -27,7 +29,6 @@ import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -43,17 +44,19 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
|
||||
final MonetaryCurrency loanCurrency = loan.getCurrency();
|
||||
LocalDate overdueSinceDate = null;
|
||||
CollectionData collectionData = CollectionData.template();
|
||||
BigDecimal amountAvailable = BigDecimal.ZERO;
|
||||
BigDecimal amountAvailable;
|
||||
BigDecimal outstandingAmount = BigDecimal.ZERO;
|
||||
boolean oldestOverdueInstallment = false;
|
||||
boolean overdueSinceDateWasSet = false;
|
||||
boolean firstNotYetDueInstallment = false;
|
||||
LoanRepaymentScheduleInstallment latestInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||
|
||||
List<LoanTransaction> chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback);
|
||||
|
||||
log.debug("Loan id {} with {} installments", loan.getId(), loan.getRepaymentScheduleInstallments().size());
|
||||
// Get the oldest overdue installment if exists one
|
||||
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||
if (!installment.isObligationsMet()) {
|
||||
|
||||
if (installment.getDueDate().isBefore(businessDate)) {
|
||||
log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(),
|
||||
installment.getDueDate());
|
||||
@@ -66,10 +69,17 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
|
||||
|
||||
amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
|
||||
|
||||
for (LoanTransactionToRepaymentScheduleMapping mappingInstallment : installment
|
||||
.getLoanTransactionToRepaymentScheduleMappings()) {
|
||||
final LoanTransaction loanTransaction = mappingInstallment.getLoanTransaction();
|
||||
if (loanTransaction.isChargeback()) {
|
||||
boolean isLatestInstallment = Objects.equals(installment.getId(), latestInstallment.getId());
|
||||
for (LoanTransaction loanTransaction : chargebackTransactions) {
|
||||
boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = loanTransaction.getTransactionDate().isEqual(
|
||||
installment.getFromDate()) || loanTransaction.getTransactionDate().isAfter(installment.getFromDate());
|
||||
boolean isLoanTransactionIsBeforeNotLastInstallmentDueDate = !isLatestInstallment
|
||||
&& loanTransaction.getTransactionDate().isBefore(installment.getDueDate());
|
||||
boolean isLoanTransactionIsOnOrBeforeLastInstallmentDueDate = isLatestInstallment
|
||||
&& (loanTransaction.getTransactionDate().isEqual(installment.getDueDate())
|
||||
|| loanTransaction.getTransactionDate().isBefore(installment.getDueDate()));
|
||||
if (isLoanTransactionIsOnOrAfterInstallmentFromDate && (isLoanTransactionIsBeforeNotLastInstallmentDueDate
|
||||
|| isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) {
|
||||
amountAvailable = amountAvailable.subtract(loanTransaction.getAmount());
|
||||
if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
|
||||
overdueSinceDate = loanTransaction.getTransactionDate();
|
||||
@@ -78,29 +88,33 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!firstNotYetDueInstallment) {
|
||||
log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(),
|
||||
installment.getDueDate());
|
||||
firstNotYetDueInstallment = true;
|
||||
amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
|
||||
log.debug("Amount available {}", amountAvailable);
|
||||
for (LoanTransactionToRepaymentScheduleMapping mappingInstallment : installment
|
||||
.getLoanTransactionToRepaymentScheduleMappings()) {
|
||||
final LoanTransaction loanTransaction = mappingInstallment.getLoanTransaction();
|
||||
if (loanTransaction.isChargeback() && loanTransaction.getTransactionDate().isBefore(businessDate)) {
|
||||
log.debug("Loan CB Transaction: {} {} {}", loanTransaction.getId(), loanTransaction.getTransactionDate(),
|
||||
loanTransaction.getAmount());
|
||||
amountAvailable = amountAvailable.subtract(loanTransaction.getAmount());
|
||||
if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 && !overdueSinceDateWasSet) {
|
||||
overdueSinceDate = loanTransaction.getTransactionDate();
|
||||
overdueSinceDateWasSet = true;
|
||||
} else if (!firstNotYetDueInstallment) {
|
||||
log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(),
|
||||
installment.getDueDate());
|
||||
firstNotYetDueInstallment = true;
|
||||
amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
|
||||
log.debug("Amount available {}", amountAvailable);
|
||||
for (LoanTransaction loanTransaction : chargebackTransactions) {
|
||||
boolean isLoanTransactionIsOnOrAfterInstallmentFromDate = loanTransaction.getTransactionDate().isEqual(
|
||||
installment.getFromDate()) || loanTransaction.getTransactionDate().isAfter(installment.getFromDate());
|
||||
boolean isLoanTransactionIsBeforeInstallmentDueDate = loanTransaction.getTransactionDate()
|
||||
.isBefore(installment.getDueDate());
|
||||
boolean isLoanTransactionIsBeforeBusinessDate = loanTransaction.getTransactionDate().isBefore(businessDate);
|
||||
if (isLoanTransactionIsOnOrAfterInstallmentFromDate && isLoanTransactionIsBeforeInstallmentDueDate
|
||||
&& isLoanTransactionIsBeforeBusinessDate) {
|
||||
log.debug("Loan CB Transaction: {} {} {}", loanTransaction.getId(), loanTransaction.getTransactionDate(),
|
||||
loanTransaction.getAmount());
|
||||
amountAvailable = amountAvailable.subtract(loanTransaction.getAmount());
|
||||
if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 && !overdueSinceDateWasSet) {
|
||||
overdueSinceDate = loanTransaction.getTransactionDate();
|
||||
overdueSinceDateWasSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
|
||||
outstandingAmount = outstandingAmount.add(amountAvailable.abs());
|
||||
if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
|
||||
outstandingAmount = outstandingAmount.add(amountAvailable.abs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-8
@@ -44,6 +44,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
@@ -1346,8 +1347,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
installment.updateAccrualPortion(interest, fee, penality);
|
||||
}
|
||||
LoanRepaymentScheduleInstallment lastInstallment = getRepaymentScheduleInstallments()
|
||||
.get(getRepaymentScheduleInstallments().size() - 1);
|
||||
LoanRepaymentScheduleInstallment lastInstallment = getLastLoanRepaymentScheduleInstallment();
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
if (loanTransaction.getTransactionDate().isAfter(lastInstallment.getDueDate()) && !loanTransaction.isReversed()) {
|
||||
loanTransaction.reverse();
|
||||
@@ -3798,9 +3798,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (loanTransaction.isReversed()) {
|
||||
continue;
|
||||
}
|
||||
if ((loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan() || loanTransaction.isCreditBalanceRefund()
|
||||
|| loanTransaction.isChargeback())) {
|
||||
if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
||||
} else if (loanTransaction.isCreditBalanceRefund() || loanTransaction.isChargeback()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5804,7 +5805,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()));
|
||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
||||
} else if (loanTransaction.isChargeback()) {
|
||||
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
|
||||
Money transactionOutstanding = loanTransaction.getAmount(getCurrency());
|
||||
if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
|
||||
transactionOutstanding = loanTransaction.getAmount(getCurrency())
|
||||
@@ -6277,9 +6278,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
final Money overpaidAmount = calculateTotalOverpayment(); // Before Transaction
|
||||
if (overpaidAmount.isGreaterThanZero()) {
|
||||
chargebackTransaction.setOverPayments(overpaidAmount);
|
||||
}
|
||||
|
||||
if (chargebackTransaction.isNotZero(loanCurrency())) {
|
||||
addLoanTransaction(chargebackTransaction);
|
||||
@@ -7018,4 +7016,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return this.chargedOff;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment getLastLoanRepaymentScheduleInstallment() {
|
||||
return getRepaymentScheduleInstallments().get(getRepaymentScheduleInstallments().size() - 1);
|
||||
}
|
||||
|
||||
public List<LoanTransaction> getLoanTransactions(Predicate<LoanTransaction> predicate) {
|
||||
return getLoanTransactions().stream().filter(predicate).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-1
@@ -393,6 +393,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
|
||||
this.obligationsMet = false;
|
||||
this.obligationsMetOnDate = null;
|
||||
if (this.credits != null) {
|
||||
this.principal = this.principal.subtract(this.credits);
|
||||
this.credits = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetAccrualComponents() {
|
||||
@@ -836,7 +840,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDueChargeback(final LocalDate transactionDate, final Money transactionAmount) {
|
||||
public void updateDueAndCredits(final LocalDate transactionDate, final Money transactionAmount) {
|
||||
updateDueDate(transactionDate);
|
||||
addToCredits(transactionAmount.getAmount());
|
||||
addToPrincipal(transactionDate, transactionAmount);
|
||||
|
||||
+2
-3
@@ -291,9 +291,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
|
||||
final ExternalId externalId) {
|
||||
final PaymentDetail paymentDetail = null;
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate,
|
||||
externalId);
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, amount.getAmount(),
|
||||
null, null, null, null, amount.getAmount(), false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
|
||||
|
||||
+195
-72
@@ -25,7 +25,9 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.interoperation.util.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||
@@ -34,7 +36,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
@@ -93,7 +94,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
wrapper.reprocess(currency, disbursementDate, installments, charges);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>();
|
||||
final List<LoanTransaction> transactionsToBeProcessed = new ArrayList<>();
|
||||
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
|
||||
if (loanTransaction.isChargePayment()) {
|
||||
List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
|
||||
@@ -147,11 +148,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
|
||||
} else {
|
||||
transactionstoBeProcessed.add(loanTransaction);
|
||||
transactionsToBeProcessed.add(loanTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
for (final LoanTransaction loanTransaction : transactionstoBeProcessed) {
|
||||
for (final LoanTransaction loanTransaction : transactionsToBeProcessed) {
|
||||
// TODO: analyze and remove this
|
||||
if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
||||
@@ -188,13 +189,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
||||
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
||||
} else {
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
|
||||
createNewTransactionIfNecessary(loanTransaction, newLoanTransaction, currency, changedTransactionDetail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,13 +198,200 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
handleWriteOff(loanTransaction, currency, installments);
|
||||
} else if (loanTransaction.isRefundForActiveLoan()) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
|
||||
handleRefund(loanTransaction, currency, installments, charges);
|
||||
} else if (loanTransaction.isCreditBalanceRefund()) {
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed);
|
||||
} else if (loanTransaction.isChargeback()) {
|
||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed);
|
||||
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
||||
}
|
||||
}
|
||||
reprocessInstallments(installments, currency);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
|
||||
List<LoanTransaction> transactionsToBeProcessed) {
|
||||
|
||||
List<LoanTransaction> mergedTransactionList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
||||
for (Map.Entry<Long, LoanTransaction> entry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
if (entry.getValue().isChargeback()) {
|
||||
for (LoanTransaction loanTransaction : mergedTransactionList) {
|
||||
if (loanTransaction.isReversed()) {
|
||||
continue;
|
||||
}
|
||||
LoanTransactionRelation newLoanTransactionRelation = null;
|
||||
LoanTransactionRelation oldLoanTransactionRelation = null;
|
||||
for (LoanTransactionRelation transactionRelation : loanTransaction.getLoanTransactionRelations()) {
|
||||
if (entry.getKey().equals(transactionRelation.getToTransaction().getId())
|
||||
&& LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType())) {
|
||||
newLoanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, entry.getValue(),
|
||||
LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||
oldLoanTransactionRelation = transactionRelation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newLoanTransactionRelation != null) {
|
||||
loanTransaction.getLoanTransactionRelations().add(newLoanTransactionRelation);
|
||||
loanTransaction.getLoanTransactionRelations().remove(oldLoanTransactionRelation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reprocessInstallments(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
|
||||
LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
|
||||
if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) {
|
||||
installments.remove(lastInstallment);
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateCreditTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||
List<LoanTransaction> transactionsToBeProcessed) {
|
||||
// pass through for new transactions
|
||||
if (loanTransaction.getId() == null) {
|
||||
return;
|
||||
}
|
||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||
|
||||
List<LoanTransaction> mergedList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
||||
Money overpaidAmount = calculateOverpaidAmount(loanTransaction, mergedList, installments, currency);
|
||||
processCreditTransaction(newLoanTransaction, overpaidAmount, currency, installments);
|
||||
createNewTransactionIfNecessary(loanTransaction, newLoanTransaction, currency, changedTransactionDetail);
|
||||
}
|
||||
|
||||
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
|
||||
ChangedTransactionDetail changedTransactionDetail) {
|
||||
List<LoanTransaction> mergedList = new ArrayList<>(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
mergedList.addAll(new ArrayList<>(transactionList));
|
||||
return mergedList;
|
||||
}
|
||||
|
||||
private void createNewTransactionIfNecessary(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||
MonetaryCurrency currency, ChangedTransactionDetail changedTransactionDetail) {
|
||||
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private Money calculateOverpaidAmount(LoanTransaction loanTransaction, List<LoanTransaction> transactions,
|
||||
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
|
||||
Money totalPaidInRepayments = Money.zero(currency);
|
||||
|
||||
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||
cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments
|
||||
.plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
|
||||
.plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
|
||||
}
|
||||
|
||||
for (final LoanTransaction transaction : transactions) {
|
||||
if (transaction.isReversed()) {
|
||||
continue;
|
||||
}
|
||||
if (transaction.equals(loanTransaction)) {
|
||||
// We want to process only the transactions prior to the actual one
|
||||
break;
|
||||
}
|
||||
if (transaction.isRefund() || transaction.isRefundForActiveLoan()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(transaction.getAmount(currency));
|
||||
} else if (transaction.isCreditBalanceRefund() || transaction.isChargeback()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(transaction.getOverPaymentPortion(currency));
|
||||
} else if (transaction.isRepaymentType()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.plus(transaction.getAmount(currency));
|
||||
}
|
||||
}
|
||||
|
||||
// if total paid in transactions higher than repayment schedule then
|
||||
// theres an overpayment.
|
||||
return MathUtil.negativeToZero(totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments));
|
||||
}
|
||||
|
||||
private void processCreditTransaction(LoanTransaction loanTransaction, Money overpaidAmount, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
installments.sort(byDate);
|
||||
final Money zeroMoney = Money.zero(currency);
|
||||
Money transactionAmount = loanTransaction.getAmount(currency);
|
||||
Money principalPortion = MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaidAmount));
|
||||
Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
|
||||
loanTransaction.updateOverPayments(repaidAmount);
|
||||
loanTransaction.updateComponents(principalPortion, zeroMoney, zeroMoney, zeroMoney);
|
||||
|
||||
if (principalPortion.isGreaterThanZero()) {
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
boolean loanTransactionMapped = false;
|
||||
LocalDate pastDueDate = null;
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && currentInstallment.getDueDate().isAfter(transactionDate)) {
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
|
||||
// If already exists an additional installment just update the due date and
|
||||
// principal from the Loan charge back transaction
|
||||
} else if (currentInstallment.isAdditional()) {
|
||||
currentInstallment.updateDueAndCredits(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
} else {
|
||||
Loan loan = loanTransaction.getLoan();
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
|
||||
pastDueDate, transactionDate, transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), false, null);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCredits(transactionAmount.getAmount());
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides support for processing the latest transaction (which should be latest transaction) against the loan
|
||||
* schedule.
|
||||
@@ -524,66 +706,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
@Override
|
||||
public void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() {
|
||||
|
||||
@Override
|
||||
public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
|
||||
return ord1.getDueDate().compareTo(ord2.getDueDate());
|
||||
}
|
||||
};
|
||||
Collections.sort(installments, byDate);
|
||||
final Money zeroMoney = Money.zero(currency);
|
||||
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
||||
if (overpaidAmount.isGreaterThanZero()) {
|
||||
transactionAmountUnprocessed = loanTransaction.getAmount(currency).minus(overpaidAmount);
|
||||
if (transactionAmountUnprocessed.isLessThanZero()) {
|
||||
transactionAmountUnprocessed = zeroMoney;
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
boolean loanTransactionMapped = false;
|
||||
LocalDate pastDueDate = null;
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && currentInstallment.getDueDate().isAfter(transactionDate)) {
|
||||
currentInstallment.addToCredits(transactionAmountUnprocessed.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmountUnprocessed);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment,
|
||||
transactionAmountUnprocessed, zeroMoney, zeroMoney, zeroMoney));
|
||||
|
||||
loanTransactionMapped = true;
|
||||
|
||||
break;
|
||||
|
||||
// If already exists an additional installment just update the due date and
|
||||
// principal from the Loan charge back transaction
|
||||
} else if (currentInstallment.isAdditional()) {
|
||||
currentInstallment.updateDueChargeback(transactionDate, transactionAmountUnprocessed);
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
Loan loan = loanTransaction.getLoan();
|
||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
|
||||
pastDueDate, transactionDate, transactionAmountUnprocessed.getAmount(), zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), zeroMoney.getAmount(), false, compoundingDetails);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCredits(transactionAmountUnprocessed.getAmount());
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
|
||||
transactionAmountUnprocessed, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
}
|
||||
processCreditTransaction(loanTransaction, overpaidAmount, currency, installments);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -1302,7 +1302,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private void validateLoanTransactionAmountChargeBack(LoanTransaction loanTransaction, LoanTransaction chargebackTransaction) {
|
||||
BigDecimal actualAmount = BigDecimal.ZERO;
|
||||
for (LoanTransactionRelation loanTransactionRelation : loanTransaction.getLoanTransactionRelations()) {
|
||||
if (loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK)) {
|
||||
if (loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK)
|
||||
&& loanTransactionRelation.getToTransaction().isNotReversed()) {
|
||||
actualAmount = actualAmount.add(loanTransactionRelation.getToTransaction().getPrincipalPortion());
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -26,10 +26,12 @@ import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
@@ -136,6 +138,8 @@ public class LoanDelinquencyDomainServiceTest {
|
||||
when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
when(loan.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
|
||||
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Collections.emptyList());
|
||||
when(loan.getLastLoanRepaymentScheduleInstallment()).thenReturn(repaymentScheduleInstallments.get(0));
|
||||
when(loan.getCurrency()).thenReturn(currency);
|
||||
|
||||
CollectionData collectionData = underTest.getOverdueCollectionData(loan);
|
||||
|
||||
+229
@@ -7137,6 +7137,235 @@ public class ClientLoanIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreditBalanceRefundAfterMaturityWithReverseReplayOfRepayments() {
|
||||
try {
|
||||
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, true);
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, this.responseSpec, true);
|
||||
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||
.date("2022.10.10").dateFormat("yyyy.MM.dd").locale("en"));
|
||||
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
|
||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount,
|
||||
expenseAccount, overpaymentAccount);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
|
||||
|
||||
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment("04 September 2022", Float.parseFloat("100"), loanID);
|
||||
this.loanTransactionHelper.makeRepayment("05 September 2022", Float.parseFloat("1100"), loanID);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertEquals(200.0, loanDetails.getTotalOverpaid());
|
||||
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||
|
||||
this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionAmount(200.0).transactionDate("10 October 2022").dateFormat("dd MMMM yyyy").locale("en"));
|
||||
|
||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
|
||||
|
||||
assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(1000, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
|
||||
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(1).getAmount());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTransactions().get(1).getDate());
|
||||
assertEquals(900.0, loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
|
||||
assertEquals(1100.0, loanDetails.getTransactions().get(2).getAmount());
|
||||
assertEquals(900.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 5), loanDetails.getTransactions().get(2).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getAmount());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 10, 10), loanDetails.getTransactions().get(3).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
|
||||
|
||||
this.loanTransactionHelper.reverseLoanTransaction(loanDetails.getId(), loanDetails.getTransactions().get(1).getId(),
|
||||
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy").transactionAmount(0.0)
|
||||
.transactionDate("10 October 2022").locale("en"));
|
||||
|
||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(1).getAmount());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTransactions().get(1).getDate());
|
||||
assertTrue(loanDetails.getTransactions().get(1).getManuallyReversed());
|
||||
|
||||
assertEquals(1100.0, loanDetails.getTransactions().get(2).getAmount());
|
||||
assertEquals(1000.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 5), loanDetails.getTransactions().get(2).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
|
||||
assertEquals(1, loanDetails.getTransactions().get(2).getTransactionRelations().size());
|
||||
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getAmount());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(3).getPrincipalPortion());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(3).getOverpaymentPortion());
|
||||
assertEquals(100.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
|
||||
assertEquals(LocalDate.of(2022, 10, 10), loanDetails.getTransactions().get(3).getDate());
|
||||
assertEquals(1, loanDetails.getTransactions().get(3).getTransactionRelations().size());
|
||||
|
||||
assertTrue(loanDetails.getStatus().getActive());
|
||||
|
||||
assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(1000, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
|
||||
assertTrue(loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete());
|
||||
assertEquals(200, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalDue());
|
||||
assertFalse(loanDetails.getRepaymentSchedule().getPeriods().get(2).getComplete());
|
||||
assertEquals(100.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalPaid());
|
||||
assertEquals(100.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalOutstanding());
|
||||
|
||||
} finally {
|
||||
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, false);
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, this.responseSpec, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreditBalanceRefundBeforeMaturityWithReverseReplayOfRepaymentsAndRefund() {
|
||||
try {
|
||||
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, true);
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, this.responseSpec, true);
|
||||
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||
.date("2022.10.10").dateFormat("yyyy.MM.dd").locale("en"));
|
||||
|
||||
final Account assetAccount = this.accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = this.accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = this.accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||
|
||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount,
|
||||
expenseAccount, overpaymentAccount);
|
||||
|
||||
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011");
|
||||
|
||||
final Integer loanID = applyForLoanApplication(clientID, loanProductID);
|
||||
|
||||
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||
|
||||
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000");
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
this.loanTransactionHelper.makeRepayment("04 September 2022", Float.parseFloat("500"), loanID);
|
||||
this.loanTransactionHelper.makeRepayment("05 September 2022", Float.parseFloat("700"), loanID);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertEquals(200.0, loanDetails.getTotalOverpaid());
|
||||
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||
|
||||
this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionAmount(200.0).transactionDate("06 September 2022").dateFormat("dd MMMM yyyy").locale("en"));
|
||||
|
||||
this.loanTransactionHelper.makeMerchantIssuedRefund((long) loanID, new PostLoansLoanIdTransactionsRequest().locale("en")
|
||||
.dateFormat("dd MMMM yyyy").transactionDate("07 September 2022").transactionAmount(500.0));
|
||||
|
||||
this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionAmount(500.0).transactionDate("08 September 2022").dateFormat("dd MMMM yyyy").locale("en"));
|
||||
|
||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
|
||||
|
||||
assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(1000, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTransactions().get(1).getDate());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
|
||||
|
||||
assertEquals(700.0, loanDetails.getTransactions().get(2).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 5), loanDetails.getTransactions().get(2).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
|
||||
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getAmount());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 6), loanDetails.getTransactions().get(3).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(4).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(4).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 7), loanDetails.getTransactions().get(4).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(5).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(5).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 8), loanDetails.getTransactions().get(5).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(5).getOutstandingLoanBalance());
|
||||
|
||||
this.loanTransactionHelper.reverseLoanTransaction(loanDetails.getId(), loanDetails.getTransactions().get(2).getId(),
|
||||
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy").transactionAmount(0.0)
|
||||
.transactionDate("07 September 2022").locale("en"));
|
||||
|
||||
loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID);
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 4), loanDetails.getTransactions().get(1).getDate());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
|
||||
|
||||
assertEquals(700.0, loanDetails.getTransactions().get(2).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(2).getPrincipalPortion());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(2).getOverpaymentPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 5), loanDetails.getTransactions().get(2).getDate());
|
||||
assertEquals(0.0, loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
|
||||
assertTrue(loanDetails.getTransactions().get(2).getManuallyReversed());
|
||||
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getAmount());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(3).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 6), loanDetails.getTransactions().get(3).getDate());
|
||||
assertEquals(700.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
|
||||
assertEquals(1, loanDetails.getTransactions().get(3).getTransactionRelations().size());
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(4).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(4).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 7), loanDetails.getTransactions().get(4).getDate());
|
||||
assertEquals(200.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
|
||||
assertEquals(1, loanDetails.getTransactions().get(4).getTransactionRelations().size());
|
||||
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(5).getAmount());
|
||||
assertEquals(500.0, loanDetails.getTransactions().get(5).getPrincipalPortion());
|
||||
assertEquals(LocalDate.of(2022, 9, 8), loanDetails.getTransactions().get(5).getDate());
|
||||
assertEquals(700.0, loanDetails.getTransactions().get(5).getOutstandingLoanBalance());
|
||||
assertEquals(1, loanDetails.getTransactions().get(5).getTransactionRelations().size());
|
||||
|
||||
assertTrue(loanDetails.getStatus().getActive());
|
||||
|
||||
assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(1700, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
|
||||
assertFalse(loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete());
|
||||
assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
|
||||
assertEquals(700.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
|
||||
|
||||
} finally {
|
||||
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, false);
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, this.responseSpec, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID) {
|
||||
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
||||
|
||||
Reference in New Issue
Block a user