FINERACT-1981: Fix principal due during disbursement on overpaid loan
This commit is contained in:
+1
-1
@@ -3838,7 +3838,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
|
if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
|
||||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
||||||
} else if (loanTransaction.isCreditBalanceRefund() || loanTransaction.isChargeback() || loanTransaction.isDisbursement()) {
|
} else if (loanTransaction.isCreditBalanceRefund() || loanTransaction.isChargeback()) {
|
||||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
|
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -522,7 +522,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
downPaymentAmount = Money.of(currency, downPaymentAmt);
|
downPaymentAmount = Money.of(currency, downPaymentAmt);
|
||||||
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
||||||
}
|
}
|
||||||
disbursementTransaction.setOverPayments(overpaymentHolder.getMoneyObject());
|
|
||||||
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
||||||
|
|
||||||
if (amortizableAmount.isGreaterThanZero()) {
|
if (amortizableAmount.isGreaterThanZero()) {
|
||||||
@@ -548,6 +548,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private void allocateOverpayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private void allocateOverpayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
|
List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
|
||||||
if (overpaymentHolder.getMoneyObject().isGreaterThanZero()) {
|
if (overpaymentHolder.getMoneyObject().isGreaterThanZero()) {
|
||||||
|
if (overpaymentHolder.getMoneyObject().isGreaterThan(loanTransaction.getAmount(currency))) {
|
||||||
|
loanTransaction.setOverPayments(loanTransaction.getAmount(currency));
|
||||||
|
} else {
|
||||||
|
loanTransaction.setOverPayments(overpaymentHolder.getMoneyObject());
|
||||||
|
}
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||||
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
||||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||||
@@ -653,7 +658,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction, MoneyHolder overpaymentHolder) {
|
private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction, MoneyHolder overpaymentHolder) {
|
||||||
if (overpaymentPortion.isGreaterThanZero()) {
|
if (overpaymentPortion.isGreaterThanZero()) {
|
||||||
onLoanOverpayment(loanTransaction, overpaymentPortion);
|
onLoanOverpayment(loanTransaction, overpaymentPortion);
|
||||||
overpaymentHolder.setMoneyObject(overpaymentPortion);
|
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().add(overpaymentPortion));
|
||||||
loanTransaction.setOverPayments(overpaymentPortion);
|
loanTransaction.setOverPayments(overpaymentPortion);
|
||||||
} else {
|
} else {
|
||||||
overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
|
overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
|
||||||
|
|||||||
+4
-2
@@ -477,9 +477,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
|||||||
|
|
||||||
// create journal entries for the disbursement (or disbursement
|
// create journal entries for the disbursement (or disbursement
|
||||||
// reversal)
|
// reversal)
|
||||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
if (MathUtil.isGreaterThanZero(principalPortion)) {
|
||||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversed);
|
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
||||||
|
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversed);
|
||||||
|
|
||||||
|
}
|
||||||
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
|
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
|
||||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion, isReversed);
|
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, overpaymentPortion, isReversed);
|
||||||
|
|||||||
+4
-2
@@ -475,8 +475,10 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
|
|||||||
final boolean isReversal = loanTransactionDTO.isReversed();
|
final boolean isReversal = loanTransactionDTO.isReversed();
|
||||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||||
|
|
||||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
if (MathUtil.isGreaterThanZero(principalPortion)) {
|
||||||
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversal);
|
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
||||||
|
loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalPortion, isReversal);
|
||||||
|
}
|
||||||
|
|
||||||
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
|
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
|
||||||
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
|
this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.OVERPAYMENT.getValue(),
|
||||||
|
|||||||
+78
@@ -3205,6 +3205,84 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UC123: Advanced payment allocation, 2nd disbursement on overpaid loan
|
||||||
|
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||||
|
// 1. Create a Loan product with Adv. Pment. Alloc.
|
||||||
|
// 2. Submit Loan and approve
|
||||||
|
// 3. Disburse only 100 from 1000
|
||||||
|
// 4. Overpay the loan (150)
|
||||||
|
// 5. Disburse again 25
|
||||||
|
@Test
|
||||||
|
public void uc123() {
|
||||||
|
runAt("22 November 2023", () -> {
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
|
.numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
|
||||||
|
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)).enableAutoRepaymentForDownPayment(false);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||||
|
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "22 November 2023",
|
||||||
|
1000.0, 4);
|
||||||
|
|
||||||
|
applicationRequest = applicationRequest.numberOfRepayments(3).loanTermFrequency(45)
|
||||||
|
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15);
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("22 November 2023").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("22 November 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 100.0, 0.0, 100.0, 0.0, null);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), 25.0, 0.0, 25.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 7), 25.0, 0.0, 25.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 22), 25.0, 0.0, 25.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 1, 6), 25.0, 0.0, 25.0, 0.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("22 November 2023").locale("en").transactionAmount(150.0));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 0.0, 100.0, 0.0, 100.0, 50.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), 25.0, 25.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 12, 7), 25.0, 25.0, 0.0, 25.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 22), 25.0, 25.0, 0.0, 25.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 1, 6), 25.0, 25.0, 0.0, 25.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("22 November 2023").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(28.0)).locale("en"));
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 0.0, 128.0, 0.0, 128.0, 22.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 11, 22), 25.0, 25.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 11, 22), 7.0, 7.0, 0.0, 0.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 7), 32.0, 32.0, 0.0, 32.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 12, 22), 32.0, 32.0, 0.0, 32.0, 0.0);
|
||||||
|
validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 1, 6), 32.0, 32.0, 0.0, 32.0, 0.0);
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
|
||||||
|
verifyTransactions(loanResponse.getLoanId(), //
|
||||||
|
transaction(100, "Disbursement", "22 November 2023", 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
|
transaction(150, "Repayment", "22 November 2023", 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0), //
|
||||||
|
transaction(28, "Disbursement", "22 November 2023", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 28.0) //
|
||||||
|
);
|
||||||
|
// verify journal entries
|
||||||
|
verifyJournalEntries(loanResponse.getLoanId(), journalEntry(100.0, loansReceivableAccount, "DEBIT"), //
|
||||||
|
journalEntry(100.0, suspenseClearingAccount, "CREDIT"), //
|
||||||
|
journalEntry(100.0, loansReceivableAccount, "CREDIT"), //
|
||||||
|
journalEntry(50.0, overpaymentAccount, "CREDIT"), //
|
||||||
|
journalEntry(150.0, suspenseClearingAccount, "DEBIT"), //
|
||||||
|
journalEntry(28.0, overpaymentAccount, "DEBIT"), //
|
||||||
|
journalEntry(28.0, suspenseClearingAccount, "CREDIT") //
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
|
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
|
||||||
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
|
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
|
||||||
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
|
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
|
||||||
|
|||||||
Reference in New Issue
Block a user