Skip to content

Commit

Permalink
FINERACT-2081: Fix reversal of loan accruals when loan repayment got …
Browse files Browse the repository at this point in the history
…reversed
  • Loading branch information
Jose Alberto Hernandez committed Oct 28, 2024
1 parent f76cef7 commit 9a4e9e2
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3350,6 +3350,7 @@ Feature: LoanRepayment
| 13 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
| 22 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
| 22 August 2024 | Repayment | 38.24 | 35.44 | 0.0 | 0.0 | 2.8 | 0.0 | false |
| 22 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
| 23 August 2024 | Repayment | 10.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
| 24 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false |
Then Loan Repayment schedule has 2 periods, with the following data for periods:
Expand All @@ -3369,9 +3370,10 @@ Feature: LoanRepayment
| 13 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
| 22 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
| 22 August 2024 | Repayment | 38.24 | 35.44 | 0.0 | 0.0 | 2.8 | 0.0 | false |
| 22 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
| 23 August 2024 | Repayment | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | false |
| 24 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false |
| 25 August 2024 | Accrual | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | false |
| 25 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
Then Loan Repayment schedule has 2 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 23 July 2024 | | 111.92 | | | 0.0 | | 0.0 | 0.0 | | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,7 @@ private void processAccrualTransactionsOnLoanClosure(Loan loan) {
updateLoanChargesAndInstallmentChargesPaidBy(loan, accrualTransaction);
// TODO check if this is required
// saveLoanTransactionWithDataIntegrityViolationChecks(accrualTransaction);
accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction);
loan.addLoanTransaction(accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction));

Expand Down Expand Up @@ -1357,11 +1358,17 @@ private void determineReceivableIncomeDetailsForLoanClosure(Loan loan, Map<Strin
}
BigDecimal accruedAmount = BigDecimal.ZERO;
BigDecimal waivedAmount = BigDecimal.ZERO;
for (LoanChargePaidBy loanChargePaidBy : loanCharge.getLoanChargePaidBySet()) {
if (loanChargePaidBy.getLoanTransaction().isAccrual()) {
accruedAmount = accruedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
} else if (loanChargePaidBy.getLoanTransaction().isChargesWaiver()) {
waivedAmount = waivedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
for (LoanTransaction loanTransaction : loan.getLoanTransactions()) {
if (loanTransaction.isAccrual() || loanTransaction.isChargesWaiver()) {
for (LoanChargePaidBy loanChargePaidBy : loanTransaction.getLoanChargesPaid()) {
if (loanChargePaidBy.getLoanCharge().getId().equals(loanCharge.getId())) {
if (loanTransaction.isAccrual()) {
accruedAmount = accruedAmount.add(loanTransaction.getAmount());
} else if (loanTransaction.isChargesWaiver()) {
waivedAmount = waivedAmount.add(loanTransaction.getAmount());
}
}
}
}
}
Money needToAccrueAmount = MathUtil.negativeToZero(loanCharge.getAmount(currency).minus(accruedAmount).minus(waivedAmount));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,8 +1031,9 @@ private boolean addCharge(final Loan loan, final Charge chargeDefinition, LoanCh
loanCharge = this.loanChargeRepository.saveAndFlush(loanCharge);

// we want to apply charge transactions only for those loans charges that are applied when a loan is active and
// the loan product uses Upfront Accruals
if (loan.getStatus().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
// the loan product uses Upfront Accruals, or only when the loan are closed too,
if ((loan.getStatus().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct())
|| loan.getStatus().isOverpaid() || loan.getStatus().isClosedObligationsMet()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.LoanProduct;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
Expand Down Expand Up @@ -5274,6 +5276,84 @@ public void uc148c() {
});
}

// UC149: Validate TotalUnpaidAccruedDueInterest in Zero when Interest paid is higher than Accrued Interest
// 1. Create a Loan product with Adv. Pment. Alloc. and with Declining Balance, Accrual accounting and Daily Accrual
// Activity
// 2. Submit Loan, approve and Disburse
// 3. Add a Loan Specific Due date charge
// 4. Add a Repayment higher than accrued interest to validate TotalUnpaidAccruedDueInterest equal to Zero
@Test
public void uc149() {
String operationDate = "22 April 2024";
AtomicLong createdLoanId = new AtomicLong();
AtomicLong createdLoanChargeId = new AtomicLong();
AtomicLong createdLoanAccrualId = new AtomicLong();
runAt(operationDate, () -> {
Long clientId = client.getClientId();
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.interestRateFrequencyType(YEARS).numberOfRepayments(4)//
.maxInterestRatePerPeriod((double) 0)//
.repaymentEvery(1)//
.repaymentFrequencyType(1L)//
.allowPartialPeriodInterestCalcualtion(false)//
.multiDisburseLoan(false)//
.disallowExpectedDisbursements(null)//
.allowApprovedDisbursedAmountsOverApplied(null)//
.overAppliedCalculationType(null)//
.overAppliedNumber(null)//
.installmentAmountInMultiplesOf(null)//
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
;//
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 400.0, 6);

applicationRequest = applicationRequest.interestCalculationPeriodType(DAYS).interestRatePerPeriod(BigDecimal.ZERO)
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);

PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
createdLoanId.set(loanResponse.getLoanId());

loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
.approvedLoanAmount(BigDecimal.valueOf(400.0)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));

loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(400.0)).locale("en"));

addRepaymentForLoan(createdLoanId.get(), 600.00, operationDate);

createdLoanChargeId.set(addCharge(createdLoanId.get(), true, 30, operationDate));

executeInlineCOB(createdLoanId.get());
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
// Validate Loan Accrual transaction
final GetLoansLoanIdTransactions loanTransaction = loanDetails.getTransactions().stream()
.filter(t -> Boolean.TRUE.equals(t.getType().getAccrual())).toList().get(0);
assertNotNull(loanTransaction);
createdLoanAccrualId.set(loanTransaction.getId());
});

runAt("10 October 2024", () -> {
PostLoansLoanIdChargesChargeIdRequest request = new PostLoansLoanIdChargesChargeIdRequest().amount(15.0).locale("en");
loanTransactionHelper.chargeAdjustment(createdLoanId.get(), createdLoanChargeId.get(), request);

loanTransactionHelper.makeGoodwillCredit(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest()
.dateFormat(DATETIME_PATTERN).transactionDate(operationDate).locale("en").transactionAmount(15.0));

final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
// Get the Repayment transaction
GetLoansLoanIdTransactions loanTransaction = loanDetails.getTransactions().stream()
.filter(t -> Boolean.TRUE.equals(t.getType().getRepayment())).toList().get(0);
loanTransactionHelper.reverseRepayment(Math.toIntExact(createdLoanId.get()), Math.toIntExact(loanTransaction.getId()),
operationDate);

// Validate Loan Accrual transaction
loanTransaction = loanDetails.getTransactions().stream().filter(t -> Boolean.TRUE.equals(t.getType().getAccrual())).toList()
.get(0);
assertNotNull(loanTransaction);
assertEquals(loanTransaction.getId(), createdLoanAccrualId.get());
});
}

private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
Expand Down

0 comments on commit 9a4e9e2

Please sign in to comment.