From 136bb0009ab3830045dbf0dfb09c7cde3a0162ba Mon Sep 17 00:00:00 2001 From: Marta Jankovics Date: Wed, 11 Dec 2024 12:42:42 +0100 Subject: [PATCH] FINERACT-2060: Progressive loan - final accrual calculation on closed loan --- .../test/helper/ErrorMessageHelper.java | 124 +++------ .../test/stepdef/common/BatchApiStepDef.java | 4 +- .../stepdef/common/JournalEntriesStepDef.java | 8 +- .../test/stepdef/loan/LoanStepDef.java | 25 +- .../resources/features/EMICalculation.feature | 2 + .../features/LoanAccrualActivity.feature | 15 +- .../resources/features/LoanChargeback.feature | 12 +- .../resources/features/LoanRepayment.feature | 10 +- .../loanaccount/data/AccrualChargeData.java | 1 + .../loanaccount/data/AccrualPeriodData.java | 10 + .../data/LoanScheduleAccrualData.java | 201 --------------- .../portfolio/loanaccount/domain/Loan.java | 11 +- .../LoanAccrualActivityProcessingService.java | 9 +- ...alTransactionBusinessEventServiceImpl.java | 10 +- .../LoanAccrualsProcessingService.java | 8 +- .../service/LoanWritePlatformService.java | 11 - .../domain/LoanAccountDomainServiceJpa.java | 37 +-- ...PeriodicAccrualEntriesForLoansTasklet.java | 2 +- ...heduleRequestWritePlatformServiceImpl.java | 8 +- ...nAccrualActivityProcessingServiceImpl.java | 221 +++++++++------- .../service/LoanAccrualEventService.java | 72 ++++++ .../LoanAccrualsProcessingServiceImpl.java | 211 +++++++--------- .../LoanChargeWritePlatformServiceImpl.java | 25 +- .../LoanStatusChangePlatformServiceImpl.java | 16 +- .../service/LoanTransactionAssembler.java | 22 +- ...WritePlatformServiceJpaRepositoryImpl.java | 238 +++++++----------- .../starter/LoanAccountConfiguration.java | 37 +-- ...oanChargeWritePlatformServiceImplTest.java | 3 + ...ePlatformServiceJpaRepositoryImplTest.java | 4 - .../BaseLoanIntegrationTest.java | 13 +- .../LoanChargeProgressiveTest.java | 12 +- ...WithCreditAllocationsIntegrationTests.java | 4 + .../LoanInterestRefundTest.java | 69 +++-- ...TransactionAccrualActivityPostingTest.java | 64 ++--- 34 files changed, 682 insertions(+), 837 deletions(-) delete mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index 08bd21ff565..4bc6dbdfe85 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -348,9 +348,12 @@ public static String wrongDataInDelinquentLastRepaymentDate(String actual, Strin } public static String wrongLoanStatus(Integer actual, Integer expected) { - String actualToStr = actual.toString(); - String expectedToStr = expected.toString(); - return String.format("Wrong Loan status ID. Actual ID is: %s - But expected ID is: %s", actualToStr, expectedToStr); + return wrongLoanStatus(null, actual, expected); + } + + public static String wrongLoanStatus(String resourceId, Integer actual, Integer expected) { + return String.format("Wrong Loan status ID of resource %s. Actual ID is: %s - But expected ID is: %s", resourceId, actual, + expected); } public static String wrongFraudFlag(Boolean actualFraudStatus, Boolean expectedFraudStatus) { @@ -446,109 +449,54 @@ public static String idempotencyKeyNoMatch(String actual, String expected) { return String.format("Idempotency key is not matching: Actual value is: %s - But expected value is: %s", actual, expected); } - public static String wrongNumberOfLinesInRepaymentSchedule(int actual, int expected) { - return wrongNumberOfLinesInRepaymentSchedule(null, actual, expected); - } - public static String wrongNumberOfLinesInRepaymentSchedule(String resourceId, int actual, int expected) { - String actualStr = String.valueOf(actual); - String expectedStr = String.valueOf(expected); - String prefx = "Number of lines in Repayment schedule"; - String postfx = " is not correct. Actual value is: %s - Expected value is: %s"; - if (resourceId != null) { - return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr); - } - return String.format(prefx + postfx, actualStr, expectedStr); - } - - public static String wrongValueInLineInRepaymentSchedule(int line, List> actual, List expected) { - return wrongValueInLineInRepaymentSchedule(null, line, actual, expected); + return String.format("Number of lines in Repayment schedule of resource %s is not correct. " // + + "Actual value is: %s - But expected value is: %s", resourceId, actual, expected); } - public static String wrongValueInLineInRepaymentSchedule(String resourceId, int line, List> actual, + public static String wrongValueInLineInRepaymentSchedule(String resourceId, int line, List> actualList, List expected) { - String lineStr = String.valueOf(line); - String expectedStr = expected.toString(); - StringBuilder sb = new StringBuilder(); - for (List innerList : actual) { - sb.append(innerList.toString()); - sb.append(System.lineSeparator()); - } - String prefx = "%nWrong value in Repayment schedule"; - String postfx = " tab line %s. %nActual values in line (with the same due date) are: %n%s %nExpected values in line: %n%s"; - if (resourceId != null) { - return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr); - } - return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr); - } - - public static String wrongValueInLineInTransactionsTab(int line, List> actual, List expected) { - return wrongValueInLineInTransactionsTab(null, line, actual, expected); - } - - public static String wrongValueInLineInTransactionsTab(String resourceId, int line, List> actual, List expected) { - String lineStr = String.valueOf(line); - String expectedStr = expected.toString(); - StringBuilder sb = new StringBuilder(); - for (List innerList : actual) { - sb.append(innerList.toString()); - sb.append(System.lineSeparator()); - } - String prefx = "%nWrong value in Transactions tab"; - String postfx = " line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s"; - if (resourceId != null) { - return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr); - } - return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr); + String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())); + return String.format("%nWrong value in Repayment schedule of resource %s tab line %s." // + + "%nActual values in line (with the same due date) are: %n%s - But expected values in line: %n%s", resourceId, line, + actual, expected); } - public static String nrOfLinesWrongInTransactionsTab(int actual, int expected) { - return nrOfLinesWrongInTransactionsTab(null, actual, expected); + public static String wrongValueInLineInTransactionsTab(String resourceId, int line, List> actualList, + List expected) { + String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())); + return String.format("%nWrong value in Transactions tab of resource %s line %s." // + + "%nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s", resourceId, line, actual, + expected); } public static String nrOfLinesWrongInTransactionsTab(String resourceId, int actual, int expected) { - String actualStr = String.valueOf(actual); - String expectedStr = String.valueOf(expected); - - String prefx = "%nNumber of lines does not match in Transactions tab and expected datatable"; - String postfx = ". %nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n"; - if (resourceId != null) { - return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr); - } - return String.format(prefx + postfx, actualStr, expectedStr); + return String.format("%nNumber of lines does not match in Transactions tab and expected datatable of resource %s." // + + "%nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n", resourceId, actual, expected); } - public static String wrongValueInLineInChargesTab(int line, List> actual, List expected) { - String lineStr = String.valueOf(line); - String expectedStr = expected.toString(); - StringBuilder sb = new StringBuilder(); - for (List innerList : actual) { - sb.append(innerList.toString()); - sb.append(System.lineSeparator()); - } - - return String.format( - "%nWrong value in Charges tab line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s", - lineStr, sb.toString(), expectedStr); + public static String wrongValueInLineInChargesTab(String resourceId, int line, List> actualList, List expected) { + String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())); + return String.format("%nWrong value in Charges tab of resource %s line %s." // + + "%nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s", resourceId, line, actual, + expected); } public static String wrongValueInLineInJournalEntries(int line, List>> actual, List expected) { - String lineStr = String.valueOf(line); - String expectedStr = expected.toString(); - StringBuilder sb = new StringBuilder(); - for (List> innerList : actual) { - sb.append(innerList.toString()); - sb.append(System.lineSeparator()); - } + return wrongValueInLineInJournalEntries(null, line, actual, expected); + } - return String.format( - "%nWrong value in Journal entries line %s. %nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s", - lineStr, sb.toString(), expectedStr); + public static String wrongValueInLineInJournalEntries(String resourceId, int line, List>> actualList, + List expected) { + String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())); + return String.format("%nWrong value in Journal entries of resource %s line %s." // + + "%nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s", + resourceId, line, actual, expected); } public static String wrongDataInJournalEntriesGlAccountType(int line, String actual, String expected) { - return String.format("Wrong data in Journal entries, line %s / GL account type. Actual value is: %s - But expected value is: %s", - line, actual, expected); + return String.format("Wrong data in Journal entries, line %s / GL account type. " // + + "Actual value is: %s - But expected value is: %s", line, actual, expected); } public static String wrongDataInJournalEntriesGlAccountCode(int line, String actual, String expected) { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java index e5d611bea59..6f80c2b68f6 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java @@ -865,7 +865,9 @@ public void givenLoanApproved(int nr) throws IOException { Integer statusIdActual = status.getId(); Integer statusIdExpected = LoanStatus.APPROVED.value; - assertThat(statusIdActual).as(ErrorMessageHelper.wrongLoanStatus(statusIdActual, statusIdExpected)).isEqualTo(statusIdExpected); + String resourceId = String.valueOf(response.body().getId()); + assertThat(statusIdActual).as(ErrorMessageHelper.wrongLoanStatus(resourceId, statusIdActual, statusIdExpected)) + .isEqualTo(statusIdExpected); } @Then("Nr. {int} Client creation was rolled back") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java index 97191a40a3d..f2f74e20db9 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java @@ -59,6 +59,7 @@ public void journalEntryDataCheck(String transactionType, String transactionDate DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); @@ -132,7 +133,8 @@ public void journalEntryDataCheck(String transactionType, String transactionDate } } assertThat(containsAnyExpected) - .as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue(); + .as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues)) + .isTrue(); } } @@ -141,6 +143,7 @@ public void revertedJournalEntryDataCheck(String transactionType, String transac DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); @@ -218,7 +221,8 @@ public void revertedJournalEntryDataCheck(String transactionType, String transac } } assertThat(containsAnyExpected) - .as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue(); + .as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues)) + .isTrue(); } } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 20d50d2af6b..e1b4cdcb926 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -1298,6 +1298,8 @@ public void failedLoanApproveWithAmount(String approveDate, String approvedAmoun public void disburseLoan(String actualDisbursementDate, String transactionAmount) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); + PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() .actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount)); @@ -1310,7 +1312,7 @@ public void disburseLoan(String actualDisbursementDate, String transactionAmount Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId()); assertThat(statusActual)// - .as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))// + .as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))// .isEqualTo(statusExpected);// eventCheckHelper.disburseLoanEventCheck(loanId); eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); @@ -1320,6 +1322,8 @@ public void disburseLoan(String actualDisbursementDate, String transactionAmount public void disburseLoanWithoutAutoDownpayment(String actualDisbursementDate, String transactionAmount) throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); + PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() .actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount)); @@ -1333,7 +1337,7 @@ public void disburseLoanWithoutAutoDownpayment(String actualDisbursementDate, St Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId()); assertThat(statusActual)// - .as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))// + .as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))// .isEqualTo(statusExpected);// eventCheckHelper.disburseLoanEventCheck(loanId); eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); @@ -1773,6 +1777,7 @@ public void loanTransactionsGivenTransactionNotReverted(String transactionType, public void loanChargesGivenChargeDataCheck(DataTable table) throws IOException { Response loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanCreateResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); @@ -1786,14 +1791,15 @@ public void loanChargesGivenChargeDataCheck(DataTable table) throws IOException boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues)); - assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInChargesTab(1, actualValuesList, expectedValues)) - .isTrue(); + assertThat(containsExpectedValues) + .as(ErrorMessageHelper.wrongValueInLineInChargesTab(resourceId, 1, actualValuesList, expectedValues)).isTrue(); } @Then("Loan Charges tab has the following data:") public void loanChargesTabCheck(DataTable table) throws IOException { Response loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanCreateResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); @@ -1808,8 +1814,8 @@ public void loanChargesTabCheck(DataTable table) throws IOException { boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues)); - assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInChargesTab(i, actualValuesList, expectedValues)) - .isTrue(); + assertThat(containsExpectedValues) + .as(ErrorMessageHelper.wrongValueInLineInChargesTab(resourceId, i, actualValuesList, expectedValues)).isTrue(); } } @@ -1846,6 +1852,7 @@ private List> getActualValuesList(List loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanCreateResponse.body().getLoanId(); + String resourceId = String.valueOf(loanId); Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); @@ -1855,7 +1862,7 @@ public void loanStatus(String statusExpected) throws IOException { LoanStatus loanStatusExpected = LoanStatus.valueOf(statusExpected); Integer loanStatusExpectedValue = loanStatusExpected.getValue(); - assertThat(loanStatusActualValue).as(ErrorMessageHelper.wrongLoanStatus(loanStatusActualValue, loanStatusExpectedValue)) + assertThat(loanStatusActualValue).as(ErrorMessageHelper.wrongLoanStatus(resourceId, loanStatusActualValue, loanStatusExpectedValue)) .isEqualTo(loanStatusExpectedValue); } @@ -2776,7 +2783,9 @@ private void performLoanDisbursementAndVerifyStatus(final long loanId, final Pos assertNotNull(loanDetails.body().getStatus()); final Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId()); - assertThat(statusActual).as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected))) + String resourceId = String.valueOf(loanId); + assertThat(statusActual) + .as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected))) .isEqualTo(statusExpected); eventCheckHelper.disburseLoanEventCheck(loanId); eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature index ece05f178e9..3966682d899 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature @@ -5231,6 +5231,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 22 January 2021 | Accrual | 5.7 | 0.0 | 5.7 | 0.0 | 0.0 | 0.0 | false | false | | 22 January 2021 | Merchant Issued Refund | 1000.0 | 914.37 | 5.42 | 0.0 | 0.0 | 0.0 | false | true | | 22 January 2021 | Interest Refund | 5.42 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 22 January 2021 | Accrual Adjustment | 0.28 | 0.0 | 0.28 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C3302 Scenario: UC18-2 - In case of repayment reversal the Interest Refund transaction needs to be recalculated @@ -5288,6 +5289,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 22 January 2021 | Merchant Issued Refund | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | | 22 January 2021 | Interest Refund | 5.7 | 0.0 | 5.7 | 0.0 | 0.0 | 0.0 | false | true | | 23 January 2021 | Credit Balance Refund | 85.63 | 85.63 | 0.0 | 0.0 | 0.0 | 85.63 | false | true | + | 22 January 2021 | Accrual | 5.42 | 0.0 | 5.42 | 0.0 | 0.0 | 0.0 | false | false | And In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted @TestRailId:C3303 diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature index 5bf616d7d8b..c16a4f24c47 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature @@ -1331,10 +1331,8 @@ Feature: LoanAccrualActivity Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | - | 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | | 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false | - | 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false | + | 04 January 2024 | Accrual | 4.1 | 0.0 | 4.1 | 0.0 | 0.0 | 0.0 | false | false | # --- Accrual activity --- When Admin sets the business date to "07 January 2024" When Admin runs inline COB job for Loan @@ -1351,11 +1349,9 @@ Feature: LoanAccrualActivity Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | - | 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | + | 04 January 2024 | Accrual | 4.1 | 0.0 | 4.1 | 0.0 | 0.0 | 0.0 | false | false | | 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false | - | 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false | - | 05 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | + | 05 January 2024 | Accrual Adjustment | 2.79 | 0.0 | 2.79 | 0.0 | 0.0 | 0.0 | false | false | | 06 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | | 06 January 2024 | Accrual Activity | 1.64 | 0.0 | 1.64 | 0.0 | 0.0 | 0.0 | false | false | @@ -1481,9 +1477,8 @@ Feature: LoanAccrualActivity | 05 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | | 06 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false | | 06 January 2024 | Accrual Activity | 1.64 | 0.0 | 1.64 | 0.0 | 0.0 | 0.0 | false | false | - | 07 January 2024 | Accrual | 0.25 | 0.0 | 0.25 | 0.0 | 0.0 | 0.0 | false | false | | 08 January 2024 | Repayment | 753.07 | 749.4 | 3.67 | 0.0 | 0.0 | 250.6 | false | true | - | 08 January 2024 | Accrual | 0.24 | 0.0 | 0.24 | 0.0 | 0.0 | 0.0 | false | false | + | 08 January 2024 | Accrual | 2.46 | 0.0 | 2.46 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C3182 Scenario: Verify accrual activity posting job @@ -2045,9 +2040,9 @@ Feature: LoanAccrualActivity Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 22 April 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | false | + | 22 April 2024 | Accrual | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 0.0 | false | false | | 22 April 2024 | Repayment | 600.0 | 400.0 | 0.0 | 0.0 | 30.0 | 0.0 | true | true | | 22 April 2024 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true | - | 09 October 2024 | Accrual | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 0.0 | false | false | | 10 October 2024 | Charge Adjustment | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true | @TestRailId:C3274 diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeback.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeback.feature index ee224ea57ae..48827bb9b2c 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeback.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeback.feature @@ -2959,6 +2959,7 @@ Feature: LoanChargeback | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 15 April 2024 | Chargeback | 7.0 | 2.0 | 0.0 | 0.0 | 5.0 | 0.0 | 7.0 | | 16 April 2024 | Chargeback | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | 4.0 | 3.0 | Then Loan Transactions tab has a "CHARGEBACK" transaction with date "16 April 2024" which has the following Journal entries: @@ -3032,6 +3033,7 @@ Feature: LoanChargeback | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries: | Type | Account code | Account name | Debit | Credit | @@ -3065,6 +3067,7 @@ Feature: LoanChargeback | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | | 05 March 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 | Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries: @@ -3154,8 +3157,8 @@ Feature: LoanChargeback | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | - | 15 April 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries: | Type | Account code | Account name | Debit | Credit | | ASSET | 112601 | Loans Receivable | 100.0 | | @@ -3165,7 +3168,7 @@ Feature: LoanChargeback | ASSET | 112601 | Loans Receivable | | 100.0 | | ASSET | 112603 | Interest/Fee Receivable | | 5.0 | | LIABILITY | 145023 | Suspense/Clearing account | 105.0 | | - Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries: + Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries: | Type | Account code | Account name | Debit | Credit | | ASSET | 112603 | Interest/Fee Receivable | 5.0 | | | INCOME | 404007 | Fee Income | | 5.0 | @@ -3193,8 +3196,8 @@ Feature: LoanChargeback | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | - | 15 April 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 | Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries: | Type | Account code | Account name | Debit | Credit | @@ -3205,7 +3208,7 @@ Feature: LoanChargeback | ASSET | 112601 | Loans Receivable | | 100.0 | | ASSET | 112603 | Interest/Fee Receivable | | 5.0 | | LIABILITY | 145023 | Suspense/Clearing account | 105.0 | | - Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries: + Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries: | Type | Account code | Account name | Debit | Credit | | ASSET | 112603 | Interest/Fee Receivable | 5.0 | | | INCOME | 404007 | Fee Income | | 5.0 | @@ -3354,6 +3357,7 @@ Feature: LoanChargeback | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | | 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 | | 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 | + | 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 | | 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 3.0 | | 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 3.0 | 7.0 | Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries: diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature index 4e206c9a8ab..aebca854c83 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -2473,8 +2473,8 @@ Feature: LoanRepayment Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 | + | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | Then Loan Charges tab has the following data: | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | | NSF fee | true | Specified due date | 28 January 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | @@ -2486,8 +2486,8 @@ Feature: LoanRepayment Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 | + | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 28 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 0.0 | 20.0 | 500.0 | Then Loan Charges tab has the following data: | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | @@ -2507,8 +2507,8 @@ Feature: LoanRepayment Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 | + | 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 28 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 0.0 | 20.0 | 500.0 | | 30 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 20.0 | 0.0 | 0.0 | | 30 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | @@ -3656,9 +3656,9 @@ 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 | + | 24 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 | | | | @@ -3676,9 +3676,9 @@ 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 | + | 24 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.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 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualChargeData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualChargeData.java index d3109bd7ef5..447131c051d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualChargeData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualChargeData.java @@ -34,4 +34,5 @@ public class AccrualChargeData { private Money chargeAmount; private Money chargeAccruable; private Money chargeAccrued; + private Money transactionAccrued; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualPeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualPeriodData.java index 7c39384ebb7..30252632ad1 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualPeriodData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccrualPeriodData.java @@ -68,10 +68,20 @@ public Money getFeeAccrued() { return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus); } + public Money getFeeTransactionAccrued() { + return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getTransactionAccrued).reduce(null, + MathUtil::plus); + } + public Money getPenaltyAccrued() { return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus); } + public Money getPenaltyTransactionAccrued() { + return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getTransactionAccrued).reduce(null, + MathUtil::plus); + } + public Money getChargeAccruable() { return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java deleted file mode 100644 index 21abaa1531d..00000000000 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanScheduleAccrualData.java +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.loanaccount.data; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.Map; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; - -public class LoanScheduleAccrualData { - - private final Long loanId; - private final Long officeId; - private final LocalDate accruedTill; - private final PeriodFrequencyType repaymentFrequency; - private final Integer repayEvery; - private final Integer installmentNumber; - private final LocalDate dueDate; - private final LocalDate fromDate; - private final Long repaymentScheduleId; - private final Long loanProductId; - private final BigDecimal interestIncome; - private final BigDecimal feeIncome; - private final BigDecimal penaltyIncome; - private final BigDecimal waivedInterestIncome; - private final BigDecimal accruedInterestIncome; - private final BigDecimal accruedFeeIncome; - private final BigDecimal accruedPenaltyIncome; - private final CurrencyData currencyData; - private final LocalDate interestCalculatedFrom; - - private Map applicableCharges; - private BigDecimal dueDateFeeIncome; - private BigDecimal dueDatePenaltyIncome; - private BigDecimal accruableIncome; - - private BigDecimal creditedFee; - private BigDecimal creditedPenalty; - - public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill, - final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate, - final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome, - final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome, - final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom, - final BigDecimal waivedInterestIncome, BigDecimal creditedFee, BigDecimal creditedPenalty) { - this.loanId = loanId; - this.installmentNumber = installmentNumber; - this.officeId = officeId; - this.accruedTill = accruedTill; - this.dueDate = dueDate; - this.fromDate = fromDate; - this.repaymentScheduleId = repaymentScheduleId; - this.loanProductId = loanProductId; - this.interestIncome = interestIncome; - this.feeIncome = feeIncome; - this.penaltyIncome = penaltyIncome; - this.accruedFeeIncome = accruedFeeIncome; - this.accruedInterestIncome = accruedInterestIncome; - this.accruedPenaltyIncome = accruedPenaltyIncome; - this.currencyData = currencyData; - this.repaymentFrequency = repaymentFrequency; - this.repayEvery = repayEvery; - this.interestCalculatedFrom = interestCalculatedFrom; - this.waivedInterestIncome = waivedInterestIncome; - this.creditedFee = creditedFee; - this.creditedPenalty = creditedPenalty; - } - - public Long getLoanId() { - return this.loanId; - } - - public Long getOfficeId() { - return this.officeId; - } - - public LocalDate getDueDate() { - return this.dueDate; - } - - public LocalDate getDueDateAsLocaldate() { - return this.dueDate; - } - - public Long getRepaymentScheduleId() { - return this.repaymentScheduleId; - } - - public Long getLoanProductId() { - return this.loanProductId; - } - - public BigDecimal getInterestIncome() { - return this.interestIncome; - } - - public BigDecimal getFeeIncome() { - return this.feeIncome; - } - - public BigDecimal getPenaltyIncome() { - return this.penaltyIncome; - } - - public BigDecimal getAccruedInterestIncome() { - return this.accruedInterestIncome; - } - - public BigDecimal getAccruedFeeIncome() { - return this.accruedFeeIncome; - } - - public BigDecimal getAccruedPenaltyIncome() { - return this.accruedPenaltyIncome; - } - - public CurrencyData getCurrencyData() { - return this.currencyData; - } - - public LocalDate getAccruedTill() { - return this.accruedTill; - } - - public LocalDate getFromDateAsLocaldate() { - return this.fromDate; - } - - public PeriodFrequencyType getRepaymentFrequency() { - return this.repaymentFrequency; - } - - public Integer getRepayEvery() { - return this.repayEvery; - } - - public LocalDate getInterestCalculatedFrom() { - return this.interestCalculatedFrom; - } - - public Integer getInstallmentNumber() { - return this.installmentNumber; - } - - public Map getApplicableCharges() { - return this.applicableCharges; - } - - public BigDecimal getDueDateFeeIncome() { - return this.dueDateFeeIncome; - } - - public BigDecimal getDueDatePenaltyIncome() { - return this.dueDatePenaltyIncome; - } - - public void updateChargeDetails(final Map applicableCharges, final BigDecimal dueDateFeeIncome, - final BigDecimal dueDatePenaltyIncome) { - this.applicableCharges = applicableCharges; - this.dueDateFeeIncome = dueDateFeeIncome; - this.dueDatePenaltyIncome = dueDatePenaltyIncome; - } - - public BigDecimal getWaivedInterestIncome() { - return this.waivedInterestIncome; - } - - public BigDecimal getAccruableIncome() { - return this.accruableIncome; - } - - public void updateAccruableIncome(BigDecimal accruableIncome) { - this.accruableIncome = accruableIncome; - } - - public BigDecimal getCreditedFee() { - return this.creditedFee; - } - - public BigDecimal getCreditedPenalty() { - return this.creditedPenalty; - } - -} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 280693da5fe..27bb50d75a7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE; + import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -740,6 +742,9 @@ public ChangedTransactionDetail reprocessTransactionsWithPostTransactionChecks(L * is lesser than todays date. If not, the transaction date is set to todays date */ public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { + if (isProgressiveSchedule()) { + return null; + } final Money chargeAmount = loanCharge.getAmount(getCurrency()); Money feeCharges = chargeAmount; Money penaltyCharges = Money.zero(getCurrency()); @@ -1053,7 +1058,7 @@ public void updateLoanCharges(final Set loanCharges) { LoanCharge charge = loanCharge; // add new charges if (loanCharge.getId() == null) { - LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge; loanCharge.update(this); if (this.loanProduct.isMultiDisburseLoan() && loanCharge.isTrancheDisbursementCharge()) { loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this); @@ -3563,4 +3568,8 @@ public void setIsTopup(boolean topup) { public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() { return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode); } + + public boolean isProgressiveSchedule() { + return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE; + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java index 31520704b5f..6f7f6775016 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.transaction.annotation.Transactional; @@ -25,13 +26,13 @@ public interface LoanAccrualActivityProcessingService { @Transactional - void makeAccrualActivityTransaction(Long loanId, LocalDate currentDate); + void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate); - Loan makeAccrualActivityTransaction(Loan loan, LocalDate currentDate); + void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate); @Transactional - void processAccrualActivityForLoanClosure(Loan loan); + void processAccrualActivityForLoanClosure(@NotNull Loan loan); @Transactional - void processAccrualActivityForLoanReopen(Loan loan); + void processAccrualActivityForLoanReopen(@NotNull Loan loan); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java index 0a0c8499797..bf96b21a6d5 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualTransactionBusinessEventServiceImpl.java @@ -20,7 +20,9 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -33,8 +35,12 @@ public class LoanAccrualTransactionBusinessEventServiceImpl implements LoanAccru @Override public void raiseBusinessEventForAccrualTransactions(Loan loan, List existingTransactionIds) { for (final LoanTransaction transaction : loan.getLoanTransactions()) { - if (transaction.isNotReversed() && transaction.isAccrual() && !existingTransactionIds.contains(transaction.getId())) { - businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(transaction)); + if (transaction.isNotReversed() && (transaction.isAccrual() || transaction.isAccrualAdjustment()) + && !existingTransactionIds.contains(transaction.getId())) { + LoanTransactionBusinessEvent businessEvent = transaction.isAccrual() + ? new LoanAccrualTransactionCreatedBusinessEvent(transaction) + : new LoanAccrualAdjustmentTransactionBusinessEvent(transaction); + businessEventNotifierService.notifyPostBusinessEvent(businessEvent); } } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java index 91addc4f69c..297774a04f2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java @@ -35,16 +35,14 @@ public interface LoanAccrualsProcessingService { void reprocessExistingAccruals(@NotNull Loan loan); - void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled); + void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal); - void processIncomePostingAndAccruals(@NotNull Loan loan); + void addIncomePostingAndAccruals(Long loanId) throws Exception; - void addIncomeAndAccrualTransactions(Long loanId) throws Exception; + void processIncomePostingAndAccruals(@NotNull Loan loan); void processAccrualsOnLoanClosure(@NotNull Loan loan); - void processAccrualsOnLoanReopen(@NotNull Loan loan); - void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate, @NotNull List newAccrualTransactions); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java index 97bd23535f9..f64f2d71ae7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -30,7 +30,6 @@ import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.springframework.transaction.annotation.Transactional; @@ -53,16 +52,6 @@ CommandProcessingResult makeLoanRepayment(LoanTransactionType repaymentTransacti CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId, JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType); - @Transactional - Loan reverseReplayAccrualActivityTransaction(Loan loan, LoanTransaction loanTransaction, LoanRepaymentScheduleInstallment installment, - LocalDate transactionDate); - - @Transactional - Loan makeAccrualActivityTransaction(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate transactionDate); - - @Transactional - Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction accrualActivityTransaction); - @Transactional CommandProcessingResult makeInterestPaymentWaiver(JsonCommand command); 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 6452ced33d6..a1a25ee0b99 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 @@ -293,10 +293,8 @@ public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransact this.noteRepository.save(note); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); setLoanDelinquencyTag(loan, transactionDate); @@ -306,6 +304,10 @@ public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransact businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent); } + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + // disable all active standing orders linked to this loan if status // changes to closed disableStandingInstructionsLinkedToClosedLoan(loan); @@ -492,13 +494,13 @@ public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, f this.noteRepository.save(note); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newPaymentTransaction)); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); return newPaymentTransaction; } @@ -726,13 +728,15 @@ public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate tran this.noteRepository.save(note); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService .notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction)); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return newCreditBalanceRefundTransaction; } @@ -778,13 +782,14 @@ public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessing this.noteRepository.save(note); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction)); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + builderResult.withEntityId(newRefundTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()) .withGroupId(loan.getGroupId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/addperiodicaccrualentriesforloanswithincomepostedastransactions/AddPeriodicAccrualEntriesForLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/addperiodicaccrualentriesforloanswithincomepostedastransactions/AddPeriodicAccrualEntriesForLoansTasklet.java index d7f18f226f1..68d3e5d799d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/addperiodicaccrualentriesforloanswithincomepostedastransactions/AddPeriodicAccrualEntriesForLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/addperiodicaccrualentriesforloanswithincomepostedastransactions/AddPeriodicAccrualEntriesForLoansTasklet.java @@ -46,7 +46,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon List errors = new ArrayList<>(); for (Long loanId : loanIds) { try { - loanAccrualsProcessingService.addIncomeAndAccrualTransactions(loanId); + loanAccrualsProcessingService.addIncomePostingAndAccruals(loanId); } catch (Exception e) { log.error("Failed to add income and accrual transaction for loan {}", loanId, e); errors.add(e); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 0d14d584188..e1df63373e4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -466,13 +466,13 @@ public CommandProcessingResult approve(JsonCommand jsonCommand) { replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); - // update the loan object - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, true); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false); businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan)); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId) .withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).withClientId(loan.getClientId()) .withOfficeId(loan.getOfficeId()).withGroupId(loan.getGroupId()).build(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java index 36d06724334..2f237396696 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java @@ -18,24 +18,29 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; -import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -46,131 +51,179 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActivityProcessingService { private final LoanRepositoryWrapper loanRepositoryWrapper; - private final LoanWritePlatformService loanWritePlatformService; private final ExternalIdFactory externalIdFactory; private final BusinessEventNotifierService businessEventNotifierService; - private final LoanChargeValidator loanChargeValidator; + private final LoanTransactionAssembler loanTransactionAssembler; + private final LoanAccountDomainService loanAccountDomainService; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) - public void makeAccrualActivityTransaction(Long loanId, final LocalDate currentDate) { + public void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate) { Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); makeAccrualActivityTransaction(loan, currentDate); } @Override - public Loan makeAccrualActivityTransaction(Loan loan, final LocalDate currentDate) { - if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { - // check if loan has installment due on business day - Optional first = loan.getRepaymentScheduleInstallments().stream() - .filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate)) - .findFirst(); - if (first.isPresent()) { - final LoanRepaymentScheduleInstallment installment = first.get(); - // check if there is no not-replayed-accrual-activity related to business date - List loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed() - && loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate)); - if (loanTransactions.isEmpty()) { - loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate); - } + public void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate) { + if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + return; + } + // check if loan has installment in the past or due on current date + List installments = loan + .getRepaymentScheduleInstallments(i -> !i.isDownPayment() && !DateUtils.isBefore(currentDate, i.getDueDate())); + for (LoanRepaymentScheduleInstallment installment : installments) { + LocalDate dueDate = installment.getDueDate(); + // check if there is any not-replayed-accrual-activity related to business date + ArrayList existingActivities = new ArrayList<>( + loan.getLoanTransactions(t -> t.isNotReversed() && t.isAccrualActivity() && t.getTransactionDate().isEqual(dueDate))); + boolean hasExisting = !existingActivities.isEmpty(); + LoanTransaction existingActivity = hasExisting ? existingActivities.get(0) : null; + makeOrReplayActivity(loan, installment, existingActivity); + if (hasExisting) { + existingActivities.remove(existingActivity); + existingActivities.forEach(this::reverseAccrualActivityTransaction); } } - return loan; } @Override @Transactional - public void processAccrualActivityForLoanClosure(Loan loan) { - LocalDate date = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate(); - List accrualActivityTransaction = loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed) - .filter(LoanTransaction::isAccrualActivity).filter(loanTransaction -> loanTransaction.getDateOf().isAfter(date)).toList(); - if (!accrualActivityTransaction.isEmpty()) { - accrualActivityTransaction.forEach(this::reverseAccrualActivityTransaction); - } - LoanTransaction loanTransaction = assembleClosingAccrualActivityTransaction(loan, date); - if (loanTransaction.getAmount().compareTo(BigDecimal.ZERO) != 0) { - loanWritePlatformService.makeAccrualActivityTransaction(loan, loanTransaction); + public void processAccrualActivityForLoanClosure(@NotNull Loan loan) { + if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + return; } - } - - private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, "reversed"); - loanTransaction.reverse(); - LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); - businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); - } + LocalDate date = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate(); + // reverse after closure activities + loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(date)) + .forEach(this::reverseAccrualActivityTransaction); - private LoanTransaction assembleClosingAccrualActivityTransaction(Loan loan, LocalDate date) { - // collect fees + // calculate activity amounts BigDecimal feeChargesPortion = BigDecimal.ZERO; - // collect penalties BigDecimal penaltyChargesPortion = BigDecimal.ZERO; - // collect interests BigDecimal interestPortion = BigDecimal.ZERO; - var currency = loan.getCurrency(); - // sum up all accruals + // collect installment amounts for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { - feeChargesPortion = installment.getFeeAccrued(currency).getAmount().add(feeChargesPortion); - penaltyChargesPortion = installment.getPenaltyAccrued(currency).getAmount().add(penaltyChargesPortion); - interestPortion = installment.getInterestAccrued(currency).getAmount().add(interestPortion); + feeChargesPortion = MathUtil.add(feeChargesPortion, installment.getFeeChargesCharged()); + penaltyChargesPortion = MathUtil.add(penaltyChargesPortion, installment.getPenaltyCharges()); + interestPortion = MathUtil.add(interestPortion, installment.getInterestCharged()); } List accrualActivities = loan.getLoanTransactions().stream().filter(LoanTransaction::isAccrualActivity) .filter(LoanTransaction::isNotReversed).toList(); - // subtract already Posted accruals + // subtract already posted activities for (LoanTransaction accrualActivity : accrualActivities) { - if (accrualActivity.getFeeChargesPortion() != null) { - feeChargesPortion = feeChargesPortion.subtract(accrualActivity.getFeeChargesPortion()); - } - if (accrualActivity.getPenaltyChargesPortion() != null) { - penaltyChargesPortion = penaltyChargesPortion.subtract(accrualActivity.getPenaltyChargesPortion()); - } - if (accrualActivity.getInterestPortion() != null) { - interestPortion = interestPortion.subtract(accrualActivity.getInterestPortion()); - } + feeChargesPortion = MathUtil.subtract(feeChargesPortion, accrualActivity.getFeeChargesPortion()); + penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, accrualActivity.getPenaltyChargesPortion()); + interestPortion = MathUtil.subtract(interestPortion, accrualActivity.getInterestPortion()); } - - BigDecimal transactionAmount = feeChargesPortion.add(penaltyChargesPortion).add(interestPortion); - ExternalId externalId = externalIdFactory.create(); - - return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date, transactionAmount, null, - interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId); + BigDecimal transactionAmount = MathUtil.add(feeChargesPortion, penaltyChargesPortion, interestPortion); + if (!MathUtil.isGreaterThanZero(transactionAmount)) { + return; + } + if (MathUtil.isLessThanZero(feeChargesPortion) || MathUtil.isLessThanZero(penaltyChargesPortion) + || MathUtil.isLessThanZero(interestPortion)) { + // TODO reverse latest accrual activity if any amount is negative + return; + } + LoanTransaction newActivity = new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date, + transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, + externalIdFactory.create()); + makeAccrualActivityTransaction(loan, newActivity); } @Override @Transactional - public void processAccrualActivityForLoanReopen(Loan loan) { - LoanTransaction lastAccrualActivityMarkedToReverse = null; - List accrualActivityTransaction = loan.getLoanTransactions().stream() - .filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity()) - .sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList(); + public void processAccrualActivityForLoanReopen(@NotNull Loan loan) { + if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + return; + } // grab the latest AccrualActivityTransaction // it does not matter if it is on an installment due date or not because it was posted due to loan close - if (!accrualActivityTransaction.isEmpty()) { - lastAccrualActivityMarkedToReverse = accrualActivityTransaction.get(accrualActivityTransaction.size() - 1); - } + LoanTransaction lastAccrualActivityMarkedToReverse = loan.getLoanTransactions().stream() + .filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity()) + .sorted(Comparator.comparing(LoanTransaction::getDateOf)).reduce((first, second) -> second).orElse(null); final LocalDate lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse == null ? null : lastAccrualActivityMarkedToReverse.getDateOf(); LocalDate today = DateUtils.getBusinessLocalDate(); final List installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate = loan .getRepaymentScheduleInstallments().stream() - .filter(installment -> installment.getDueDate().isBefore(today) && (lastAccrualActivityTransactionDate == null - || installment.getDueDate().isAfter(lastAccrualActivityTransactionDate) - // if close event happened on installment due date - // we should reverse replay it to calculate installment related accrual parts only - || installment.getDueDate().isEqual(lastAccrualActivityTransactionDate))) + .filter(installment -> installment.getDueDate().isBefore(today) + && (DateUtils.isAfter(installment.getDueDate(), lastAccrualActivityTransactionDate) + // if close event happened on installment due date + // we should reverse replay it to calculate installment related accrual parts only + || installment.getDueDate().isEqual(lastAccrualActivityTransactionDate))) .sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList(); for (LoanRepaymentScheduleInstallment installment : installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate) { - if (lastAccrualActivityMarkedToReverse != null) { - loanWritePlatformService.reverseReplayAccrualActivityTransaction(loan, lastAccrualActivityMarkedToReverse, installment, - installment.getDueDate()); - lastAccrualActivityMarkedToReverse = null; - } else { - loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, installment.getDueDate()); - } + makeOrReplayActivity(loan, installment, lastAccrualActivityMarkedToReverse); + lastAccrualActivityMarkedToReverse = null; } if (lastAccrualActivityMarkedToReverse != null) { reverseAccrualActivityTransaction(lastAccrualActivityMarkedToReverse); } } + private void makeOrReplayActivity(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment, + LoanTransaction existingActivity) { + LocalDate dueDate = installment.getDueDate(); + if (existingActivity == null) { + makeAccrualActivityTransaction(loan, installment, dueDate); + } else { + reverseReplayAccrualActivityTransaction(loan, existingActivity, installment, dueDate); + } + } + + private LoanTransaction reverseReplayAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction loanTransaction, + @NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate transactionDate) { + if (validateActivityTransaction(installment, loanTransaction)) { + return loanTransaction; + } + + LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, + transactionDate); + if (newLoanTransaction != null) { + newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations()); + newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction, + loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED)); + loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction); + loan.addLoanTransaction(newLoanTransaction); + + LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); + data.setNewTransactionDetail(newLoanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + } + reverseAccrualActivityTransaction(loanTransaction); + return newLoanTransaction; + } + + private boolean validateActivityTransaction(@NotNull LoanRepaymentScheduleInstallment installment, + @NotNull LoanTransaction transaction) { + return DateUtils.isEqual(installment.getDueDate(), transaction.getDateOf()) + && MathUtil.isEqualTo(transaction.getInterestPortion(), installment.getInterestCharged()) + && MathUtil.isEqualTo(transaction.getFeeChargesPortion(), installment.getFeeChargesCharged()) + && MathUtil.isEqualTo(transaction.getPenaltyChargesPortion(), installment.getPenaltyCharges()); + } + + private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) { + loanTransaction.reverse(); + loanTransaction.updateExternalId(null); + LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + } + + private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment, + @NotNull LocalDate transactionDate) { + LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, + transactionDate); + return newAccrualActivityTransaction == null ? null : makeAccrualActivityTransaction(loan, newAccrualActivityTransaction); + } + + private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction newAccrualActivityTransaction) { + businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); + newAccrualActivityTransaction = loanAccountDomainService + .saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); + + loan.addLoanTransaction(newAccrualActivityTransaction); + businessEventNotifierService + .notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction)); + return newAccrualActivityTransaction; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java new file mode 100644 index 00000000000..9697a56d08d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.event.business.BusinessEventListener; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; + +@Slf4j +@RequiredArgsConstructor +public class LoanAccrualEventService { + + private final BusinessEventNotifierService businessEventNotifierService; + private final LoanAccrualsProcessingService loanAccrualsProcessingService; + private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService; + + @PostConstruct + public void addListeners() { + businessEventNotifierService.addPostBusinessEventListener(LoanCloseBusinessEvent.class, new LoanCloseListener()); + businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class, new LoanBalanceChangedListener()); + } + + private final class LoanCloseListener implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanCloseBusinessEvent event) { + final Loan loan = event.get(); + LoanStatus status = loan.getStatus(); + if (status.isClosedObligationsMet() || status.isOverpaid()) { + log.debug("Loan closure on accrual for loan {}", loan.getId()); + loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan); + loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); + } + } + } + + private final class LoanBalanceChangedListener implements BusinessEventListener { + + @Override + public void onBusinessEvent(LoanBalanceChangedBusinessEvent event) { + final Loan loan = event.get(); + LoanStatus status = loan.getStatus(); + if (status.isClosedObligationsMet() || status.isOverpaid()) { + log.debug("Loan balance change on accrual for loan {}", loan.getId()); + loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan); + loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); + } + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index bb867e29970..55d5de5b70f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -56,14 +56,12 @@ import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.office.domain.OfficeRepository; import org.apache.fineract.portfolio.loanaccount.data.AccrualChargeData; import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodData; import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodsData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; -import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; @@ -77,8 +75,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; -import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.springframework.stereotype.Component; @@ -102,9 +98,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing private final LoanTransactionRepository loanTransactionRepository; private final LoanScheduleGeneratorFactory loanScheduleFactory; private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; - private final OfficeRepository officeRepository; - private final LoanChargeRepository loanChargeRepository; - private final LoanChargeValidator loanChargeValidator; /** * method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan @@ -167,9 +160,6 @@ public void addAccruals(@NotNull LocalDate tillDate) throws JobExecutionExceptio */ @Override public void reprocessExistingAccruals(@NotNull Loan loan) { - if (isProgressiveAccrual(loan)) { - return; - } List accrualTransactions = retrieveListOfAccrualTransactions(loan); if (!accrualTransactions.isEmpty()) { if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { @@ -185,7 +175,7 @@ public void reprocessExistingAccruals(@NotNull Loan loan) { */ @Override @Transactional - public void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled) { + public void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal) { if (isProgressiveAccrual(loan)) { return; } @@ -194,13 +184,31 @@ public void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean return; } try { - addPeriodicAccruals(accruedTill, loan); + addAccruals(loan, accruedTill, true, false, addJournal); } catch (Exception e) { String globalisationMessageCode = "error.msg.accrual.exception"; throw new GeneralPlatformDomainRuleException(globalisationMessageCode, e.getMessage(), e); } } + @Transactional + @Override + public void addIncomePostingAndAccruals(Long loanId) throws LoanNotFoundException { + if (loanId == null) { + return; + } + Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); + if (isProgressiveAccrual(loan)) { + return; + } + final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); + processIncomePostingAndAccruals(loan); + this.loanRepositoryWrapper.saveAndFlush(loan); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + } + /** * method calculates accruals for loan with interest recalculation and compounding to be posted as income */ @@ -228,26 +236,8 @@ public void processIncomePostingAndAccruals(@NotNull Loan loan) { } List installments = loan.getRepaymentScheduleInstallments(); LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment.getLastNonDownPaymentInstallment(installments); - reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate()); - reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate()); - } - - @Transactional - @Override - public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException { - if (loanId == null) { - return; - } - Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); - if (isProgressiveAccrual(loan)) { - return; - } - final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); - final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); - processIncomePostingAndAccruals(loan); - this.loanRepositoryWrapper.saveAndFlush(loan); - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate(), false); + reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate(), false); } /** @@ -264,32 +254,6 @@ public void processAccrualsOnLoanClosure(@NotNull Loan loan) { processIncomeAndAccrualTransactionOnLoanClosure(loan); } - @Override - public void processAccrualsOnLoanReopen(@NotNull Loan loan) { - if (isProgressiveAccrual(loan)) { - List accruals = retrieveListOfAccrualTransactions(loan); - LocalDate accruedTill = loan.getAccruedTill(); - // if accruedTill is null, the all the accrual transactions will be reversed here - if (reverseTransactionsAfter(accruals, accruedTill)) { - return; - } - LoanTransaction closureAccrual = accruals.stream() - .filter(t -> !t.isReversed() && t.isAccrual() && DateUtils.isEqual(t.getTransactionDate(), accruedTill)) - .sorted(LoanTransactionComparator.INSTANCE).reduce((first, second) -> second).orElse(null); - if (closureAccrual == null) { - return; - } - int firstInstallmentNumber = fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments()); - LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment( - i -> isInPeriod(closureAccrual.getDateOf(), i, i.getInstallmentNumber().equals(firstInstallmentNumber))); - if (MathUtil.isGreaterThan(closureAccrual.getInterestPortion(), installment.getInterestCharged()) - || MathUtil.isGreaterThan(closureAccrual.getFeeChargesPortion(), installment.getFeeChargesCharged()) - || MathUtil.isGreaterThan(closureAccrual.getPenaltyChargesPortion(), installment.getPenaltyCharges())) { - reverseAccrual(closureAccrual); - } - } - } - /** * method calculates accruals for loan on loan fore closure */ @@ -301,7 +265,7 @@ public void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalD && (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) { final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate); MonetaryCurrency currency = loan.getCurrency(); - reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate); + reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false); HashMap incomeDetails = new HashMap<>(); @@ -331,20 +295,29 @@ private void addAccruals(@NotNull Loan loan, @NotNull LocalDate tillDate, boolea return; } List existingAccruals = retrieveListOfAccrualTransactions(loan); - reverseTransactionsAfter(existingAccruals, loan.getLastLoanRepaymentScheduleInstallment().getDueDate()); + LocalDate lastDueDate = loan.getLastLoanRepaymentScheduleInstallment().getDueDate(); + reverseTransactionsAfter(existingAccruals, lastDueDate, addJournal); ensureAccrualTransactionMappings(loan); + if (DateUtils.isAfter(tillDate, lastDueDate)) { + tillDate = lastDueDate; + } boolean progressiveAccrual = isProgressiveAccrual(loan); LocalDate accruedTill = loan.getAccruedTill(); - if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill) - && existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), tillDate))) { - return; + LocalDate businessDate = DateUtils.getBusinessLocalDate(); + LocalDate accrualDate = isFinal + ? (progressiveAccrual ? (DateUtils.isBefore(lastDueDate, businessDate) ? lastDueDate : businessDate) + : getFinalAccrualTransactionDate(loan)) + : tillDate; + if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)) { + if (isFinal) { + reverseTransactionsAfter(existingAccruals, accrualDate, addJournal); + } else if (existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), accrualDate))) { + return; + } } AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic); - - LocalDate accrualDate = isFinal ? (progressiveAccrual ? DateUtils.getBusinessLocalDate() : getFinalAccrualTransactionDate(loan)) - : tillDate; boolean mergeTransactions = isFinal || progressiveAccrual; MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency(); List accrualTransactions = new ArrayList<>(); @@ -399,17 +372,17 @@ private void addAccruals(@NotNull Loan loan, @NotNull LocalDate tillDate, boolea return; } - if (!isFinal) { - loan.setAccruedTill(tillDate); + if (!isFinal || progressiveAccrual) { + loan.setAccruedTill(isFinal ? accrualDate : tillDate); } ArrayList> newTransactionMapping = new ArrayList<>(); for (LoanTransaction accrualTransaction : accrualTransactions) { accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction); - LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual() - ? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction) - : new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction); - businessEventNotifierService.notifyPostBusinessEvent(businessEvent); if (addJournal) { + LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual() + ? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction) + : new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction); + businessEventNotifierService.notifyPostBusinessEvent(businessEvent); newTransactionMapping.add(accrualTransaction.toMapData(currency.getCode())); } } @@ -584,7 +557,9 @@ private void addChargeAccrual(@NotNull LoanCharge loanCharge, @NotNull LocalDate Money unrecognizedWaived = MathUtil.toMoney(calcChargeUnrecognizedWaivedAmount(paidBys, tillDate), currency); Money transactionWaived = MathUtil.minusToZero(waived, unrecognizedWaived); - chargeData.setChargeAccrued(MathUtil.minusToZero(MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency), transactionWaived)); + Money transactionAccrued = MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency); + chargeData.setTransactionAccrued(transactionAccrued); + chargeData.setChargeAccrued(MathUtil.minusToZero(transactionAccrued, transactionWaived)); duePeriod.addCharge(chargeData); } @@ -720,17 +695,29 @@ private void reprocessPeriodicAccruals(Loan loan, final List ac return; } ensureAccrualTransactionMappings(loan); - List installments = loan.getRepaymentScheduleInstallments(); - boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate(); - for (LoanRepaymentScheduleInstallment installment : installments) { - checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment); + LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment(); + LocalDate lastDueDate = lastInstallment.getDueDate(); + if (isProgressiveAccrual(loan)) { + AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, lastDueDate, true); + for (AccrualPeriodData period : accrualPeriods.getPeriods()) { + Money interestAccrued = period.getTransactionAccrued(); + Money feeAccrued = period.getFeeTransactionAccrued(); + Money penaltyAccrued = period.getPenaltyTransactionAccrued(); + LoanRepaymentScheduleInstallment installment = loan.fetchRepaymentScheduleInstallment(period.getInstallmentNumber()); + installment.updateAccrualPortion(interestAccrued, feeAccrued, penaltyAccrued); + } + } else { + List installments = loan.getRepaymentScheduleInstallments(); + boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate(); + for (LoanRepaymentScheduleInstallment installment : installments) { + checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment); + } } // reverse accruals after last installment - LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment(); - reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate()); + reverseTransactionsAfter(accrualTransactions, lastDueDate, false); } - private void reprocessNonPeriodicAccruals(Loan loan, final List accruals) { + private void reprocessNonPeriodicAccruals(Loan loan, final List accrualTransactions) { if (isProgressiveAccrual(loan)) { return; } @@ -738,29 +725,25 @@ private void reprocessNonPeriodicAccruals(Loan loan, final List ExternalId externalId = ExternalId.empty(); boolean isExternalIdAutoGenerationEnabled = configurationDomainService.isExternalIdAutoGenerationEnabled(); - for (LoanTransaction loanTransaction : accruals) { - if (loanTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) { - if (loanTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, - "reversed"); - loanTransaction.reverse(); + for (LoanTransaction accrualTransaction : accrualTransactions) { + if (accrualTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) { + if (accrualTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) { + accrualTransaction.reverse(); if (isExternalIdAutoGenerationEnabled) { externalId = ExternalId.generate(); } - final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan, + final LoanTransaction interestAccrualTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan, interestApplied, loan.getDisbursementDate(), externalId); - loan.addLoanTransaction(interestAppliedTransaction); + loan.addLoanTransaction(interestAccrualTransaction); } } else { - Set chargePaidBies = loanTransaction.getLoanChargesPaid(); + Set chargePaidBies = accrualTransaction.getLoanChargesPaid(); for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { LoanCharge loanCharge = chargePaidBy.getLoanCharge(); Money chargeAmount = loanCharge.getAmount(loan.getCurrency()); - if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(loan.getCurrency()))) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), - loanTransaction, "reversed"); - loanTransaction.reverse(); - loan.handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate()); + if (chargeAmount.isNotEqualTo(accrualTransaction.getAmount(loan.getCurrency()))) { + accrualTransaction.reverse(); + loan.handleChargeAppliedTransaction(loanCharge, accrualTransaction.getTransactionDate()); } } } @@ -786,8 +769,6 @@ private void checkAndUpdateAccrualsForInstallment(Loan loan, List cumulativeIncomeFromInstallments = new HashMap<>(); determineCumulativeIncomeFromInstallments(loan, cumulativeIncomeFromInstallments); @@ -1188,36 +1165,34 @@ private void updateLoanChargesPaidBy(Loan loan, LoanTransaction accrual, Map transactions, LocalDate effectiveDate) { + private boolean reverseTransactionsAfter(List accrualTransactions, LocalDate effectiveDate, boolean addEvent) { boolean reversed = false; - for (LoanTransaction loanTransaction : transactions) { - if (DateUtils.isAfter(loanTransaction.getTransactionDate(), effectiveDate)) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, - "reversed"); - reverseAccrual(loanTransaction); + for (LoanTransaction accrualTransaction : accrualTransactions) { + if (!accrualTransaction.isReversed() && DateUtils.isAfter(accrualTransaction.getTransactionDate(), effectiveDate)) { + reverseAccrual(accrualTransaction, addEvent); reversed = true; } } return reversed; } - private boolean reverseTransactionsOnOrAfter(List transactions, LocalDate date) { + private boolean reverseTransactionsOnOrAfter(List accrualTransactions, LocalDate date, boolean addEvent) { boolean reversed = false; - for (LoanTransaction loanTransaction : transactions) { - if (!DateUtils.isBefore(loanTransaction.getTransactionDate(), date)) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, - "reversed"); - reverseAccrual(loanTransaction); + for (LoanTransaction accrualTransaction : accrualTransactions) { + if (!accrualTransaction.isReversed() && !DateUtils.isBefore(accrualTransaction.getTransactionDate(), date)) { + reverseAccrual(accrualTransaction, addEvent); reversed = true; } } return reversed; } - private void reverseAccrual(LoanTransaction transaction) { + private void reverseAccrual(LoanTransaction transaction, boolean addEvent) { transaction.reverse(); - LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction); - businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + if (addEvent) { + LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + } } private LocalDate getFinalAccrualTransactionDate(Loan loan) { @@ -1229,7 +1204,7 @@ private LocalDate getFinalAccrualTransactionDate(Loan loan) { } public boolean isProgressiveAccrual(@NotNull Loan loan) { - return loan.getLoanProductRelatedDetail().getLoanScheduleType() == LoanScheduleType.PROGRESSIVE; + return loan.isProgressiveSchedule(); } private void setSetHelpers(Loan loan) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 3e3b8dee0fd..c8525e4de33 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -301,17 +301,18 @@ public CommandProcessingResult addLoanCharge(final Long loanId, final JsonComman loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) { - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false); } this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()) // .withEntityId(loanCharge.getId()) // .withEntityExternalId(loanCharge.getExternalId()) // @@ -825,14 +826,14 @@ public void applyOverdueChargesForLoan(final Long loanId, Collection { + private static final class LoanStatusChangedListener implements BusinessEventListener { @Override public void onBusinessEvent(LoanStatusChangedBusinessEvent event) { final Loan loan = event.get(); log.debug("Loan Status change for loan {}", loan.getId()); - LoanStatus oldStatus = event.getOldStatus(); - LoanStatus newStatus = loan.getStatus(); - if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) { - log.debug("Loan Status {} for loan {}", newStatus.getCode(), loan.getId()); - loan.updateLoanSummaryDerivedFields(); - loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan); - } else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) { + if (loan.isOpen()) { loan.handleMaturityDateActivate(); - loanAccrualsProcessingService.processAccrualsOnLoanReopen(loan); } } } @@ -70,9 +62,7 @@ public void onBusinessEvent(LoanStatusChangedBusinessEvent event) { if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { LoanStatus oldStatus = event.getOldStatus(); LoanStatus newStatus = loan.getStatus(); - if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) { - loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); - } else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) { + if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) { loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java index 0e641a7dfd6..11f60232a92 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Map; @@ -26,7 +27,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -68,16 +69,17 @@ LoanTransaction assembleTransactionAndCalculateChanges(Loan loan, JsonCommand co txnExternalId, null); } - public LoanTransaction assembleAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment, - final LocalDate transactionDate) { - MonetaryCurrency currency = loan.getCurrency(); + public LoanTransaction assembleAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment, + @NotNull LocalDate transactionDate) { ExternalId externalId = externalIdFactory.create(); - BigDecimal interestPortion = installment.getInterestCharged(currency).getAmount(); - BigDecimal feeChargesPortion = installment.getFeeChargesCharged(currency).getAmount(); - BigDecimal penaltyChargesPortion = installment.getPenaltyChargesCharged(currency).getAmount(); - BigDecimal transactionAmount = interestPortion.add(feeChargesPortion).add(penaltyChargesPortion); - return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate, - transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId); + BigDecimal interestPortion = installment.getInterestCharged(); + BigDecimal feeChargesPortion = installment.getFeeChargesCharged(); + BigDecimal penaltyChargesPortion = installment.getPenaltyCharges(); + BigDecimal transactionAmount = MathUtil.add(interestPortion, feeChargesPortion, penaltyChargesPortion); + return MathUtil.isGreaterThanZero(transactionAmount) + ? new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate, + transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId) + : null; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index a747373c73d..08040cb1e80 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -95,8 +95,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent; @@ -230,7 +228,6 @@ @RequiredArgsConstructor public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService { - private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; private final PlatformSecurityContext context; private final LoanTransactionValidator loanTransactionValidator; private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer; @@ -499,9 +496,6 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand createNote(loan, command, changes); // auto create standing instruction createStandingInstruction(loan); - - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); } final Set loanCharges = loan.getActiveCharges(); @@ -526,8 +520,8 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } updateRecurringCalendarDatesForInterestRecalculation(loan); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); // Post Dated Checks @@ -551,6 +545,9 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction)); } + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loan.getId()) // @@ -869,8 +866,8 @@ public Map bulkLoanDisbursal(final JsonCommand command, final Co this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } updateRecurringCalendarDatesForInterestRecalculation(loan); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), true); loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan)); } @@ -1085,8 +1082,9 @@ public boolean isChronologicallyTheLatestTransaction(final LoanTransaction loanT private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransaction newInterestPaymentWaiverTransaction) { LocalDate recalculateFrom = null; + LocalDate transactionDate = newInterestPaymentWaiverTransaction.getTransactionDate(); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - recalculateFrom = newInterestPaymentWaiverTransaction.getTransactionDate(); + recalculateFrom = transactionDate; } final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null); @@ -1102,14 +1100,11 @@ private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loa final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor(); - final LoanRepaymentScheduleInstallment currentInstallment = loan - .fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate()); + final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate); boolean reprocess = true; - - if (!loan.isForeclosure() && isTransactionChronologicallyLatest - && DateUtils.isEqualBusinessDate(newInterestPaymentWaiverTransaction.getTransactionDate()) && currentInstallment != null - && currentInstallment.getTotalOutstanding(loan.getCurrency()) + if (!loan.isForeclosure() && isTransactionChronologicallyLatest && DateUtils.isEqualBusinessDate(transactionDate) + && currentInstallment != null && currentInstallment.getTotalOutstanding(loan.getCurrency()) .isEqualTo(newInterestPaymentWaiverTransaction.getAmount(loan.getCurrency()))) { reprocess = false; } @@ -1155,66 +1150,9 @@ private ChangedTransactionDetail reprocessChangedLoanTransactions(Loan loan, return loan.reprocessTransactions(); } - @Transactional - @Override - public Loan reverseReplayAccrualActivityTransaction(Loan loan, final LoanTransaction loanTransaction, - final LoanRepaymentScheduleInstallment installment, final LocalDate transactionDate) { - - LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, - transactionDate); - if (!newLoanTransaction.getDateOf().isEqual(loanTransaction.getDateOf()) - || !LoanTransaction.transactionAmountsMatch(loan.getCurrency(), loanTransaction, newLoanTransaction)) { - loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, - "reversed"); - loanTransaction.reverse(); - loanTransaction.updateExternalId(null); - newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations()); - newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction, - loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED)); - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction); - loan.addLoanTransaction(newLoanTransaction); - - LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); - data.setNewTransactionDetail(newLoanTransaction); - businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); - } - return loan; - } - - @Transactional - @Override - public Loan makeAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment, - final LocalDate transactionDate) { - - LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, - transactionDate); - - if (newAccrualActivityTransaction.getAmount().compareTo(BigDecimal.ZERO) == 0) { - return loan; - } - - return makeAccrualActivityTransaction(loan, newAccrualActivityTransaction); - } - - @Transactional - @Override - public Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction newAccrualActivityTransaction) { - businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); - - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); - - loan.addLoanTransaction(newAccrualActivityTransaction); - - businessEventNotifierService - .notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction)); - - return loan; - } - @Transactional @Override public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand command) { - loanTransactionValidator.validateLoanTransactionInterestPaymentWaiver(command); final Long loanId = command.getLoanId(); @@ -1245,10 +1183,8 @@ public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand comma saveNote(noteText, loan, newInterestPaymentWaiverTransaction); - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate()); // disable all active standing orders linked to this loan if status changes to closed @@ -1264,6 +1200,9 @@ public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand comma businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()) // .withLoanId(loan.getId()) // .withEntityId(newInterestPaymentWaiverTransaction.getId()) // @@ -1564,11 +1503,8 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo loan.updateLoanSummaryAndStatus(); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); @@ -1586,6 +1522,9 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData)); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(entityId) // @@ -1647,9 +1586,7 @@ public ChangedTransactionDetail adjustExistingTransaction(final Loan loan, final writeOffTransaction.reverse(); } - if (loan.isClosedObligationsMet() || loan.isClosedWrittenOff() || loan.isClosedWithOutstandingAmountMarkedForReschedule()) { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan); - } + loan.updateLoanSummaryAndStatus(); if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { changedTransactionDetail = loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, @@ -1850,14 +1787,15 @@ public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final Json this.noteRepository.save(note); } - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction)); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(waiveInterestTransaction.getId()) // @@ -1918,44 +1856,48 @@ public CommandProcessingResult writeOff(final Long loanId, final JsonCommand com loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.WRITE_OFF_OUTSTANDING, writtenOffOnLocalDate); - final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes, - existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); - loanAccrualsProcessingService.reprocessExistingAccruals(loan); - if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) { - loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); - } - - LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L); - this.loanTransactionRepository.saveAndFlush(writeOff); - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - this.loanTransactionRepository.save(mapEntry.getValue()); - this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - saveLoanWithDataIntegrityViolationChecks(loan); - final String noteText = command.stringValueOfParameterNamed("note"); - if (StringUtils.isNotBlank(noteText)) { - changes.put("note", noteText); - final Note note = Note.loanTransactionNote(loan, writeOff, noteText); - this.noteRepository.save(note); - } - - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); - loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); - businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); - businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff)); - return new CommandProcessingResultBuilder() // + CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // - .withEntityId(writeOff.getId()) // - .withEntityExternalId(writeOff.getExternalId()) // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // .withGroupId(loan.getGroupId()) // .withLoanId(loanId) // - .with(changes).build(); + .with(changes); + + final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes, + existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); + + if (changedTransactionDetail != null) { + loanAccrualsProcessingService.reprocessExistingAccruals(loan); + if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) { + loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); + } + LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L); + this.loanTransactionRepository.saveAndFlush(writeOff); + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + this.loanTransactionRepository.save(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + saveLoanWithDataIntegrityViolationChecks(loan); + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + changes.put("note", noteText); + final Note note = Note.loanTransactionNote(loan, writeOff, noteText); + this.noteRepository.save(note); + } + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); + loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff)); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + + builder.withEntityId(writeOff.getId()).withEntityExternalId(writeOff.getExternalId()); + } + return builder.build(); } @Transactional @@ -2020,12 +1962,9 @@ public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand co this.noteRepository.save(note); } - if (possibleClosingTransaction != null) { - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - } loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); @@ -2037,6 +1976,9 @@ public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand co // disable all active standing instructions linked to the loan this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + CommandProcessingResult result; if (possibleClosingTransaction != null) { @@ -2654,15 +2596,17 @@ public CommandProcessingResult undoWriteOff(Long loanId) { } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); + + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction)); + + this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); - if (writeOffTransaction != null) { - businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); - businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction)); - } - this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -2811,11 +2755,13 @@ private CommandProcessingResult processLoanDisbursementDetail(Loan loan, Long lo if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { createLoanScheduleArchive(loan, scheduleGeneratorDTO); } + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); + this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); - this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -2879,11 +2825,12 @@ public Loan recalculateInterest(Loan loan) { replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); + businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); - businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); return loan; } @@ -3396,8 +3343,6 @@ public CommandProcessingResult makeRefund(final Long loanId, final LoanTransacti } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); - // Create journal entries for the new transaction(s) - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); // Calculate and apply delinquency tag (if applicable) loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate); // disable all active standing orders linked to this loan if status @@ -3414,11 +3359,14 @@ public CommandProcessingResult makeRefund(final Long loanId, final LoanTransacti loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction); } // Raise business events - loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, - loan.repaymentScheduleDetail().isInterestRecalculationEnabled()); + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, + loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + // Create journal entries for the new transaction(s) + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + Long entityId = refundTransaction.getId(); ExternalId entityExternalId = refundTransaction.getExternalId(); Long subEntityId = interestRefundTransaction != null ? interestRefundTransaction.getId() : null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index fdfaa607608..1f0605de629 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -100,6 +100,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService; @@ -332,9 +333,16 @@ public LoanReadPlatformServiceImpl loanReadPlatformService(JdbcTemplate jdbcTemp @Bean @ConditionalOnMissingBean(LoanStatusChangePlatformService.class) public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService, + LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) { + return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualActivityProcessingService); + } + + @Bean + @ConditionalOnMissingBean(LoanAccrualEventService.class) + public LoanAccrualEventService loanAccrualEventService(BusinessEventNotifierService businessEventNotifierService, LoanAccrualsProcessingService loanAccrualsProcessingService, LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) { - return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService, + return new LoanAccrualEventService(businessEventNotifierService, loanAccrualsProcessingService, loanAccrualActivityProcessingService); } @@ -351,8 +359,8 @@ public LoanUtilService loanUtilService(ApplicationCurrencyRepositoryWrapper appl @Bean @ConditionalOnMissingBean(LoanWritePlatformService.class) - public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory, - PlatformSecurityContext context, LoanTransactionValidator loanTransactionValidator, + public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context, + LoanTransactionValidator loanTransactionValidator, LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper, LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository, LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository, @@ -381,18 +389,17 @@ public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTr LoanOfficerValidator loanOfficerValidator, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService, LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService) { - return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator, - loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository, - loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, - calendarInstanceRepository, paymentDetailWritePlatformService, holidayRepository, configurationDomainService, - workingDaysRepository, accountTransfersWritePlatformService, accountTransfersReadPlatformService, - accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository, - loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository, - accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper, - entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository, - cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, - postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine, - loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, + return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer, + loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, + loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository, + paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository, + accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService, + loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService, + loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService, + guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService, + transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository, + repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, + defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository, loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator, loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index b593f2b3a85..17f633d3e17 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -128,6 +128,9 @@ class LoanChargeWritePlatformServiceImplTest { @Mock private LoanChargeValidator loanChargeValidator; + @Mock + private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; + @BeforeEach void setUp() { when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java index 3f3923e8e24..4574dc5d8f8 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java @@ -240,8 +240,6 @@ void givenMerchantIssuedRefundTransactionWithRelatedTransactions_whenAdjustExist when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList()); doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any()); when(loan.isClosedWrittenOff()).thenReturn(false); - when(loan.isClosedObligationsMet()).thenReturn(false); - when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false); when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); // Act @@ -287,8 +285,6 @@ void givenNonMerchantIssuedRefundTransaction_whenAdjustExistingTransaction_thenN when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList()); doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any()); when(loan.isClosedWrittenOff()).thenReturn(false); - when(loan.isClosedObligationsMet()).thenReturn(false); - when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false); when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); // Act diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index c494a05957b..b164f88a71d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -553,7 +553,7 @@ protected void verifyNoTransactions(Long loanId) { protected void verifyTransactions(Long loanId, Transaction... transactions) { GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); if (transactions == null || transactions.length == 0) { - Assertions.assertTrue(loanDetails.getTransactions().isEmpty(), "No transaction is expected"); + Assertions.assertTrue(loanDetails.getTransactions().isEmpty(), "No transaction is expected on loan " + loanId); } else { Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size()); Arrays.stream(transactions).forEach(tr -> { @@ -562,12 +562,13 @@ protected void verifyTransactions(Long loanId, Transaction... transactions) { && Objects.equals(item.getType().getValue(), tr.type) // && Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter))) .findFirst(); - Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr); + Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr + " on loan " + loanId); GetLoansLoanIdTransactions tx = optTx.get(); if (tr.reversed != null) { - Assertions.assertEquals(tr.reversed, tx.getManuallyReversed(), "Transaction is not reversed: " + tr); + Assertions.assertEquals(tr.reversed, tx.getManuallyReversed(), + "Transaction is not reversed: " + tr + " on loan " + loanId); } }); } @@ -576,9 +577,9 @@ protected void verifyTransactions(Long loanId, Transaction... transactions) { protected void verifyTransactions(Long loanId, TransactionExt... transactions) { GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); if (transactions == null || transactions.length == 0) { - assertNull(loanDetails.getTransactions(), "No transaction is expected"); + assertNull(loanDetails.getTransactions(), "No transaction is expected on loan " + loanId); } else { - Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size()); + Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size(), "Number of transactions on loan " + loanId); Arrays.stream(transactions).forEach(tr -> { boolean found = loanDetails.getTransactions().stream().anyMatch(item -> Objects.equals(item.getAmount(), tr.amount) // && Objects.equals(item.getType().getValue(), tr.type) // @@ -591,7 +592,7 @@ protected void verifyTransactions(Long loanId, TransactionExt... transactions) { && Objects.equals(item.getOverpaymentPortion(), tr.overpaymentPortion) // && Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) // ); - Assertions.assertTrue(found, "Required transaction not found: " + tr); + Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanId); }); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java index 5b7de1503d9..a217e704b99 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java @@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; import org.apache.fineract.integrationtests.common.ClientHelper; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -78,15 +79,20 @@ public void immediateChargeAccrualPostMaturityTest() { addLoanCharge(loanId, chargeResponse.getResourceId(), "03 October 2024", 20.0d); final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); Assertions.assertTrue( - loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d))); + loanDetails.getTransactions().stream().noneMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d))); }); runAt("04 October 2024", () -> { globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY, false); executeInlineCOB(loanId); final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); - Assertions.assertTrue( - loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d))); + Assertions.assertTrue(loanDetails.getTransactions().stream() + .anyMatch(t -> t.getType().getAccrual() && t.getFeeChargesPortion().equals(20.0d))); }); } + + @AfterEach + public void afterEach() { + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY, false); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java index 70b42eecb08..696fafe63fb 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackWithCreditAllocationsIntegrationTests.java @@ -300,6 +300,7 @@ public void chargebackWithCreditAllocationPenaltyFeeInterestAndPrincipalOnNPlusO transaction(312.0, "Repayment", "20 February 2023", 626.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), // transaction(312.0, "Repayment", "20 March 2023", 314.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), // transaction(384.0, "Repayment", "20 April 2023", 0.0, 314.0, 0.0, 50.0, 20.0, 0.0, 0.0), // + transaction(70.0, "Accrual", "20 April 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), // transaction(100.0, "Chargeback", "02 May 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) // ); @@ -584,6 +585,7 @@ public void chargebackWithCreditAllocationPrincipalInterestFeePenaltyWhenOverpai verifyTransactions(loanId, // transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), // + transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), // transaction(100.0, "Chargeback", "02 May 2023", 50.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0) // ); @@ -649,6 +651,7 @@ public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpai verifyTransactions(loanId, // transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), // + transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), // transaction(100.0, "Chargeback", "20 January 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) // ); @@ -715,6 +718,7 @@ public void chargebackWithCreditAllocationFeePenaltyPrincipalInterestWhenOverpai verifyTransactions(loanId, // transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), // + transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), // transaction(100.0, "Chargeback", "20 January 2023", 0.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) // ); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java index 3af858d1573..c9cc50765ea 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java @@ -210,9 +210,11 @@ public void verifyInterestRefundCreatedForMerchantIssuedRefund() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), - transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"), - transaction(5.74, "Interest Refund", "22 January 2021")); + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), // + transaction(5.74, "Accrual", "22 January 2021"), // + transaction(5.74, "Interest Refund", "22 January 2021") // + ); }); } @@ -241,9 +243,11 @@ public void verifyUC01() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), - transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"), - transaction(5.74, "Interest Refund", "22 January 2021")); + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Payout Refund", "22 January 2021"), // + transaction(5.74, "Accrual", "22 January 2021"), // + transaction(5.74, "Interest Refund", "22 January 2021") // + ); }); } @@ -272,9 +276,10 @@ public void verifyUC02a() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), - transaction(1000.0, "Payout Refund", "01 February 2021"), transaction(8.48, "Accrual", "01 February 2021"), - transaction(8.48, "Interest Refund", "01 February 2021")); + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Payout Refund", "01 February 2021"), // + transaction(8.48, "Accrual", "01 February 2021"), // + transaction(8.48, "Interest Refund", "01 February 2021")); // }); } @@ -315,9 +320,12 @@ public void verifyUC02b() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), - transaction(87.89, "Repayment", "01 February 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"), - transaction(10.49, "Interest Refund", "09 February 2021"), transaction(10.49, "Accrual", "09 February 2021")); + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(87.89, "Repayment", "01 February 2021"), // + transaction(1000.0, "Payout Refund", "09 February 2021"), // + transaction(10.49, "Interest Refund", "09 February 2021"), // + transaction(10.49, "Accrual", "09 February 2021") // + ); }); } @@ -350,9 +358,12 @@ public void verifyUC03() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"), - transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"), - transaction(5.74, "Accrual", "22 January 2021"), transaction(5.74, "Interest Refund", "22 January 2021")); + verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"), // + transaction(250.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Payout Refund", "22 January 2021"), // + transaction(5.74, "Accrual", "22 January 2021"), // + transaction(5.74, "Interest Refund", "22 January 2021") // + ); }); } @@ -386,9 +397,12 @@ public void verifyUC04() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"), - transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"), - transaction(5.13, "Accrual", "22 January 2021"), transaction(5.13, "Interest Refund", "22 January 2021")); + verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"), // + transaction(250.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Payout Refund", "22 January 2021"), // + transaction(5.13, "Accrual", "22 January 2021"), // + transaction(5.13, "Interest Refund", "22 January 2021") // + ); }); } @@ -435,10 +449,13 @@ public void verifyUC05() { Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); logLoanTransactions(loanId); - verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"), - transaction(500.0, "Disbursement", "07 January 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"), - transaction(87.82, "Repayment", "01 February 2021"), transaction(9.67, "Interest Refund", "09 February 2021"), - transaction(9.67, "Accrual", "09 February 2021")); + verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"), // + transaction(500.0, "Disbursement", "07 January 2021"), // + transaction(1000.0, "Payout Refund", "09 February 2021"), // + transaction(87.82, "Repayment", "01 February 2021"), // + transaction(9.67, "Interest Refund", "09 February 2021"), // + transaction(9.67, "Accrual", "09 February 2021") // + ); }); } @@ -1115,9 +1132,10 @@ public void verifyUC18S1() { verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // transaction(85.63, "Repayment", "10 January 2021"), // - transaction(5.7, "Accrual", "22 January 2021"), // + transaction(5.70, "Accrual", "22 January 2021"), // transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), // - transaction(5.42, "Interest Refund", "22 January 2021") // + transaction(5.42, "Interest Refund", "22 January 2021"), // + transaction(0.28, "Accrual Adjustment", "22 January 2021") // ); }); } @@ -1202,7 +1220,8 @@ public void verifyUC18S2() { reversedTransaction(85.63, "Repayment", "10 January 2021"), // transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), // transaction(5.70, "Interest Refund", "22 January 2021"), // - transaction(5.42, "Accrual", "22 January 2021") // + transaction(5.42, "Accrual", "22 January 2021"), // + transaction(0.28, "Accrual", "22 January 2021") // ); }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java index a531b6374ed..622394ea89c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java @@ -569,8 +569,8 @@ public void testAccrualActivityPostingForProgressiveLoanWithEarlyRepaymentAndRev verifyTransactions(loanId.get(), transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, true), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); // - }); } @@ -652,32 +652,36 @@ public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRe addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); - verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // - transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // - transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // - + transaction(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false) // + ); }); runAt(disbursementDay2, () -> { loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2) .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en")); - verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // - transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // - transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // - + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false) // + ); }); runAt(repaymentPeriod1CloseDate, () -> { inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); - verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + verifyTransactions(loanId.get(), + transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // - transaction(70.0, "Accrual", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // - transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // - transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // - + transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false) // + ); }); } @@ -727,6 +731,7 @@ public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRe verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // + transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // @@ -828,6 +833,7 @@ public void testAccrualActivityPostingForMultiDisburseProgressiveLoan() { verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), transaction(500.0, "Disbursement", disbursementDay2, 500, 0, 0, 0, 0, 0, 0, false)); }); @@ -837,9 +843,9 @@ public void testAccrualActivityPostingForMultiDisburseProgressiveLoan() { verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false), transaction(570.0, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false), + transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false), - transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false), - transaction(70.0, "Accrual", repaymentPeriod2DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false)); + transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false)); }); } @@ -867,18 +873,7 @@ public void testAccrualActivityPostingForMultiDisburseLoan() { chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1); verifyTransactions(loanId.get(), - transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, - // "Accrual", - // repaymentDate1, - // 0.0, - // 0, - // 39.45, - // 40.0, - // 30.0, - // 0, - // 0, - // false), - // // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) // @@ -887,18 +882,7 @@ public void testAccrualActivityPostingForMultiDisburseLoan() { runAt(repaymentPeriod1CloseDate, () -> { inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); verifyTransactions(loanId.get(), - transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, - // "Accrual", - // repaymentDate1, - // 0.0, - // 0, - // 39.45, - // 40.0, - // 30.0, - // 0, - // 0, - // false), - // // + transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) //