FINERACT-2232: Capitalized income - amortization in case of Charge-off event
This commit is contained in:
committed by
Arnold Gálovics
parent
39062ad1a1
commit
4861ad19fd
+1
@@ -124,6 +124,7 @@ public enum DefaultLoanProduct implements LoanProduct {
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, //
|
||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSAL_CAPITALIZED_INCOME, //
|
||||
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, //
|
||||
;
|
||||
|
||||
@Override
|
||||
|
||||
+11
@@ -1628,4 +1628,15 @@ public class LoanProductsRequestFactory {
|
||||
.incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME));//
|
||||
}
|
||||
|
||||
public PostLoanProductsRequest defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithCapitalizedIncome() {
|
||||
return defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappings()//
|
||||
.enableIncomeCapitalization(true)//
|
||||
.enableAutoRepaymentForDownPayment(false)//
|
||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
|
||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
|
||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST)//
|
||||
.deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.DEFERRED_CAPITALIZED_INCOME))//
|
||||
.incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME));//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
@@ -521,6 +521,13 @@ public final class ErrorMessageHelper {
|
||||
resourceId, line, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongValueInLineInJournalEntry(String resourceId, int line, List<List<String>> actualList, List<String> expected) {
|
||||
String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator()));
|
||||
return String.format("%nWrong value in Journal entries of resource %s line %s." //
|
||||
+ "%nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s",
|
||||
resourceId, line, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongDataInJournalEntriesGlAccountType(int line, String actual, String expected) {
|
||||
return String.format("Wrong data in Journal entries, line %s / GL account type. " //
|
||||
+ "Actual value is: %s - But expected value is: %s", line, actual, expected);
|
||||
|
||||
+30
@@ -2509,6 +2509,36 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE,
|
||||
responseLoanProductsRequestLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncomeFee);
|
||||
|
||||
// LP2 + zero-interest chargeOff behaviour + progressive loan schedule + horizontal + delinquent charge-off
|
||||
// reason to GL account mapping + interest recalculation
|
||||
// capitalized income enabled
|
||||
final String name102 = DefaultLoanProduct.LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME
|
||||
.getName();
|
||||
final PostLoanProductsRequest loanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome = loanProductsRequestFactory
|
||||
.defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithCapitalizedIncome()//
|
||||
.name(name102)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.preClosureInterestCalculationStrategy(1)//
|
||||
.rescheduleStrategyMethod(4)//
|
||||
.interestRecalculationCompoundingMethod(0)//
|
||||
.recalculationRestFrequencyType(2)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
|
||||
.loanScheduleType("PROGRESSIVE") //
|
||||
.loanScheduleProcessingType("HORIZONTAL")//
|
||||
.interestRateFrequencyType(3)//
|
||||
.maxInterestRatePerPeriod(10.0)//
|
||||
.paymentAllocation(List.of(//
|
||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
|
||||
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||
final Response<PostLoanProductsResponse> responseLoanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome = loanProductsApi
|
||||
.createLoanProduct(loanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome).execute();
|
||||
TestContext.INSTANCE.set(
|
||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INT_CHARGE_OFF_DELINQUENT_REASON_INT_RECALC_CAPITALIZED_INCOME,
|
||||
responseLoanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome);
|
||||
}
|
||||
|
||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||
|
||||
+77
@@ -26,6 +26,7 @@ import java.io.IOException;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
|
||||
@@ -138,6 +139,82 @@ public class JournalEntriesStepDef extends AbstractStepDef {
|
||||
}
|
||||
}
|
||||
|
||||
@Then("Reversed loan capitalized income amortization transaction has the following Journal entries:")
|
||||
public void capitalizedIncomeAmortizationJournalEntryDataCheck(final DataTable table) {
|
||||
final long capitalizedIncomeAmortizationId = testContext().get(TestContextKey.LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID);
|
||||
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
assert loanResponse.body() != null;
|
||||
final long loanId = loanResponse.body().getLoanId();
|
||||
final String resourceId = String.valueOf(loanId);
|
||||
|
||||
List<JournalEntryTransactionItem> journalLinesActualList;
|
||||
final String transactionId = "L" + capitalizedIncomeAmortizationId;
|
||||
Response<GetJournalEntriesTransactionIdResponse> journalEntryDataResponse = null;
|
||||
try {
|
||||
journalEntryDataResponse = journalEntriesApi.retrieveAll1(//
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
transactionId, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
null, //
|
||||
loanId, //
|
||||
null, //
|
||||
null, //
|
||||
true//
|
||||
).execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse);
|
||||
} catch (IOException e) {
|
||||
log.error("Exception", e);
|
||||
}
|
||||
assert journalEntryDataResponse != null;
|
||||
assert journalEntryDataResponse.body() != null;
|
||||
journalLinesActualList = journalEntryDataResponse.body().getPageItems();
|
||||
|
||||
final List<List<String>> data = table.asLists();
|
||||
for (int i = 1; i < data.size(); i++) {
|
||||
final List<List<String>> possibleActualValuesList = new ArrayList<>();
|
||||
final List<String> expectedValues = data.get(i);
|
||||
boolean containsAnyExpected = false;
|
||||
|
||||
for (int j = 0; j < Objects.requireNonNull(journalLinesActualList).size(); j++) {
|
||||
final JournalEntryTransactionItem journalLinesActual = journalLinesActualList.get(j);
|
||||
final List<String> actualValues = new ArrayList<>();
|
||||
assert journalLinesActual.getGlAccountType() != null;
|
||||
actualValues.add(
|
||||
journalLinesActual.getGlAccountType().getValue() == null ? null : journalLinesActual.getGlAccountType().getValue());
|
||||
actualValues.add(journalLinesActual.getGlAccountCode() == null ? null : journalLinesActual.getGlAccountCode());
|
||||
actualValues.add(journalLinesActual.getGlAccountName() == null ? null : journalLinesActual.getGlAccountName());
|
||||
assert journalLinesActual.getEntryType() != null;
|
||||
actualValues
|
||||
.add("DEBIT".equals(journalLinesActual.getEntryType().getValue()) ? String.valueOf(journalLinesActual.getAmount())
|
||||
: null);
|
||||
actualValues
|
||||
.add("CREDIT".equals(journalLinesActual.getEntryType().getValue()) ? String.valueOf(journalLinesActual.getAmount())
|
||||
: null);
|
||||
possibleActualValuesList.add(actualValues);
|
||||
|
||||
final boolean containsExpectedValues = possibleActualValuesList.stream()
|
||||
.anyMatch(actualValue -> actualValue.equals(expectedValues));
|
||||
if (containsExpectedValues) {
|
||||
containsAnyExpected = true;
|
||||
}
|
||||
}
|
||||
assertThat(containsAnyExpected)
|
||||
.as(ErrorMessageHelper.wrongValueInLineInJournalEntry(resourceId, i, possibleActualValuesList, expectedValues))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Then("In Loan transactions the replayed {string} transaction with date {string} has a reverted transaction pair with the following Journal entries:")
|
||||
public void revertedJournalEntryDataCheck(String transactionType, String transactionDate, DataTable table) throws IOException {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
|
||||
+11
@@ -1692,6 +1692,17 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(chargeOffResponse);
|
||||
|
||||
Long transactionId = chargeOffResponse.body().getResourceId();
|
||||
|
||||
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||
assert loanDetailsResponse.body() != null;
|
||||
final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
||||
final Optional<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
|
||||
.filter(t -> formatter.format(t.getDate()).equals(transactionDate) && t.getType().getCapitalizedIncomeAmortization())
|
||||
.findFirst();
|
||||
if (transactionsMatch.isPresent()) {
|
||||
testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID, transactionsMatch.get().getId());
|
||||
}
|
||||
eventAssertion.assertEvent(LoanChargeOffEvent.class, transactionId).extractingData(LoanTransactionDataV1::getLoanId)
|
||||
.isEqualTo(loanId).extractingData(LoanTransactionDataV1::getId).isEqualTo(chargeOffResponse.body().getResourceId());
|
||||
}
|
||||
|
||||
+2
@@ -149,6 +149,7 @@ public abstract class TestContextKey {
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncome";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncomeFee";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSAL_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcMultidisbursalCapitalizedIncome";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INT_CHARGE_OFF_DELINQUENT_REASON_INT_RECALC_CAPITALIZED_INCOME = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffDelinquentReasonInterestRecalculationCapitalizedIncome";
|
||||
public static final String LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_INTEREST_FIRST_RESPONSE = "loanProductCreateResponseLP2NoInterestRecalculationChargebackAllocationInterestFirst";
|
||||
public static final String LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_PRINCIPAL_FIRST_RESPONSE = "loanProductCreateResponseLP2NoInterestRecalculationChargebackAllocationPrincipalFirst";
|
||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR_LAST_INSTALLMENT_STRATEGY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationAccelerateMaturityChargeOffBehaviourLastInstallmentStrategy";
|
||||
@@ -209,4 +210,5 @@ public abstract class TestContextKey {
|
||||
public static final String LOAN_TRANSACTION_RESPONSE = "loanTransactionResponse";
|
||||
public static final String LOAN_CAPITALIZED_INCOME_RESPONSE = "loanCapitalizedIncomeResponse";
|
||||
public static final String LOAN_DISBURSEMENT_DETAIL_RESPONSE = "loanDisbursementDetailResponse";
|
||||
public static final String LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID = "loanCapitalizedIncomeAmortizationId";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user