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 |
|
| 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 | 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 | 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 |
|
| 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 |
|
| 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:
|
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 |
|
| 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 | 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 | 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 |
|
| 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 |
|
| 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:
|
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 |
|
| 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 | | | |
|
| | | 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);
|
updateLoanChargesAndInstallmentChargesPaidBy(loan, accrualTransaction);
|
||||||
// TODO check if this is required
|
// TODO check if this is required
|
||||||
// saveLoanTransactionWithDataIntegrityViolationChecks(accrualTransaction);
|
// saveLoanTransactionWithDataIntegrityViolationChecks(accrualTransaction);
|
||||||
|
accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction);
|
||||||
loan.addLoanTransaction(accrualTransaction);
|
loan.addLoanTransaction(accrualTransaction);
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction));
|
||||||
|
|
||||||
@@ -1357,11 +1358,17 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
}
|
}
|
||||||
BigDecimal accruedAmount = BigDecimal.ZERO;
|
BigDecimal accruedAmount = BigDecimal.ZERO;
|
||||||
BigDecimal waivedAmount = BigDecimal.ZERO;
|
BigDecimal waivedAmount = BigDecimal.ZERO;
|
||||||
for (LoanChargePaidBy loanChargePaidBy : loanCharge.getLoanChargePaidBySet()) {
|
for (LoanTransaction loanTransaction : loan.getLoanTransactions()) {
|
||||||
if (loanChargePaidBy.getLoanTransaction().isAccrual()) {
|
if (loanTransaction.isAccrual() || loanTransaction.isChargesWaiver()) {
|
||||||
accruedAmount = accruedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
|
for (LoanChargePaidBy loanChargePaidBy : loanTransaction.getLoanChargesPaid()) {
|
||||||
} else if (loanChargePaidBy.getLoanTransaction().isChargesWaiver()) {
|
if (loanChargePaidBy.getLoanCharge().getId().equals(loanCharge.getId())) {
|
||||||
waivedAmount = waivedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
|
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));
|
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);
|
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
|
// 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
|
// the loan product uses Upfront Accruals, or only when the loan are closed too,
|
||||||
if (loan.getStatus().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
if ((loan.getStatus().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct())
|
||||||
|
|| loan.getStatus().isOverpaid() || loan.getStatus().isClosedObligationsMet()) {
|
||||||
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
|
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
|
||||||
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
|
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.GetLoansLoanIdLoanChargeData;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
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.LoanProduct;
|
||||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
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.PostCreateRescheduleLoansResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
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.PostLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
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,
|
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||||
|
|||||||
Reference in New Issue
Block a user