FINERACT-2081: Fix reversal of loan accruals when loan repayment got reversed
This commit is contained in:
committed by
Adam Saghy
parent
f76cef768e
commit
15e013b90e
@@ -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:
|
||||
@@ -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 | | | |
|
||||
|
||||
+12
-5
@@ -1264,6 +1264,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
updateLoanChargesAndInstallmentChargesPaidBy(loan, accrualTransaction);
|
||||
// TODO check if this is required
|
||||
// saveLoanTransactionWithDataIntegrityViolationChecks(accrualTransaction);
|
||||
accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction);
|
||||
loan.addLoanTransaction(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction));
|
||||
|
||||
@@ -1357,11 +1358,17 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
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));
|
||||
|
||||
+3
-2
@@ -1031,8 +1031,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
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);
|
||||
}
|
||||
|
||||
+80
@@ -48,6 +48,7 @@ import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||
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;
|
||||
@@ -55,6 +56,7 @@ 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;
|
||||
@@ -5274,6 +5276,84 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
});
|
||||
}
|
||||
|
||||
// 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 ---------------------------------------");
|
||||
|
||||
Reference in New Issue
Block a user