This is an automated email from the ASF dual-hosted git repository. aleks pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit c2e19bb68faf637f4aa473f0278e122c9230a42c Author: mariiaKraievska <[email protected]> AuthorDate: Fri Jul 18 19:03:18 2025 +0300 FINERACT-2327: Allow to create Interest Refund transaction manually --- .../commands/service/CommandWrapperBuilder.java | 9 + .../apache/fineract/test/data/TransactionType.java | 1 + .../fineract/test/helper/ErrorMessageHelper.java | 4 + .../test/messaging/event/EventCheckHelper.java | 5 +- .../LoanTransactionInterestRefundPostEvent.java | 27 +++ .../fineract/test/stepdef/loan/LoanStepDef.java | 74 ++++++++ .../fineract/test/support/TestContextKey.java | 1 + .../features/LoanMerchantIssuedRefund.feature | 200 ++++++++++++++++++++- .../resources/features/LoanPayoutRefund.feature | 200 ++++++++++++++++++++- .../domain/LoanAccountDomainService.java | 3 + .../loanaccount/domain/LoanTransaction.java | 5 + .../ManualInterestRefundCommandHandler.java | 42 +++++ .../serialization/LoanTransactionValidator.java | 2 + .../service/LoanReadPlatformService.java | 2 + .../service/LoanWritePlatformService.java | 2 + .../ProgressiveLoanTransactionValidatorImpl.java | 5 + .../api/LoanTransactionsApiResource.java | 11 +- .../domain/LoanAccountDomainServiceJpa.java | 6 + .../LoanTransactionValidatorImpl.java | 31 ++++ .../service/LoanReadPlatformServiceImpl.java | 36 ++++ .../LoanWritePlatformServiceJpaRepositoryImpl.java | 134 +++++++++++++- .../starter/LoanAccountConfiguration.java | 7 +- 22 files changed, 799 insertions(+), 8 deletions(-) diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 42f23b6bac..f0321581b6 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -3885,4 +3885,13 @@ public class CommandWrapperBuilder { this.href = "/loans/" + loanId; return this; } + + public CommandWrapperBuilder manualInterestRefund(final Long loanId, final Long transactionId) { + this.actionName = "MANUAL_INTEREST_REFUND"; + this.entityName = "LOAN_TRANSACTION"; + this.loanId = loanId; + this.entityId = transactionId; + this.href = "/loans/" + loanId + "/transactions/" + transactionId + "?command=interest-refund"; + return this; + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java index 74c0330554..8029dd6d74 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java @@ -41,6 +41,7 @@ public enum TransactionType { BUY_DOWN_FEE("buyDownFee"), // BUY_DOWN_FEE_ADJUSTMENT("buyDownFeeAdjustment"), // BUY_DOWN_FEE_AMORTIZATION("buyDownFeeAmortization"), // + INTEREST_REFUND("interestRefund"), // ; public final String value; 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 595abad3ef..dd0a911663 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 @@ -68,6 +68,10 @@ public final class ErrorMessageHelper { return "Loan can't be disbursed, disburse amount is exceeding approved principal."; } + public static String addManualInterestRefundIfAlreadyExistsFailure() { + return "Interest Refund already exists for this transaction"; + } + public static String addDisbursementExceedMaxAppliedAmountFailure(String totalDisbAmount, String maxDisbursalAmount) { return String.format("Loan disbursal amount can't be greater than maximum applied loan amount calculation. " + "Total disbursed amount: %s Maximum disbursal amount: %s", totalDisbAmount, maxDisbursalAmount); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java index f7e0ea509f..d3f876f008 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java @@ -76,6 +76,7 @@ import org.apache.fineract.test.messaging.event.loan.transaction.LoanDisbursalTr import org.apache.fineract.test.messaging.event.loan.transaction.LoanRefundPostBusinessEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionGoodwillCreditPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionInterestPaymentWaiverPostEvent; +import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionInterestRefundPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMakeRepaymentPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMerchantIssuedRefundPostEvent; import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionPayoutRefundPostEvent; @@ -308,7 +309,8 @@ public class EventCheckHelper { Response<PostLoansLoanIdTransactionsResponse> transactionResponse, TransactionType transactionType, String externalOwnerId) throws IOException { Long loanId = transactionResponse.body().getLoanId(); - Long transactionId = transactionResponse.body().getResourceId(); + Long transactionId = transactionType.equals(TransactionType.INTEREST_REFUND) ? transactionResponse.body().getSubResourceId() + : transactionResponse.body().getResourceId(); Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); GetLoansLoanIdTransactions transactionFound = transactions// @@ -324,6 +326,7 @@ public class EventCheckHelper { case MERCHANT_ISSUED_REFUND -> LoanTransactionMerchantIssuedRefundPostEvent.class; case REFUND_BY_CASH -> LoanRefundPostBusinessEvent.class; case INTEREST_PAYMENT_WAIVER -> LoanTransactionInterestPaymentWaiverPostEvent.class; + case INTEREST_REFUND -> LoanTransactionInterestRefundPostEvent.class; default -> throw new IllegalStateException(String.format("transaction type %s cannot be found", transactionType.getValue())); }; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java new file mode 100644 index 0000000000..8be3a75e6d --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanTransactionInterestRefundPostEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan.transaction; + +public class LoanTransactionInterestRefundPostEvent extends AbstractLoanTransactionEvent { + + @Override + public String getEventName() { + return "LoanTransactionInterestRefundPostBusinessEvent"; + } +} 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 8e82eb3d10..a4c667045f 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 @@ -433,6 +433,63 @@ public class LoanStepDef extends AbstractStepDef { eventCheckHelper.loanBalanceChangedEventCheck(loanId); } + @When("Admin manually adds Interest Refund for {string} transaction made on {string} with {double} EUR interest refund amount") + public void addInterestRefundTransactionManually(final String transactionTypeInput, final String transactionDate, final double amount) + throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.body().getLoanId(); + final TransactionType transactionType = TransactionType.valueOf(transactionTypeInput); + + final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + assert loanDetailsResponse.body() != null; + final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); + assert transactions != null; + final GetLoansLoanIdTransactions refundTransaction = transactions.stream() + .filter(t -> t.getType() != null + && (transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : "Merchant Issued Refund") + .equals(t.getType().getValue()) + && t.getDate() != null && transactionDate.equals(FORMATTER.format(t.getDate()))) + .findFirst().orElseThrow(() -> new IllegalStateException("No refund transaction found for loan " + loanId)); + + final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse = addInterestRefundTransaction(amount, + refundTransaction.getId()); + testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, adjustmentResponse); + ErrorHelper.checkSuccessfulApiCall(adjustmentResponse); + eventCheckHelper.transactionEventCheck(adjustmentResponse, TransactionType.INTEREST_REFUND, null); + eventCheckHelper.loanBalanceChangedEventCheck(loanId); + } + + @Then("Admin fails to add duplicate Interest Refund for {string} transaction made on {string} with {double} EUR interest refund amount") + public void failToAddManualInterestRefundIfAlreadyExists(final String transactionTypeInput, final String transactionDate, + final double amount) throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.body().getLoanId(); + final TransactionType transactionType = TransactionType.valueOf(transactionTypeInput); + + final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + assert loanDetailsResponse.body() != null; + final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); + assert transactions != null; + final GetLoansLoanIdTransactions refundTransaction = transactions.stream() + .filter(t -> t.getType() != null + && (transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : "Merchant Issued Refund") + .equals(t.getType().getValue()) + && t.getDate() != null && transactionDate.equals(FORMATTER.format(t.getDate()))) + .findFirst().orElseThrow(() -> new IllegalStateException("No refund transaction found for loan " + loanId)); + + final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse = addInterestRefundTransaction(amount, + refundTransaction.getId()); + testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, adjustmentResponse); + final ErrorResponse errorDetails = ErrorResponse.from(adjustmentResponse); + assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure()).isEqualTo(403); + assertThat(errorDetails.getSingleError().getDeveloperMessage()) + .isEqualTo(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure()); + } + private void createTransactionWithAutoIdempotencyKeyAndWithExternalOwner(String transactionTypeInput, String transactionPaymentType, String transactionDate, double transactionAmount, String externalOwnerId) throws IOException { eventStore.reset(); @@ -4984,4 +5041,21 @@ public class LoanStepDef extends AbstractStepDef { } assertThat(developerMessage).isEqualTo(ErrorMessageHelper.updateApprovedLoanLessMinAllowedAmountFailure()); } + + private Response<PostLoansLoanIdTransactionsResponse> addInterestRefundTransaction(final double amount, final Long transactionId) + throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + assert loanResponse.body() != null; + final long loanId = loanResponse.body().getLoanId(); + + final DefaultPaymentType paymentType = DefaultPaymentType.AUTOPAY; + final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType); + + final PostLoansLoanIdTransactionsTransactionIdRequest interestRefundRequest = new PostLoansLoanIdTransactionsTransactionIdRequest() + .dateFormat("dd MMMM yyyy").locale("en").transactionAmount(amount).paymentTypeId(paymentTypeValue) + .externalId("EXT-INT-REF-" + UUID.randomUUID()).note(""); + + return loanTransactionsApi.adjustLoanTransaction(loanId, transactionId, interestRefundRequest, "interest-refund").execute(); + } + } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index 9b7c554cc9..a70ddc138a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -241,6 +241,7 @@ public abstract class TestContextKey { public static final String LOAN_DISBURSEMENT_DETAIL_RESPONSE = "loanDisbursementDetailResponse"; public static final String LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID = "loanCapitalizedIncomeAmortizationId"; public static final String LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE = "loanCapitalizedIncomeAdjustmentResponse"; + public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination"; public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse"; public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature index 89c1536a34..1a9aacd2b8 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature @@ -333,4 +333,202 @@ Feature: MerchantIssuedRefund | 01 August 2024 | Accrual | 8.33 | 0.0 | 8.33 | 0.0 | 0.0 | 0.0 | false | false | | 01 August 2024 | Accrual Activity | 8.33 | 0.0 | 8.33 | 0.0 | 0.0 | 0.0 | false | false | | 05 August 2024 | Merchant Issued Refund | 10.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" \ No newline at end of file + Then Loan status will be "OVERPAID" + + @TestRailId:C3873 + Scenario: Manual Interest Refund creation for Merchant Issued Refund with interestRefundCalculation = false + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.36 | 0.0 | 0.0 | 335.78 | 50.0 | 50.0 | 0.0 | 285.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 13.58 | 0.0 | 0.0 | 1013.58 | 388.9 | 388.9 | 0.0 | 624.68 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + When Admin manually adds Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 0.19 | 0.19 | 0.0 | 338.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.36 | 0.0 | 0.0 | 335.78 | 50.0 | 50.0 | 0.0 | 285.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 13.58 | 0.0 | 0.0 | 1013.58 | 389.09 | 389.09 | 0.0 | 624.49 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + + @TestRailId:C3874 + Scenario: Undo Merchant Issued Refund with manual Interest Refund, both transactions are reversed + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.36 | 0.0 | 0.0 | 335.78 | 50.0 | 50.0 | 0.0 | 285.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 13.58 | 0.0 | 0.0 | 1013.58 | 388.9 | 388.9 | 0.0 | 624.68 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + When Admin manually adds Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 0.19 | 0.19 | 0.0 | 338.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.36 | 0.0 | 0.0 | 335.78 | 50.0 | 50.0 | 0.0 | 285.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 13.58 | 0.0 | 0.0 | 1013.58 | 389.09 | 389.09 | 0.0 | 624.49 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + When Customer undo "1"th transaction made on "15 July 2024" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 334.07 | 329.45 | 9.45 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 334.07 | 2.78 | 0.0 | 0.0 | 336.85 | 0.0 | 0.0 | 0.0 | 336.85 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.65 | 0.0 | 0.0 | 1014.65 | 338.9 | 338.9 | 0.0 | 675.75 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | true | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | true | false | + Then Loan Transactions tab has a "MERCHANT_ISSUED_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + | ASSET | 112601 | Loans Receivable | 50.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 50.0 | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + | ASSET | 112603 | Interest/Fee Receivable | 0.19 | | + | INCOME | 404000 | Interest Income | | 0.19 | + + @TestRailId:C3875 + Scenario: Prevent manual Interest Refund creation if interestRefundCalculation = true and Interest Refund already exists + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation true + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 0.19 | 0.19 | 0.0 | 338.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.36 | 0.0 | 0.0 | 335.78 | 50.0 | 50.0 | 0.0 | 285.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 13.58 | 0.0 | 0.0 | 1013.58 | 389.09 | 389.09 | 0.0 | 624.49 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + When Admin fails to add duplicate Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount \ No newline at end of file diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature index 820a30e6d7..4de834faee 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature @@ -192,4 +192,202 @@ Feature: PayoutRefund | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | | 10 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 950.0 | true | false | | 10 July 2024 | Interest Refund | 0.12 | 0.0 | 0.12 | 0.0 | 0.0 | 950.0 | true | false | - | 15 July 2024 | Repayment | 100.0 | 96.24 | 3.76 | 0.0 | 0.0 | 903.76 | false | true | \ No newline at end of file + | 15 July 2024 | Repayment | 100.0 | 96.24 | 3.76 | 0.0 | 0.0 | 903.76 | false | true | + + @TestRailId:C3870 + Scenario: Manual Interest Refund creation for Payout Refund with interestRefundCalculation = false + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 50.0 | 50.0 | 0.0 | 288.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.78 | 0.0 | 0.0 | 336.2 | 0.0 | 0.0 | 0.0 | 336.2 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.0 | 0.0 | 0.0 | 1014.0 | 388.9 | 388.9 | 0.0 | 625.1 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + When Admin manually adds Interest Refund for "PAYOUT_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 50.19 | 50.19 | 0.0 | 288.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.78 | 0.0 | 0.0 | 336.2 | 0.0 | 0.0 | 0.0 | 336.2 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.0 | 0.0 | 0.0 | 1014.0 | 389.09 | 389.09 | 0.0 | 624.91 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + + @TestRailId:C3871 + Scenario: Undo Payout Refund with manual Interest Refund, both transactions are reversed + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 50.0 | 50.0 | 0.0 | 288.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.78 | 0.0 | 0.0 | 336.2 | 0.0 | 0.0 | 0.0 | 336.2 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.0 | 0.0 | 0.0 | 1014.0 | 388.9 | 388.9 | 0.0 | 625.1 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + When Admin manually adds Interest Refund for "PAYOUT_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 50.19 | 50.19 | 0.0 | 288.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.78 | 0.0 | 0.0 | 336.2 | 0.0 | 0.0 | 0.0 | 336.2 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.0 | 0.0 | 0.0 | 1014.0 | 389.09 | 389.09 | 0.0 | 624.91 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + When Customer undo "1"th transaction made on "15 July 2024" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 334.07 | 329.45 | 9.45 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 334.07 | 2.78 | 0.0 | 0.0 | 336.85 | 0.0 | 0.0 | 0.0 | 336.85 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.65 | 0.0 | 0.0 | 1014.65 | 338.9 | 338.9 | 0.0 | 675.75 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | true | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | true | false | + Then Loan Transactions tab has a "PAYOUT_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 50.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 50.0 | | + | ASSET | 112601 | Loans Receivable | 50.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 50.0 | + Then Loan Transactions tab has a "INTEREST_REFUND" transaction with date "15 July 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | | 0.19 | + | INCOME | 404000 | Interest Income | 0.19 | | + | ASSET | 112603 | Interest/Fee Receivable | 0.19 | | + | INCOME | 404000 | Interest Income | | 0.19 | + + @TestRailId:C3872 + Scenario: Prevent manual Interest Refund creation if interestRefundCalculation = true and Interest Refund already exists + When Admin sets the business date to "01 July 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation true + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 333.42 | 330.1 | 8.8 | 0.0 | 0.0 | 338.9 | 50.19 | 50.19 | 0.0 | 288.71 | + | 3 | 30 | 01 October 2024 | | 0.0 | 333.42 | 2.78 | 0.0 | 0.0 | 336.2 | 0.0 | 0.0 | 0.0 | 336.2 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.0 | 0.0 | 0.0 | 1014.0 | 389.09 | 389.09 | 0.0 | 624.91 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | + | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | + When Admin fails to add duplicate Interest Refund for "PAYOUT_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount \ No newline at end of file diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java index 1cc87f2b16..ad8d5d0fa6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -97,4 +97,7 @@ public interface LoanAccountDomainService { LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData loanRefundRequest); void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction transaction); + + LoanTransaction createManualInterestRefundWithAmount(Loan loan, LoanTransaction targetTransaction, BigDecimal amount, + PaymentDetail paymentDetail, ExternalId txnExternalId); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index d8dc5940b3..4b692da5f7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -195,6 +195,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.INTEREST_REFUND, null, amount, date, externalId); } + public static LoanTransaction interestRefund(final Loan loan, final BigDecimal amount, final LocalDate date, + final PaymentDetail paymentDetail, final ExternalId externalId) { + return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.INTEREST_REFUND, paymentDetail, amount, date, externalId); + } + public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate, final ExternalId externalId, PaymentDetail paymentDetail) { return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java new file mode 100644 index 0000000000..09c9abc093 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java @@ -0,0 +1,42 @@ +/** + * 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.handler; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@CommandType(entity = "LOAN_TRANSACTION", action = "MANUAL_INTEREST_REFUND") +public class ManualInterestRefundCommandHandler implements NewCommandSourceHandler { + + private final LoanWritePlatformService loanWritePlatformService; + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return loanWritePlatformService.makeManualInterestRefund(command.getLoanId(), command.entityId(), command); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java index 1e77207dea..d3592f8a0d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java @@ -94,4 +94,6 @@ public interface LoanTransactionValidator { void validateExternalId(DataValidatorBuilder baseDataValidator, JsonElement element); void validateReversalExternalId(DataValidatorBuilder baseDataValidator, JsonElement element); + + void validateManualInterestRefundTransaction(String json); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index ed2d7176a1..cf8c4eb08f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -155,4 +155,6 @@ public interface LoanReadPlatformService { List<Long> retrieveLoanIdsByExternalIds(List<ExternalId> externalIds); boolean existsByLoanId(Long loanId); + + LoanTransactionData retrieveManualInterestRefundTemplate(Long loanId, Long targetTransactionId); } 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 f64f2d71ae..59cf3054df 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 @@ -125,4 +125,6 @@ public interface LoanWritePlatformService { CommandProcessingResult undoChargeOff(JsonCommand command); CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command); + + CommandProcessingResult makeManualInterestRefund(Long loanId, Long transactionId, JsonCommand command); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java index a1a0155afc..02e4739d30 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java @@ -572,6 +572,11 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT loanTransactionValidator.validateReversalExternalId(baseDataValidator, element); } + @Override + public void validateManualInterestRefundTransaction(final String json) { + loanTransactionValidator.validateManualInterestRefundTransaction(json); + } + private Set<String> getCapitalizedIncomeParameters() { return new HashSet<>( Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId")); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 3d0ef8d180..b88965b999 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -93,6 +93,7 @@ public class LoanTransactionsApiResource { public static final String CAPITALIZED_INCOME = "capitalizedIncome"; public static final String CAPITALIZED_INCOME_ADJUSTMENT = "capitalizedIncomeAdjustment"; public static final String CONTRACT_TERMINATION = "contractTermination"; + public static final String INTEREST_REFUND_COMMAND_VALUE = "interest-refund"; private final Set<String> responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId", LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME)); @@ -121,7 +122,8 @@ public class LoanTransactionsApiResource { + "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n" + "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver" + "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" - + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n") + + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n" + + "loans/1/transactions/template?command=interest-refund") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) }) public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId, @@ -151,7 +153,8 @@ public class LoanTransactionsApiResource { + "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n" + "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver" + "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)" - + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n") + + "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n" + + "loans/1/transactions/template?command=interest-refund") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) }) public String retrieveTransactionTemplate( @@ -694,6 +697,8 @@ public class LoanTransactionsApiResource { } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) { transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId, LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionId); + } else if (CommandParameterUtil.is(commandParam, INTEREST_REFUND_COMMAND_VALUE)) { + transactionData = this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId, transactionId); } else { throw new UnrecognizedQueryParamException("command", commandParam); } @@ -718,6 +723,8 @@ public class LoanTransactionsApiResource { commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build(); } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) { commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, resolvedTransactionId).build(); + } else if (CommandParameterUtil.is(commandParam, INTEREST_REFUND_COMMAND_VALUE)) { + commandRequest = builder.manualInterestRefund(resolvedLoanId, resolvedTransactionId).build(); } else { // Default to adjust the Loan Transaction commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build(); } 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 20cf3535af..f528c505c4 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 @@ -206,7 +206,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final ExternalId txnExternalId = externalIdFactory.create(); businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan)); return LoanTransaction.interestRefund(loan, interestRefundAmount, refundTransaction.getDateOf(), txnExternalId); + } + @Override + public LoanTransaction createManualInterestRefundWithAmount(final Loan loan, final LoanTransaction targetTransaction, + final BigDecimal interestRefundAmount, final PaymentDetail paymentDetail, final ExternalId txnExternalId) { + businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan)); + return LoanTransaction.interestRefund(loan, interestRefundAmount, targetTransaction.getDateOf(), paymentDetail, txnExternalId); } @Transactional diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java index 4847176e4f..f75e69daf4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java @@ -1089,4 +1089,35 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida baseDataValidator.reset().parameter("reversalExternalId").value(reversalExternalId).notExceedingLengthOf(100); } } + + @Override + public void validateManualInterestRefundTransaction(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Set<String> transactionParameters = new HashSet<>( + Arrays.asList("transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber", + "checkNumber", "routingCode", "receiptNumber", "bankNumber", "loanId", "numberOfRepayments")); + + final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters); + + final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element); + baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount(); + + final String note = this.fromApiJsonHelper.extractStringNamed("note", element); + baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); + + final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element); + baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100); + + validatePaymentDetails(baseDataValidator, element); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 95113011ca..b956534c20 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -46,8 +46,10 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.MathUtil; @@ -192,6 +194,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa private final LoanBalanceService loanBalanceService; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository; + private final InterestRefundServiceDelegate interestRefundServiceDelegate; @Override public LoanAccountData retrieveOne(final Long loanId) { @@ -2448,6 +2451,39 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa return loanRepositoryWrapper.existsByLoanId(loanId); } + @Override + public LoanTransactionData retrieveManualInterestRefundTemplate(final Long loanId, final Long targetTransactionId) { + final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); + final LoanTransaction targetTxn = loan.getLoanTransaction(txn -> txn.getId() != null && txn.getId().equals(targetTransactionId)); + if (targetTxn == null) { + throw new LoanTransactionNotFoundException(targetTransactionId); + } + if (!(targetTxn.isMerchantIssuedRefund() || targetTxn.isPayoutRefund())) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type", "Only for refund transactions"); + } + if (targetTxn.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", "Refund transaction is reversed"); + } + final boolean alreadyExists = loan.getLoanTransactions().stream().anyMatch(txn -> txn.isInterestRefund() && !txn + .getLoanTransactionRelations(rel -> rel.getToTransaction() != null && rel.getToTransaction().equals(targetTxn)).isEmpty()); + if (alreadyExists) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists", + "Interest Refund already exists for this refund"); + } + + final InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan); + final Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(), + List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + final Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(), + List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + final BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount(); + final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes(); + final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND); + + return LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType, targetTxn.getTransactionDate(), + interestRefundAmount, paymentTypeOptions, loan.getCurrency().toData()); + } + private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) { final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan(); Money possibleInterestToWaive = totalInterestOutstanding.copy(); 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 af8ee32567..57e3a17368 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 @@ -98,6 +98,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction 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; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent; @@ -188,6 +189,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactio import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException; import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; @@ -210,6 +212,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommand import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentParameter; import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentService; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.note.domain.Note; @@ -3010,6 +3013,136 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } + @Transactional + @Override + public CommandProcessingResult makeManualInterestRefund(final Long loanId, final Long transactionId, final JsonCommand command) { + loanTransactionValidator.validateManualInterestRefundTransaction(command.json()); + Loan loan = loanAssembler.assembleFrom(loanId); + if (loan == null) { + throw new LoanNotFoundException(loanId); + } + final LoanTransaction targetTransaction = this.loanTransactionRepository.findByIdAndLoanId(transactionId, loanId) + .orElseThrow(() -> new LoanTransactionNotFoundException(transactionId, loanId)); + + final LocalDate transactionDate = targetTransaction.getDateOf(); + if (!(targetTransaction.isMerchantIssuedRefund() || targetTransaction.isPayoutRefund())) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type", + "Target transaction must be Merchant Issued Refund or Payout Refund"); + } + if (targetTransaction.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", "Target transaction is already reversed"); + } + boolean alreadyHasInterestRefund = loan.getLoanTransactions().stream() + .anyMatch(txn -> txn.isInterestRefund() + && !txn.getLoanTransactionRelations(rel -> rel.getToTransaction().equals(targetTransaction)).isEmpty() + && !txn.isReversed()); + if (alreadyHasInterestRefund) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists", + "Interest Refund already exists for this transaction"); + } + final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.amount.invalid", + "Amount must be provided and positive"); + } + + final boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream() + .map(LoanSupportedInterestRefundTypes::getTransactionType) + .anyMatch(transactionType -> transactionType.equals(targetTransaction.getTypeOf())); + if (!shouldCreateInterestRefundTransaction) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.not.supported", + "Interest Refund calculation is not supported for this loan"); + } + final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName); + + final Map<String, Object> changes = new LinkedHashMap<>(); + changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); + changes.put("locale", command.locale()); + changes.put("dateFormat", command.dateFormat()); + changes.put(LoanApiConstants.externalIdParameterName, txnExternalId); + + final LocalDate recalculateFrom = loan.isInterestBearingAndInterestRecalculationEnabled() ? transactionDate : null; + final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null); + + this.loanTransactionValidator.validateRefund(loan, LoanTransactionType.INTEREST_REFUND, transactionDate, scheduleGeneratorDTO); + + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + + createNote(loan, command, changes); + + final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan); + final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan); + + final LoanTransaction interestRefundTxn = loanAccountDomainService.createManualInterestRefundWithAmount(loan, targetTransaction, + amount, paymentDetail, txnExternalId); + + interestRefundTxn.getLoanTransactionRelations().add( + LoanTransactionRelation.linkToTransaction(interestRefundTxn, targetTransaction, LoanTransactionRelationTypeEnum.RELATED)); + + final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan, + interestRefundTxn); + final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate); + + final boolean processLatest = isTransactionChronologicallyLatest // + && !loan.isForeclosure() // + && !loan.hasChargesAffectedByBackdatedRepaymentLikeTransaction(interestRefundTxn) // + && loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, interestRefundTxn, currentInstallment); // + if (processLatest) { + loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), interestRefundTxn, + new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + loan.addLoanTransaction(interestRefundTxn); + } else { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { + loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); + } else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) + || loan.hasContractTerminationTransaction())) { + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); + } + loan.addLoanTransaction(interestRefundTxn); + reprocessLoanTransactionsService.reprocessTransactions(loan); + } + + // Update outstanding loan balances + loanBalanceService.updateLoanOutstandingBalances(loan); + + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTxn); + businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(interestRefundTxn)); + + // Accrual reprocessing + if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + loanAccrualsProcessingService.reprocessExistingAccruals(loan); + loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); + } + + loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate); + loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); + + loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, interestRefundTxn); + loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, interestRefundTxn); + + loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), + false); + + journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withLoanId(loan.getId()) // + .withEntityId(targetTransaction.getId()) // + .withEntityExternalId(targetTransaction.getExternalId()) // + .withSubEntityId(interestRefundTxn.getId()) // + .withSubEntityExternalId(interestRefundTxn.getExternalId()) // + .withOfficeId(loan.getOfficeId()) // + .withClientId(loan.getClientId()) // + .withGroupId(loan.getGroupId()) // + .with(changes) // + .build(); + } + public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) { loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction); @@ -3626,5 +3759,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.setExpectedMaturityDate(latestRepaymentDate); } } - } 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 70866c25c4..cdfcd8de02 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 @@ -110,6 +110,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate; import org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService; @@ -177,6 +178,7 @@ import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; @Configuration @@ -344,7 +346,8 @@ public class LoanAccountConfiguration { LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper, LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService, LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository, - LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository) { + LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, + @Lazy InterestRefundServiceDelegate interestRefundServiceDelegate) { return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository, loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService, fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService, @@ -352,7 +355,7 @@ public class LoanAccountConfiguration { loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator, delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService, loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService, - loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository); + loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate); } @Bean
