FINERACT-1883 - New loan repayment parameter

- [x] New loan repayment parameter
- [x] Integration test
This commit is contained in:
Janos Haber
2023-02-23 15:11:16 +01:00
committed by Arnold Gálovics
parent 670e0d55a3
commit 764f3de203
10 changed files with 72 additions and 30 deletions
@@ -66,6 +66,22 @@
"null",
"bigdecimal"
]
},
{
"default": null,
"name": "lastRepaymentDate",
"type": [
"null",
"string"
]
},
{
"default": null,
"name": "lastRepaymentAmount",
"type": [
"null",
"bigdecimal"
]
}
]
}
@@ -112,10 +112,16 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()));
collectionData.setNextPaymentDueDate(loan.possibleNextRepaymentDate());
final LoanTransaction lastRepaymenTransaction = loan.getLastRepaymentTransaction();
if (lastRepaymenTransaction != null) {
collectionData.setLastPaymentDate(lastRepaymenTransaction.getTransactionDate());
collectionData.setLastPaymentAmount(lastRepaymenTransaction.getAmount());
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
if (lastPayment != null) {
collectionData.setLastPaymentDate(lastPayment.getTransactionDate());
collectionData.setLastPaymentAmount(lastPayment.getAmount());
}
final LoanTransaction lastRepaymentTransaction = loan.getLastRepaymentTransaction();
if (lastRepaymentTransaction != null) {
collectionData.setLastRepaymentDate(lastRepaymentTransaction.getTransactionDate());
collectionData.setLastRepaymentAmount(lastRepaymentTransaction.getAmount());
}
}
@@ -979,6 +979,11 @@ final class LoansApiResourceSwagger {
public LocalDate lastPaymentDate;
@Schema(example = "100.000000")
public Double lastPaymentAmount;
@Schema(example = "[2022, 07, 01]")
public LocalDate lastRepaymentDate;
@Schema(example = "100.000000")
public Double lastRepaymentAmount;
}
@Schema(example = "1")
@@ -40,9 +40,12 @@ public final class CollectionData {
private LocalDate lastPaymentDate;
private BigDecimal lastPaymentAmount;
private LocalDate lastRepaymentDate;
private BigDecimal lastRepaymentAmount;
public static CollectionData template() {
final BigDecimal zero = BigDecimal.ZERO;
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero);
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero);
}
}
@@ -3217,7 +3217,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(loanTransaction);
}
if (loanTransaction.isNotRepaymentType() && loanTransaction.isNotWaiver() && loanTransaction.isNotRecoveryRepayment()) {
if (loanTransaction.isNotRepaymentLikeType() && loanTransaction.isNotWaiver() && loanTransaction.isNotRecoveryRepayment()) {
final String errorMessage = "A transaction of type repayment or recovery repayment or waiver was expected but not received.";
throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.repayment.or.waiver.or.recovery.transaction",
errorMessage);
@@ -3604,7 +3604,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
for (final LoanTransaction previousTransaction : loanTransactions) {
if (previousTransaction.isRepaymentType() && previousTransaction.isNotReversed()
if (previousTransaction.isRepaymentLikeType() && previousTransaction.isNotReversed()
&& currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
isAfterLatRepayment = false;
break;
@@ -3642,7 +3642,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
LocalDate lastTransactionDate = null;
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isRepaymentType() && transaction.isNonZero()) {
if (transaction.isRepaymentLikeType() && transaction.isNonZero()) {
lastTransactionDate = transaction.getTransactionDate();
}
}
@@ -3714,7 +3714,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
transactionForAdjustment.getTransactionDate());
if (transactionForAdjustment.isNotRepaymentType() && transactionForAdjustment.isNotWaiver()
if (transactionForAdjustment.isNotRepaymentLikeType() && transactionForAdjustment.isNotWaiver()
&& transactionForAdjustment.isNotCreditBalanceRefund()) {
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver or credit balance refund can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
@@ -3734,7 +3734,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, this);
}
if (newTransactionDetail.isRepaymentType() || newTransactionDetail.isInterestWaiver()) {
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment, scheduleGeneratorDTO);
}
@@ -4227,7 +4227,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
Money cumulativePaid = Money.zero(loanCurrency());
for (final LoanTransaction repayment : this.loanTransactions) {
if (repayment.isRepaymentType() && !repayment.isReversed()) {
if (repayment.isRepaymentLikeType() && !repayment.isReversed()) {
cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
}
}
@@ -4550,7 +4550,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
&& !transaction.getTransactionDate().isAfter(tillDate)) {
if (transaction.isAccrual()) {
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
} else if (transaction.isRepaymentType() || transaction.isInterestWaiver()) {
} else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver()) {
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
}
}
@@ -4942,7 +4942,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final LoanTransaction repaymentTransaction,
final String reversedOrCreated) {
if (repaymentTransaction.isRepaymentType() && !repaymentTransaction.isChargeRefund()) {
if (repaymentTransaction.isRepaymentLikeType() && !repaymentTransaction.isChargeRefund()) {
for (LoanTransaction txn : this.getLoanTransactions()) {
if (txn.isChargeRefund() && repaymentTransaction.getTransactionDate().isBefore(txn.getTransactionDate())) {
final String errorMessage = "loan.transaction.cant.be." + reversedOrCreated + ".because.later.charge.refund.exists";
@@ -4968,17 +4968,25 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public LocalDate getLastRepaymentDate() {
LocalDate currentTransactionDate = getDisbursementDate();
for (final LoanTransaction previousTransaction : this.loanTransactions) {
if (previousTransaction.isRepaymentType() && currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
if (previousTransaction.isRepaymentLikeType() && currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
currentTransactionDate = previousTransaction.getTransactionDate();
}
}
return currentTransactionDate;
}
public LoanTransaction getLastPaymentTransaction() {
return loanTransactions.stream() //
.filter(loanTransaction -> !loanTransaction.isReversed()) //
.filter(LoanTransaction::isRepaymentLikeType) //
.reduce((first, second) -> second) //
.orElse(null);
}
public LoanTransaction getLastRepaymentTransaction() {
return loanTransactions.stream() //
.filter(loanTransaction -> !loanTransaction.isReversed()) //
.filter(LoanTransaction::isRepaymentType) //
.filter(LoanTransaction::isRepayment) //
.reduce((first, second) -> second) //
.orElse(null);
}
@@ -6300,7 +6308,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
LocalDate lastTransactionDate = null;
for (final LoanTransaction transaction : this.loanTransactions) {
if ((transaction.isRepaymentType() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
if ((transaction.isRepaymentLikeType() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
&& transaction.isNonZero() && transaction.isNotReversed()) {
lastTransactionDate = transaction.getTransactionDate();
}
@@ -6349,8 +6357,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
loanTransactions = retrieveListOfTransactionsExcludeAccruals();
Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) {
if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())
&& (previousTransaction.isRepaymentType() || previousTransaction.isWaiver() || previousTransaction.isChargePayment())) {
if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate()) && (previousTransaction.isRepaymentLikeType()
|| previousTransaction.isWaiver() || previousTransaction.isChargePayment())) {
throw new UndoLastTrancheDisbursementException(previousTransaction.getId());
}
if (previousTransaction.getId().compareTo(lastDisbursalTransaction.getId()) < 0) {
@@ -6673,7 +6681,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(currency));
receivableFee = receivableFee.plus(transaction.getFeeChargesPortion(currency));
receivablePenalty = receivablePenalty.plus(transaction.getPenaltyChargesPortion(currency));
} else if (transaction.isRepaymentType() || transaction.isChargePayment()) {
} else if (transaction.isRepaymentLikeType() || transaction.isChargePayment()) {
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(currency));
receivableFee = receivableFee.minus(transaction.getFeeChargesPortion(currency));
receivablePenalty = receivablePenalty.minus(transaction.getPenaltyChargesPortion(currency));
@@ -562,7 +562,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
this.manuallyAdjustedOrReversed = true;
}
public boolean isRepaymentType() {
public boolean isRepaymentLikeType() {
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment();
}
@@ -591,8 +591,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return LoanTransactionType.CHARGE_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotRepaymentType() {
return !isRepaymentType();
public boolean isNotRepaymentLikeType() {
return !isRepaymentLikeType();
}
public boolean isIncomePosting() {
@@ -165,7 +165,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Collections.sort(installments, byDate);
}
if (loanTransaction.isRepaymentType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
// pass through for new transactions
if (loanTransaction.getId() == null) {
handleTransaction(loanTransaction, currency, installments, charges);
@@ -306,7 +306,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
totalPaidInRepayments = totalPaidInRepayments.minus(transaction.getAmount(currency));
} else if (transaction.isCreditBalanceRefund() || transaction.isChargeback()) {
totalPaidInRepayments = totalPaidInRepayments.minus(transaction.getOverPaymentPortion(currency));
} else if (transaction.isRepaymentType()) {
} else if (transaction.isRepaymentLikeType()) {
totalPaidInRepayments = totalPaidInRepayments.plus(transaction.getAmount(currency));
}
}
@@ -428,7 +428,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
final boolean isFeeCharge) {
// to.
if (loanTransaction.isRepaymentType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess);
@@ -686,7 +686,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money unProcessed = Money.zero(currency);
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
Money amountToProcess = null;
if (loanTransaction.isRepaymentType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
if (loanTransaction.isInterestWaiver()) {
@@ -1197,7 +1197,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust);
if (newTransactionDetail.isRepaymentType() && thereIsNewTransaction) {
if (newTransactionDetail.isRepaymentLikeType() && thereIsNewTransaction) {
eventData.setNewTransactionDetail(newTransactionDetail);
}
Long entityId = transactionToAdjust.getId();
@@ -115,7 +115,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2);
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
loanForProcessing);
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null);
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null,
null, null);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
@@ -224,7 +224,7 @@ public class LoanTransactionSummaryTest {
.transactionAmount(20.0));
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("8 September 2022").locale("en")
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("7 September 2022").locale("en")
.transactionAmount(100.0));
final PostLoansLoanIdTransactionsResponse repaymentTransaction_2 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
@@ -234,8 +234,11 @@ public class LoanTransactionSummaryTest {
// Retrieve Loan with loanId
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
assertEquals(100.0, loanDetails.getDelinquent().getLastPaymentAmount());
assertEquals(20.0, loanDetails.getDelinquent().getLastPaymentAmount());
assertEquals(LocalDate.of(2022, 9, 8), loanDetails.getDelinquent().getLastPaymentDate());
assertEquals(100.0, loanDetails.getDelinquent().getLastRepaymentAmount());
assertEquals(LocalDate.of(2022, 9, 7), loanDetails.getDelinquent().getLastRepaymentDate());
}
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,