diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index d6c542e28..b44af4d2d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -936,18 +936,33 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { Money penaltyPortion = Money.zero(currency); for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) { + // TODO: test with interest waiving interestPortion = interestPortion.add(loanRepaymentScheduleInstallment.getInterestCharged(currency)) .minus(loanRepaymentScheduleInstallment.getInterestAccrued(currency)) .minus(loanRepaymentScheduleInstallment.getInterestWaived(currency)); - feePortion = feePortion.add(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency)) - .minus(loanRepaymentScheduleInstallment.getFeeAccrued(currency)) - .minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency)) - .minus(loanRepaymentScheduleInstallment.getCreditedFee(currency)); - penaltyPortion = penaltyPortion.add(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency)) - .minus(loanRepaymentScheduleInstallment.getPenaltyAccrued(currency)) - .minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency)) - .minus(loanRepaymentScheduleInstallment.getCreditedPenalty(currency)); } + + for (LoanCharge loanCharge : loan.getLoanCharges()) { + if (!loanCharge.isActive()) { + continue; + } + 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()); + } + } + Money needToAccrueAmount = MathUtil.negativeToZero(loanCharge.getAmount(currency).minus(accruedAmount).minus(waivedAmount)); + if (loanCharge.isPenaltyCharge()) { + penaltyPortion = penaltyPortion.add(needToAccrueAmount); + } else if (loanCharge.isFeeCharge()) { + feePortion = feePortion.add(needToAccrueAmount); + } + } + Money total = interestPortion.plus(feePortion).plus(penaltyPortion); if (total.isGreaterThanZero()) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java index 9d631d87f..8c01cccaa 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java @@ -32,6 +32,7 @@ import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse; import org.apache.fineract.client.models.PostLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.integrationtests.common.ClientHelper; @@ -139,4 +140,130 @@ public class LoanWaiveChargeTest extends BaseLoanIntegrationTest { assertEquals(expected, obligationsMetOnDate); }); } + + @ParameterizedTest + @MethodSource("processingStrategy") + public void accrualIsCalculatedWhenThereIsWaivedChargeAndLoanIsClosed(boolean advancedPaymentStrategy) { + double amount = 1000.0; + AtomicLong appliedLoanId = new AtomicLong(); + String LoanCoBJobName = "Loan COB"; + + runAt("01 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + PostLoanProductsRequest product; + if (advancedPaymentStrategy) { + product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation(); + } else { + product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + } + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + // Apply and Approve Loan + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, 1); + if (advancedPaymentStrategy) { + applicationRequest = applicationRequest + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY); + } + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "01 January 2023")); + + Long loanId = approvedLoanResult.getLoanId(); + appliedLoanId.set(loanId); + + // disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(amount), "01 January 2023"); + + // verify schedule + verifyRepaymentSchedule(loanId, // + installment(0.0, null, "01 January 2023"), // + installment(1000.0, 0.0, 0.0, 1000.0, false, "31 January 2023")); + }); + + runAt("10 January 2023", () -> { + Long loanId = appliedLoanId.get(); + + // create charge + double chargeAmount = 10.0; + PostChargesResponse chargeResult = createCharge(chargeAmount); + Long chargeId = chargeResult.getResourceId(); + + PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "09 January 2023", chargeAmount); + loanChargeResult.getResourceId(); + this.schedulerJobHelper.executeAndAwaitJob(LoanCoBJobName); + + verifyRepaymentSchedule(loanId, // + installment(0.0, null, "01 January 2023"), // + installment(1000.0, 0.0, 10.0, 1010.0, false, "31 January 2023") // + + ); + verifyTransactions(loanId, // + transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0)); + }); + runAt("11 January 2023", () -> { + Long loanId = appliedLoanId.get(); + + // create charge + double chargeAmount = 9.0; + PostChargesResponse chargeResult = createCharge(chargeAmount); + Long chargeId = chargeResult.getResourceId(); + + PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "10 January 2023", chargeAmount); + Long loanChargeId = loanChargeResult.getResourceId(); + this.schedulerJobHelper.executeAndAwaitJob(LoanCoBJobName); + // waive charge + waiveLoanCharge(loanId, loanChargeId, 1); + + verifyTransactions(loanId, // + transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0), // + transaction(9.0, "Accrual", "10 January 2023", 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), // + transaction(9.0, "Waive loan charges", "10 January 2023", 1000.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0) // + ); + + verifyRepaymentSchedule(loanId, // + installment(0.0, null, "01 January 2023"), // + installment(1000.0, 0.0, 19.0, 1010.0, false, "31 January 2023") // + ); + }); + + runAt("12 January 2023", () -> { + Long loanId = appliedLoanId.get(); + + // create charge + double chargeAmount = 8.0; + PostChargesResponse chargeResult = createCharge(chargeAmount); + Long chargeId = chargeResult.getResourceId(); + + PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "11 January 2023", chargeAmount); + loanChargeResult.getResourceId(); + + loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().transactionDate("12 January 2023") + .dateFormat("dd MMMM yyyy").locale("en").transactionAmount(1018.0)); + + verifyTransactions(loanId, // + transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0), // + transaction(9.0, "Accrual", "10 January 2023", 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), // + transaction(9.0, "Waive loan charges", "10 January 2023", 1000.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), // + transaction(1018.0, "Repayment", "12 January 2023", 0.0, 1000.0, 0.0, 18.0, 0.0, 0.0, 0.0), // + transaction(8.0, "Accrual", "12 January 2023", 0.0, 0.0, 0.0, 8.0, 0.0, 0.0, 0.0) // + ); + + verifyRepaymentSchedule(loanId, // + installment(0.0, null, "01 January 2023"), // + installment(1000.0, 0.0, 27.0, 0.0, true, "31 January 2023") // + ); + }); + + } }