FINERACT-2107: update related relationship for interest refund transaction

This commit is contained in:
Oleksii Novikov
2024-11-08 23:13:00 +01:00
committed by Adam Saghy
parent 87ad93216e
commit 75ad6082d0
7 changed files with 107 additions and 2 deletions
@@ -69,7 +69,9 @@ public enum DefaultLoanProduct implements LoanProduct {
LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, //
LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY;//
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION, //
;
@Override
public String getName() {
@@ -990,6 +990,28 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT,
loanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyTillPrecloseLastInstallmentResponse);
// LP2 with progressive loan schedule + horizontal + interest EMI + actual/actual + interest refund with
// Merchant issued and Payment refund + interest recalculation
// (LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION)
String name48 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION.getName();
PostLoanProductsRequest loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculation = loanProductsRequestFactory
.defaultLoanProductsRequestLP2Emi()//
.isInterestRecalculationEnabled(true)//
.preClosureInterestCalculationStrategy(1)//
.rescheduleStrategyMethod(4)//
.interestRecalculationCompoundingMethod(0)//
.recalculationRestFrequencyType(2)//
.recalculationRestFrequencyInterval(1)//
.name(name48)//
.supportedInterestRefundTypes(Arrays.asList("MERCHANT_ISSUED_REFUND", "PAYOUT_REFUND"))//
.paymentAllocation(List.of(//
createPaymentAllocation("DEFAULT", "LAST_INSTALLMENT")));//
Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculation = loanProductsApi
.createLoanProduct(loanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundRecalculation).execute();
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION,
responseLoanProductsRequestLP2AdvancedpaymentInterestEmiActualActualInterestRefundInterestRecalculation);
}
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
@@ -22,6 +22,7 @@ import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.AD
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -41,6 +42,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -56,6 +58,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdDelinquencySummary;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargePaidByData;
import org.apache.fineract.client.models.GetLoansLoanIdLoanTermVariations;
import org.apache.fineract.client.models.GetLoansLoanIdLoanTransactionRelation;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -2498,6 +2501,29 @@ public class LoanStepDef extends AbstractStepDef {
});
}
@Then("In Loan Transactions the {string}th Transaction has relationship type={} with the {string}th Transaction")
public void loanTransactionsRelationshipCheck(String nthTransactionFromStr, String relationshipType, String nthTransactionToStr)
throws IOException {
final Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
final long loanId = loanCreateResponse.body().getLoanId();
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
final int nthTransactionFrom = nthTransactionFromStr == null ? transactions.size() - 1
: Integer.parseInt(nthTransactionFromStr) - 1;
final int nthTransactionTo = nthTransactionToStr == null ? transactions.size() - 1 : Integer.parseInt(nthTransactionToStr) - 1;
final GetLoansLoanIdTransactions transactionFrom = transactions.get(nthTransactionFrom);
final GetLoansLoanIdTransactions transactionTo = transactions.get(nthTransactionTo);
final Optional<GetLoansLoanIdLoanTransactionRelation> relationshipOptional = transactionFrom.getTransactionRelations().stream()
.filter(r -> r.getRelationType().equals(relationshipType))
.filter(r -> r.getToLoanTransaction().equals(transactionTo.getId())).findFirst();
assertTrue(relationshipOptional.isPresent(), "Missed relationship between transactions");
}
private void createCustomizedLoan(final List<String> loanData, final boolean withEmi) throws IOException {
final String loanProduct = loanData.get(0);
final String submitDate = loanData.get(1);
@@ -102,6 +102,7 @@ public abstract class TestContextKey {
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefund";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL = "loanProductCreateResponseLP1ProgressiveLoanScheduleHorizontal";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillPreCloseWholeTerm";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefundFInterestRecalculation";
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_CREATE_RESPONSE = "ChargeForLoanPercentLateCreateResponse";
public static final String CHARGE_FOR_LOAN_PERCENT_LATE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanPercentLateAmountPlusInterestCreateResponse";
public static final String CHARGE_FOR_LOAN_PERCENT_PROCESSING_CREATE_RESPONSE = "ChargeForLoanPercentProcessingCreateResponse";
@@ -3397,3 +3397,42 @@ Feature: LoanRepayment
When Admin makes Credit Balance Refund transaction on "10 September 2024" with 91.21 EUR transaction amount
When Customer undo "1"th "Repayment" transaction made on "24 June 2024"
Then Loan status will be "ACTIVE"
Scenario: Verify the relationship for Interest Refund transaction after repayment by reverting related transaction
When Admin sets the business date to "30 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION | 01 January 2024 | 200 | 15 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "200" amount and expected disbursement date on "01 January 2024"
And Admin successfully disburse the loan on "01 January 2024" with "200" EUR transaction amount
When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 January 2024" with 50 EUR transaction amount and self-generated Idempotency key
And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "16 January 2024" with 50 EUR transaction amount and self-generated Idempotency key
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false |
| 15 January 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 150.0 | false |
| 15 January 2024 | Interest Refund | 0.29 | 0.29 | 0.0 | 0.0 | 0.0 | 149.71 | false |
| 16 January 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 99.71 | false |
| 16 January 2024 | Interest Refund | 0.31 | 0.31 | 0.0 | 0.0 | 0.0 | 99.4 | false |
When Customer makes "AUTOPAY" repayment on "10 January 2024" with 25 EUR transaction amount (and transaction fails because of wrong date)
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false |
| 10 January 2024 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 175.0 | false |
| 15 January 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 125.0 | false |
| 15 January 2024 | Interest Refund | 0.29 | 0.29 | 0.0 | 0.0 | 0.0 | 124.71 | false |
| 16 January 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 74.71 | false |
| 16 January 2024 | Interest Refund | 0.31 | 0.31 | 0.0 | 0.0 | 0.0 | 74.4 | false |
Then In Loan Transactions the "4"th Transaction has relationship type=RELATED with the "3"th Transaction
Then In Loan Transactions the "6"th Transaction has relationship type=RELATED with the "5"th Transaction
When Customer undo "1"th "Merchant Issued Refund" transaction made on "15 January 2024"
When Customer undo "1"th "Payout Refund" transaction made on "16 January 2024"
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false |
| 10 January 2024 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 175.0 | false |
| 15 January 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 125.0 | true |
| 15 January 2024 | Interest Refund | 0.29 | 0.29 | 0.0 | 0.0 | 0.0 | 124.71 | true |
| 16 January 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 125.0 | true |
| 16 January 2024 | Interest Refund | 0.31 | 0.31 | 0.0 | 0.0 | 0.0 | 124.69 | true |
@@ -27,6 +27,7 @@ import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@Getter
@@ -38,6 +39,7 @@ public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCus
@JoinColumn(name = "from_loan_transaction_id", nullable = false)
private LoanTransaction fromTransaction;
@Setter
@ManyToOne
@JoinColumn(name = "to_loan_transaction_id")
private LoanTransaction toTransaction;
@@ -743,9 +743,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
return null;
}
protected void createNewTransaction(LoanTransaction oldTransaction, LoanTransaction newTransaction, TransactionCtx ctx) {
protected void createNewTransaction(final LoanTransaction oldTransaction, final LoanTransaction newTransaction,
final TransactionCtx ctx) {
oldTransaction.updateExternalId(null);
oldTransaction.getLoanChargesPaid().clear();
if (newTransaction.getTypeOf().isInterestRefund()) {
newTransaction.getLoanTransactionRelations().stream().filter(
r -> r.getToTransaction().getTypeOf().isMerchantIssuedRefund() || r.getToTransaction().getTypeOf().isPayoutRefund())
.filter(r -> r.getToTransaction().isReversed())
.forEach(newRelation -> oldTransaction.getLoanTransactionRelations().stream()
.filter(oldRelation -> LoanTransactionRelationTypeEnum.RELATED.equals(oldRelation.getRelationType()))
.findFirst().map(oldRelation -> oldRelation.getToTransaction().getId())
.ifPresent(oldToTransactionId -> newRelation.setToTransaction(
ctx.getChangedTransactionDetail().getNewTransactionMappings().get(oldToTransactionId))));
}
// Adding Replayed relation from newly created transaction to reversed transaction
newTransaction.getLoanTransactionRelations()
.add(LoanTransactionRelation.linkToTransaction(newTransaction, oldTransaction, LoanTransactionRelationTypeEnum.REPLAYED));