FINERACT-2228: Fix incorrect current balance calculation and unintentional transaction reversals
This commit is contained in:
@@ -4450,3 +4450,121 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
|
||||||
| 01 February 2024 | Repayment | 20.0 | 19.42 | 0.58 | 0.0 | 0.0 | 80.58 | false | false |
|
| 01 February 2024 | Repayment | 20.0 | 19.42 | 0.58 | 0.0 | 0.0 | 80.58 | false | false |
|
||||||
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
||||||
|
|
||||||
|
@TestRailId:C3569
|
||||||
|
Scenario: Verify Loan is fully paid and closed after partial repayments, Merchant issued refund which overpays the loan, partial CBR and reversal of 1st repayment
|
||||||
|
When Admin sets the business date to "28 March 2025"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 26 March 2025 | 120 | 35.29 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "26 March 2025" with "120" amount and expected disbursement date on "26 March 2025"
|
||||||
|
When Admin successfully disburse the loan on "26 March 2025" with "120" EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | | 81.15 | 38.85 | 3.53 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
|
||||||
|
| 2 | 30 | 26 May 2025 | | 41.16 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
|
||||||
|
| 3 | 31 | 26 June 2025 | | 0.0 | 41.16 | 1.21 | 0.0 | 0.0 | 42.37 | 0.0 | 0.0 | 0.0 | 42.37 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 7.13 | 0.0 | 0.0 | 127.13 | 0.0 | 0.0 | 0.0 | 127.13 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | | 80.58 | 39.42 | 2.96 | 0.0 | 0.0 | 42.38 | 20.0 | 20.0 | 0.0 | 22.38 |
|
||||||
|
| 2 | 30 | 26 May 2025 | | 40.57 | 40.01 | 2.37 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
|
||||||
|
| 3 | 31 | 26 June 2025 | | 0.0 | 40.57 | 1.19 | 0.0 | 0.0 | 41.76 | 0.0 | 0.0 | 0.0 | 41.76 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 6.52 | 0.0 | 0.0 | 126.52 | 20.0 | 20.0 | 0.0 | 106.52 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
|
||||||
|
And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | | 80.01 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 40.0 | 40.0 | 0.0 | 2.38 |
|
||||||
|
| 2 | 30 | 26 May 2025 | | 39.98 | 40.03 | 2.35 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
|
||||||
|
| 3 | 31 | 26 June 2025 | | 0.0 | 39.98 | 1.18 | 0.0 | 0.0 | 41.16 | 0.0 | 0.0 | 0.0 | 41.16 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 5.92 | 0.0 | 0.0 | 125.92 | 40.0 | 40.0 | 0.0 | 85.92 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
|
||||||
|
And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "27 March 2025" with 120 EUR transaction amount and self-generated Idempotency key
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 |
|
||||||
|
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 |
|
||||||
|
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
|
||||||
|
| 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
Then Loan status will be "OVERPAID"
|
||||||
|
Then Loan has 0 outstanding amount
|
||||||
|
Then Loan has 40 overpaid amount
|
||||||
|
And Admin makes Credit Balance Refund transaction on "28 March 2025" with 20 EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 |
|
||||||
|
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 |
|
||||||
|
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
|
||||||
|
| 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
Then Loan status will be "OVERPAID"
|
||||||
|
Then Loan has 0 outstanding amount
|
||||||
|
Then Loan has 20 overpaid amount
|
||||||
|
When Customer undo "1"th "Repayment" transaction made on "27 March 2025"
|
||||||
|
Then Loan Repayment schedule has 3 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 |
|
||||||
|
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 26 April 2025 | 27 March 2025 | 84.76 | 35.24 | 0.11 | 0.0 | 0.0 | 35.35 | 35.35 | 35.35 | 0.0 | 0.0 |
|
||||||
|
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
|
||||||
|
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | true | false |
|
||||||
|
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | true |
|
||||||
|
| 27 March 2025 | Merchant Issued Refund | 120.0 | 100.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
|
||||||
|
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
|
||||||
|
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | true |
|
||||||
|
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
|
||||||
|
Then Loan has 0 outstanding amount
|
||||||
|
|||||||
-1
@@ -60,7 +60,6 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
||||||
loan.retrieveListOfTransactionsForReprocessing());
|
loan.retrieveListOfTransactionsForReprocessing());
|
||||||
loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine());
|
|
||||||
handleChangedDetail(changedTransactionDetail);
|
handleChangedDetail(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -275,8 +275,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, periodDueDate,
|
Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, disbursementDate);
|
||||||
disbursementDate);
|
|
||||||
|
|
||||||
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc);
|
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc);
|
||||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||||
|
|||||||
+1
-2
@@ -119,8 +119,7 @@ public interface EMICalculator {
|
|||||||
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
|
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
|
||||||
|
|
||||||
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate,
|
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate targetDate);
|
||||||
LocalDate targetDate);
|
|
||||||
|
|
||||||
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
|
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
|
||||||
|
|
||||||
|
|||||||
+66
-39
@@ -256,7 +256,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
@NotNull LocalDate targetDate) {
|
@NotNull LocalDate targetDate) {
|
||||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
periodDueDate, targetDate);
|
targetDate);
|
||||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
long notFullyRepaidRepaymentPeriodCount = recalculatedScheduleModelTillDate.repaymentPeriods().stream()
|
long notFullyRepaidRepaymentPeriodCount = recalculatedScheduleModelTillDate.repaymentPeriods().stream()
|
||||||
.filter(rp -> !rp.isFullyPaid()).count();
|
.filter(rp -> !rp.isFullyPaid()).count();
|
||||||
@@ -265,6 +265,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
||||||
if (multiplePeriodIsUnpaid) {
|
if (multiplePeriodIsUnpaid) {
|
||||||
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
|
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
|
||||||
|
Money totalOutstandingPrincipal = recalculatedScheduleModelTillDate.getTotalOutstandingPrincipal();
|
||||||
|
Money outstandingPrincipal = repaymentPeriod.getOutstandingPrincipal();
|
||||||
|
// If there are less outstanding principal than anticipated
|
||||||
|
Money emiAdjustment = MathUtil.negativeToZero(outstandingPrincipal.minus(totalOutstandingPrincipal));
|
||||||
|
repaymentPeriod.setEmi(repaymentPeriod.getEmi().minus(emiAdjustment));
|
||||||
} else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) {
|
} else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) {
|
||||||
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
|
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
|
||||||
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
|
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
|
||||||
@@ -285,7 +290,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
@NotNull LocalDate targetDate, boolean includeChargebackInterest) {
|
@NotNull LocalDate targetDate, boolean includeChargebackInterest) {
|
||||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
periodDueDate, targetDate);
|
targetDate);
|
||||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest()
|
return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest()
|
||||||
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
|
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
|
||||||
@@ -293,11 +298,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate,
|
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) {
|
||||||
LocalDate targetDate) {
|
|
||||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
periodDueDate, targetDate);
|
targetDate);
|
||||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriod(targetDate).orElseGet(() -> {
|
||||||
|
// If target date is after maturity date
|
||||||
|
if (targetDate.isAfter(recalculatedScheduleModelTillDate.getLastRepaymentPeriod().getDueDate())) {
|
||||||
|
return recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
|
||||||
|
} else {
|
||||||
|
// if target date is before 1st disbursement date, we use 1st repayment period
|
||||||
|
return recalculatedScheduleModelTillDate.repaymentPeriods().get(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return repaymentPeriod.getOutstandingLoanBalance();
|
return repaymentPeriod.getOutstandingLoanBalance();
|
||||||
}
|
}
|
||||||
@@ -343,43 +355,58 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(
|
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(
|
||||||
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
|
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate targetDate) {
|
||||||
MathContext mc = scheduleModel.mc();
|
MathContext mc = scheduleModel.mc();
|
||||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||||
RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
boolean isBeforeFirstDisbursement = targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate());
|
||||||
|
boolean isAfterMaturityDate = !targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
|
||||||
LocalDate adjustedTargetDate = targetDate;
|
if (isBeforeFirstDisbursement) {
|
||||||
InterestPeriod interestPeriod;
|
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().clear());
|
||||||
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
return scheduleModelCopy;
|
||||||
interestPeriod = repaymentPeriod.getFirstInterestPeriod();
|
} else if (isAfterMaturityDate) {
|
||||||
adjustedTargetDate = repaymentPeriod.getFromDate();
|
return scheduleModelCopy;
|
||||||
} else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
|
|
||||||
interestPeriod = repaymentPeriod.getLastInterestPeriod();
|
|
||||||
adjustedTargetDate = repaymentPeriod.getDueDate();
|
|
||||||
} else {
|
} else {
|
||||||
interestPeriod = repaymentPeriod.findInterestPeriod(targetDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriod(targetDate).orElseThrow();
|
||||||
}
|
|
||||||
// TODO use findInterestPeriod
|
|
||||||
scheduleModelCopy.findRepaymentPeriod(targetDate)//
|
|
||||||
.flatMap(rp -> rp.findInterestPeriod(targetDate)).ifPresent(ip -> ip.setDueDate(targetDate)); //
|
|
||||||
interestPeriod.setDueDate(adjustedTargetDate);
|
|
||||||
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
|
|
||||||
int nextIdx = index + 1;
|
|
||||||
boolean thereIsInterestPeriodFromDateOnTargetDate = repaymentPeriod.getInterestPeriods().size() > nextIdx
|
|
||||||
&& repaymentPeriod.getInterestPeriods().get(nextIdx).getFromDate().isEqual(targetDate);
|
|
||||||
if (thereIsInterestPeriodFromDateOnTargetDate) {
|
|
||||||
// NOTE: If there is a next interest period with fromDate on the target date
|
|
||||||
// then the related chargeback amounts comes from the next interest period too.
|
|
||||||
InterestPeriod nextInterestPeriod = repaymentPeriod.getInterestPeriods().get(nextIdx);
|
|
||||||
interestPeriod.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
|
|
||||||
interestPeriod.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
|
|
||||||
}
|
|
||||||
repaymentPeriod.getInterestPeriods().subList(nextIdx, repaymentPeriod.getInterestPeriods().size()).clear();
|
|
||||||
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
|
|
||||||
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
|
||||||
calculateOutstandingBalance(scheduleModelCopy);
|
|
||||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
|
|
||||||
|
|
||||||
|
scheduleModelCopy.repaymentPeriods().forEach(rp -> {
|
||||||
|
if (rp.getDueDate().isAfter(targetDate)) {
|
||||||
|
if (rp.equals(repaymentPeriod)) {
|
||||||
|
rp.findInterestPeriod(targetDate).ifPresent(ip -> {
|
||||||
|
ip.setDueDate(targetDate);
|
||||||
|
int index = rp.getInterestPeriods().indexOf(ip);
|
||||||
|
int nextIdx = index + 1;
|
||||||
|
boolean thereIsInterestPeriodFromDateOnTargetDate = ip.getRepaymentPeriod().getInterestPeriods()
|
||||||
|
.size() > nextIdx;
|
||||||
|
if (thereIsInterestPeriodFromDateOnTargetDate) {
|
||||||
|
// NOTE: If there is a next interest period with fromDate on the target date
|
||||||
|
// then the related chargeback amounts comes from the next interest period too.
|
||||||
|
InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
|
||||||
|
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
|
||||||
|
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
|
||||||
|
}
|
||||||
|
ip.getRepaymentPeriod().getInterestPeriods()
|
||||||
|
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
|
||||||
|
});
|
||||||
|
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
|
||||||
|
&& (rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero()
|
||||||
|
|| rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) {
|
||||||
|
// NOTE: we need to check whether there is chargeback on the 1st interest period of the next
|
||||||
|
// period
|
||||||
|
// if so, we need to retain that interest period, but need to update due date to match with from
|
||||||
|
// date -> 0 interest
|
||||||
|
rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate());
|
||||||
|
if (rp.getInterestPeriods().size() > 1) {
|
||||||
|
rp.getInterestPeriods().subList(1, rp.getInterestPeriods().size()).clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rp.getInterestPeriods().clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
||||||
|
calculateOutstandingBalance(scheduleModelCopy);
|
||||||
|
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
|
||||||
|
}
|
||||||
return scheduleModelCopy;
|
return scheduleModelCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||||
@@ -338,6 +339,14 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
return repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus);
|
return repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getTotalOutstandingPrincipal() {
|
||||||
|
return MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getTotalOutstandingInterest() {
|
||||||
|
return MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest()));
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
|
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
|
||||||
return repaymentPeriods.stream() //
|
return repaymentPeriods.stream() //
|
||||||
.filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
|
.filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
|
||||||
|
|||||||
+4
@@ -318,4 +318,8 @@ public final class RepaymentPeriod {
|
|||||||
public boolean isFirstRepaymentPeriod() {
|
public boolean isFirstRepaymentPeriod() {
|
||||||
return previous == null;
|
return previous == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getOutstandingPrincipal() {
|
||||||
|
return getDuePrincipal().minus(getPaidPrincipal());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -302,7 +302,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
} else {
|
} else {
|
||||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||||
}
|
}
|
||||||
|
loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine());
|
||||||
loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -184,8 +184,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
|||||||
if (!transactionIds.isEmpty()) {
|
if (!transactionIds.isEmpty()) {
|
||||||
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds,
|
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds,
|
||||||
PortfolioAccountType.LOAN);
|
PortfolioAccountType.LOAN);
|
||||||
loan.updateLoanSummaryAndStatus();
|
|
||||||
}
|
}
|
||||||
|
loan.updateLoanSummaryAndStatus();
|
||||||
|
|
||||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
|
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
|
||||||
false);
|
false);
|
||||||
@@ -267,8 +267,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
|||||||
writeOffTransaction.reverse();
|
writeOffTransaction.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
loan.updateLoanSummaryAndStatus();
|
|
||||||
|
|
||||||
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
|
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
|
||||||
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail,
|
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail,
|
||||||
loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO);
|
loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO);
|
||||||
|
|||||||
Reference in New Issue
Block a user