FINERACT-2327: Allow to create Interest Refund transaction manually
This commit is contained in:
committed by
Aleksandar Vidakovic
parent
59409550a7
commit
c2e19bb68f
+9
@@ -3885,4 +3885,13 @@ public class CommandWrapperBuilder {
|
|||||||
this.href = "/loans/" + loanId;
|
this.href = "/loans/" + loanId;
|
||||||
return this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -41,6 +41,7 @@ public enum TransactionType {
|
|||||||
BUY_DOWN_FEE("buyDownFee"), //
|
BUY_DOWN_FEE("buyDownFee"), //
|
||||||
BUY_DOWN_FEE_ADJUSTMENT("buyDownFeeAdjustment"), //
|
BUY_DOWN_FEE_ADJUSTMENT("buyDownFeeAdjustment"), //
|
||||||
BUY_DOWN_FEE_AMORTIZATION("buyDownFeeAmortization"), //
|
BUY_DOWN_FEE_AMORTIZATION("buyDownFeeAmortization"), //
|
||||||
|
INTEREST_REFUND("interestRefund"), //
|
||||||
;
|
;
|
||||||
|
|
||||||
public final String value;
|
public final String value;
|
||||||
|
|||||||
+4
@@ -68,6 +68,10 @@ public final class ErrorMessageHelper {
|
|||||||
return "Loan can't be disbursed, disburse amount is exceeding approved principal.";
|
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) {
|
public static String addDisbursementExceedMaxAppliedAmountFailure(String totalDisbAmount, String maxDisbursalAmount) {
|
||||||
return String.format("Loan disbursal amount can't be greater than maximum applied loan amount calculation. "
|
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);
|
+ "Total disbursed amount: %s Maximum disbursal amount: %s", totalDisbAmount, maxDisbursalAmount);
|
||||||
|
|||||||
+4
-1
@@ -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.LoanRefundPostBusinessEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionGoodwillCreditPostEvent;
|
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.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.LoanTransactionMakeRepaymentPostEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMerchantIssuedRefundPostEvent;
|
import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionMerchantIssuedRefundPostEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionPayoutRefundPostEvent;
|
import org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionPayoutRefundPostEvent;
|
||||||
@@ -308,7 +309,8 @@ public class EventCheckHelper {
|
|||||||
Response<PostLoansLoanIdTransactionsResponse> transactionResponse, TransactionType transactionType, String externalOwnerId)
|
Response<PostLoansLoanIdTransactionsResponse> transactionResponse, TransactionType transactionType, String externalOwnerId)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Long loanId = transactionResponse.body().getLoanId();
|
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();
|
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||||
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
||||||
GetLoansLoanIdTransactions transactionFound = transactions//
|
GetLoansLoanIdTransactions transactionFound = transactions//
|
||||||
@@ -324,6 +326,7 @@ public class EventCheckHelper {
|
|||||||
case MERCHANT_ISSUED_REFUND -> LoanTransactionMerchantIssuedRefundPostEvent.class;
|
case MERCHANT_ISSUED_REFUND -> LoanTransactionMerchantIssuedRefundPostEvent.class;
|
||||||
case REFUND_BY_CASH -> LoanRefundPostBusinessEvent.class;
|
case REFUND_BY_CASH -> LoanRefundPostBusinessEvent.class;
|
||||||
case INTEREST_PAYMENT_WAIVER -> LoanTransactionInterestPaymentWaiverPostEvent.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()));
|
default -> throw new IllegalStateException(String.format("transaction type %s cannot be found", transactionType.getValue()));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+27
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -433,6 +433,63 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
eventCheckHelper.loanBalanceChangedEventCheck(loanId);
|
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,
|
private void createTransactionWithAutoIdempotencyKeyAndWithExternalOwner(String transactionTypeInput, String transactionPaymentType,
|
||||||
String transactionDate, double transactionAmount, String externalOwnerId) throws IOException {
|
String transactionDate, double transactionAmount, String externalOwnerId) throws IOException {
|
||||||
eventStore.reset();
|
eventStore.reset();
|
||||||
@@ -4984,4 +5041,21 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
}
|
}
|
||||||
assertThat(developerMessage).isEqualTo(ErrorMessageHelper.updateApprovedLoanLessMinAllowedAmountFailure());
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -241,6 +241,7 @@ public abstract class TestContextKey {
|
|||||||
public static final String LOAN_DISBURSEMENT_DETAIL_RESPONSE = "loanDisbursementDetailResponse";
|
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_AMORTIZATION_ID = "loanCapitalizedIncomeAmortizationId";
|
||||||
public static final String LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE = "loanCapitalizedIncomeAdjustmentResponse";
|
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 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_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
|
||||||
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
|
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
|
||||||
|
|||||||
+198
@@ -334,3 +334,201 @@ Feature: MerchantIssuedRefund
|
|||||||
| 01 August 2024 | Accrual Activity | 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 |
|
| 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"
|
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
|
||||||
@@ -192,4 +192,202 @@ Feature: PayoutRefund
|
|||||||
| 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 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 | 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 |
|
| 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 |
|
| 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
|
||||||
+3
@@ -97,4 +97,7 @@ public interface LoanAccountDomainService {
|
|||||||
LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData loanRefundRequest);
|
LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData loanRefundRequest);
|
||||||
|
|
||||||
void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction transaction);
|
void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction transaction);
|
||||||
|
|
||||||
|
LoanTransaction createManualInterestRefundWithAmount(Loan loan, LoanTransaction targetTransaction, BigDecimal amount,
|
||||||
|
PaymentDetail paymentDetail, ExternalId txnExternalId);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -195,6 +195,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.INTEREST_REFUND, null, amount, date, externalId);
|
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,
|
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
|
||||||
final ExternalId externalId, PaymentDetail paymentDetail) {
|
final ExternalId externalId, PaymentDetail paymentDetail) {
|
||||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
|
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
|
||||||
|
|||||||
+42
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -94,4 +94,6 @@ public interface LoanTransactionValidator {
|
|||||||
void validateExternalId(DataValidatorBuilder baseDataValidator, JsonElement element);
|
void validateExternalId(DataValidatorBuilder baseDataValidator, JsonElement element);
|
||||||
|
|
||||||
void validateReversalExternalId(DataValidatorBuilder baseDataValidator, JsonElement element);
|
void validateReversalExternalId(DataValidatorBuilder baseDataValidator, JsonElement element);
|
||||||
|
|
||||||
|
void validateManualInterestRefundTransaction(String json);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -155,4 +155,6 @@ public interface LoanReadPlatformService {
|
|||||||
List<Long> retrieveLoanIdsByExternalIds(List<ExternalId> externalIds);
|
List<Long> retrieveLoanIdsByExternalIds(List<ExternalId> externalIds);
|
||||||
|
|
||||||
boolean existsByLoanId(Long loanId);
|
boolean existsByLoanId(Long loanId);
|
||||||
|
|
||||||
|
LoanTransactionData retrieveManualInterestRefundTemplate(Long loanId, Long targetTransactionId);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -125,4 +125,6 @@ public interface LoanWritePlatformService {
|
|||||||
CommandProcessingResult undoChargeOff(JsonCommand command);
|
CommandProcessingResult undoChargeOff(JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command);
|
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult makeManualInterestRefund(Long loanId, Long transactionId, JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -572,6 +572,11 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
|
|||||||
loanTransactionValidator.validateReversalExternalId(baseDataValidator, element);
|
loanTransactionValidator.validateReversalExternalId(baseDataValidator, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateManualInterestRefundTransaction(final String json) {
|
||||||
|
loanTransactionValidator.validateManualInterestRefundTransaction(json);
|
||||||
|
}
|
||||||
|
|
||||||
private Set<String> getCapitalizedIncomeParameters() {
|
private Set<String> getCapitalizedIncomeParameters() {
|
||||||
return new HashSet<>(
|
return new HashSet<>(
|
||||||
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
||||||
|
|||||||
+9
-2
@@ -93,6 +93,7 @@ public class LoanTransactionsApiResource {
|
|||||||
public static final String CAPITALIZED_INCOME = "capitalizedIncome";
|
public static final String CAPITALIZED_INCOME = "capitalizedIncome";
|
||||||
public static final String CAPITALIZED_INCOME_ADJUSTMENT = "capitalizedIncomeAdjustment";
|
public static final String CAPITALIZED_INCOME_ADJUSTMENT = "capitalizedIncomeAdjustment";
|
||||||
public static final String CONTRACT_TERMINATION = "contractTermination";
|
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",
|
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));
|
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=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||||
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
|
+ "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=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({
|
@ApiResponses({
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
@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,
|
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=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
|
||||||
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
|
+ "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=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({
|
@ApiResponses({
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
|
||||||
public String retrieveTransactionTemplate(
|
public String retrieveTransactionTemplate(
|
||||||
@@ -694,6 +697,8 @@ public class LoanTransactionsApiResource {
|
|||||||
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
|
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
|
||||||
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
|
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
|
||||||
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionId);
|
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionId);
|
||||||
|
} else if (CommandParameterUtil.is(commandParam, INTEREST_REFUND_COMMAND_VALUE)) {
|
||||||
|
transactionData = this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId, transactionId);
|
||||||
} else {
|
} else {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||||
}
|
}
|
||||||
@@ -718,6 +723,8 @@ public class LoanTransactionsApiResource {
|
|||||||
commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build();
|
commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build();
|
||||||
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
|
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
|
||||||
commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, resolvedTransactionId).build();
|
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
|
} else { // Default to adjust the Loan Transaction
|
||||||
commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build();
|
commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build();
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -206,7 +206,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final ExternalId txnExternalId = externalIdFactory.create();
|
final ExternalId txnExternalId = externalIdFactory.create();
|
||||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
|
||||||
return LoanTransaction.interestRefund(loan, interestRefundAmount, refundTransaction.getDateOf(), txnExternalId);
|
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
|
@Transactional
|
||||||
|
|||||||
+31
@@ -1089,4 +1089,35 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida
|
|||||||
baseDataValidator.reset().parameter("reversalExternalId").value(reversalExternalId).notExceedingLengthOf(100);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -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.api.ApiFacingEnum;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
|
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.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
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.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
@@ -192,6 +194,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
private final LoanBalanceService loanBalanceService;
|
private final LoanBalanceService loanBalanceService;
|
||||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||||
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
|
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
|
||||||
|
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanAccountData retrieveOne(final Long loanId) {
|
public LoanAccountData retrieveOne(final Long loanId) {
|
||||||
@@ -2448,6 +2451,39 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
return loanRepositoryWrapper.existsByLoanId(loanId);
|
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) {
|
private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
|
||||||
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
|
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
|
||||||
Money possibleInterestToWaive = totalInterestOutstanding.copy();
|
Money possibleInterestToWaive = totalInterestOutstanding.copy();
|
||||||
|
|||||||
+133
-1
@@ -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.LoanTransactionBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
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.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.LoanUndoChargeOffBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
|
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.InvalidPaidInAdvanceAmountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
|
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.LoanOfficerAssignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
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.LoanAdjustmentParameter;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentService;
|
import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentService;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
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.exception.LinkedAccountRequiredException;
|
||||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||||
import org.apache.fineract.portfolio.note.domain.Note;
|
import org.apache.fineract.portfolio.note.domain.Note;
|
||||||
@@ -3010,6 +3013,136 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.build();
|
.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) {
|
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
|
||||||
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
|
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
|
||||||
|
|
||||||
@@ -3626,5 +3759,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.setExpectedMaturityDate(latestRepaymentDate);
|
loan.setExpectedMaturityDate(latestRepaymentDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -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.GLIMAccountInfoReadPlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
|
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.LoanAccountServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService;
|
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -344,7 +346,8 @@ public class LoanAccountConfiguration {
|
|||||||
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
|
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
|
||||||
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
|
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
|
||||||
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
|
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
|
||||||
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository) {
|
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
|
||||||
|
@Lazy InterestRefundServiceDelegate interestRefundServiceDelegate) {
|
||||||
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
|
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
|
||||||
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
|
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
|
||||||
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
|
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
|
||||||
@@ -352,7 +355,7 @@ public class LoanAccountConfiguration {
|
|||||||
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
|
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
|
||||||
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
|
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
|
||||||
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
|
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
|
||||||
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository);
|
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
Reference in New Issue
Block a user