Skip to content

Commit

Permalink
FINERACT-2060: Accrual reverse replay logic and Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Marta Jankovics committed Nov 29, 2024
1 parent 479e71c commit c51a4c7
Show file tree
Hide file tree
Showing 83 changed files with 2,834 additions and 2,898 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.charge.service;

import java.util.Collection;
import java.util.List;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;

Expand Down Expand Up @@ -53,7 +54,7 @@ public interface ChargeReadPlatformService {
* Excludes Given List of Charge Types from the response
* @return
*/
Collection<ChargeData> retrieveLoanAccountApplicableCharges(Long loanId, ChargeTimeType[] excludeChargeTimes);
List<ChargeData> retrieveLoanAccountApplicableCharges(Long loanId, ChargeTimeType[] excludeChargeTimes);

/**
* Returns all charges applicable for a given loan product (filter based on Currency of Selected Loan Product)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,23 @@ public static int compare(OffsetDateTime first, OffsetDateTime second) {
}

public static int compare(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) {
return compare(first, second, truncate, true);
}

public static int compareWithNullsLast(OffsetDateTime first, OffsetDateTime second) {
return compare(first, second, null, false);
}

public static int compareWithNullsLast(@NotNull Optional<OffsetDateTime> first, @NotNull Optional<OffsetDateTime> second) {
return compareWithNullsLast(first.orElse(null), second.orElse(null));
}

public static int compare(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate, boolean nullFirst) {
if (first == null) {
return second == null ? 0 : -1;
return second == null ? 0 : (nullFirst ? -1 : 1);
}
if (second == null) {
return 1;
return nullFirst ? 1 : -1;
}
first = first.withOffsetSameInstant(ZoneOffset.UTC);
second = second.withOffsetSameInstant(ZoneOffset.UTC);
Expand Down Expand Up @@ -291,7 +303,23 @@ public static boolean isDateInTheFuture(final LocalDate localDate) {
}

public static int compare(LocalDate first, LocalDate second) {
return first == null ? (second == null ? 0 : -1) : (second == null ? 1 : first.compareTo(second));
return compare(first, second, true);
}

/**
* Comparing dates. Null will be considered as last elements
*
* @param first
* @param second
* @return
*/
public static int compareWithNullsLast(LocalDate first, LocalDate second) {
return compare(first, second, false);
}

public static int compare(LocalDate first, LocalDate second, boolean nullFirst) {
return first == null ? (second == null ? 0 : (nullFirst ? -1 : 1))
: (second == null ? (nullFirst ? 1 : -1) : first.compareTo(second));
}

public static boolean isEqual(LocalDate first, LocalDate second) {
Expand Down Expand Up @@ -325,6 +353,10 @@ public static int getExactDifferenceInDays(LocalDate first, LocalDate second) {
return getExactDifference(first, second, DAYS);
}

public static LocalDate minusDays(LocalDate first, int days) {
return first == null ? null : first.minusDays(days);
}

// Parse, format

public static LocalDate parseLocalDate(String stringDate) {
Expand Down Expand Up @@ -426,23 +458,4 @@ private static DateTimeFormatter getDateTimeFormatter(String format, Locale loca
}
return formatter;
}

/**
* Comparing dates. Null will be considered as last elements
*
* @param first
* @param second
* @return
*/
public static int compareWithNullsLast(LocalDate first, LocalDate second) {
return first == null ? (second == null ? 0 : 1) : (second == null ? -1 : first.compareTo(second));
}

public static int compareWithNullsLast(@NotNull Optional<OffsetDateTime> first, @NotNull Optional<OffsetDateTime> second) {
return DateUtils.compareWithNullsLast(first.orElse(null), second.orElse(null));
}

public static int compareWithNullsLast(OffsetDateTime first, OffsetDateTime second) {
return first == null ? (second == null ? 0 : 1) : (second == null ? -1 : first.compareTo(second));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public static BigDecimal abs(BigDecimal value) {
* if true then null parameter is omitted, otherwise returns null
*/
public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) {
return notNull ? first == null ? second : second == null ? first : min(first, second, false)
return notNull ? (first == null ? second : (second == null ? first : min(first, second, false)))
: isLessThan(first, second) ? first : second;
}

Expand Down Expand Up @@ -291,6 +291,11 @@ public static BigDecimal subtract(BigDecimal first, BigDecimal second) {
return subtract(first, second, MoneyHelper.getMathContext());
}

/** @return first minus the others considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second, BigDecimal third) {
return subtract(subtract(first, second), third);
}

/** @return first minus second considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second, MathContext mc) {
return first == null ? null : second == null ? first : first.subtract(second, mc);
Expand Down Expand Up @@ -336,6 +341,10 @@ public static String formatToSql(BigDecimal amount) {
return amount == null ? null : amount.toPlainString();
}

public static Money toMoney(BigDecimal amount, @NotNull MonetaryCurrency currency) {
return amount == null ? null : Money.of(currency, amount);
}

// ----------------- Money -----------------

public static BigDecimal toBigDecimal(Money value) {
Expand Down Expand Up @@ -454,6 +463,16 @@ public static Money min(Money first, Money second, Money third, boolean notNull)
return min(min(first, second, notNull), third, notNull);
}

/** @return Money null safe negate */
public static Money negate(Money amount) {
return negate(amount, MoneyHelper.getMathContext());
}

/** @return Money null safe negate */
public static Money negate(Money amount, MathContext mc) {
return isEmpty(amount) ? amount : amount.negated(mc);
}

/**
* Calculate percentage of a value
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public enum DefaultLoanProduct implements LoanProduct {
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_PRECLOSE, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_TILL_REST_FREQUENCY_DATE, //
LP2_ADV_CUSTOM_PAYMENT_ALLOC_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT, //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,47 +447,75 @@ public static String idempotencyKeyNoMatch(String actual, String expected) {
}

public static String wrongNumberOfLinesInRepaymentSchedule(int actual, int expected) {
return wrongNumberOfLinesInRepaymentSchedule(null, actual, expected);
}

public static String wrongNumberOfLinesInRepaymentSchedule(String resourceId, int actual, int expected) {
String actualStr = String.valueOf(actual);
String expectedStr = String.valueOf(expected);
return String.format("Number of lines in Repayment schedule is not correct. Actual value is: %s - Expected value is: %s", actualStr,
expectedStr);
String prefx = "Number of lines in Repayment schedule";
String postfx = " is not correct. Actual value is: %s - Expected value is: %s";
if (resourceId != null) {
return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr);
}
return String.format(prefx + postfx, actualStr, expectedStr);
}

public static String wrongValueInLineInRepaymentSchedule(int line, List<List<String>> actual, List<String> expected) {
return wrongValueInLineInRepaymentSchedule(null, line, actual, expected);
}

public static String wrongValueInLineInRepaymentSchedule(String resourceId, int line, List<List<String>> actual,
List<String> expected) {
String lineStr = String.valueOf(line);
String expectedStr = expected.toString();
StringBuilder sb = new StringBuilder();
for (List<String> innerList : actual) {
sb.append(innerList.toString());
sb.append(System.lineSeparator());
}

return String.format(
"%nWrong value in Repayment schedule tab line %s. %nActual values in line (with the same due date) are: %n%s %nExpected values in line: %n%s",
lineStr, sb.toString(), expectedStr);
String prefx = "%nWrong value in Repayment schedule";
String postfx = " tab line %s. %nActual values in line (with the same due date) are: %n%s %nExpected values in line: %n%s";
if (resourceId != null) {
return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr);
}
return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr);
}

public static String wrongValueInLineInTransactionsTab(int line, List<List<String>> actual, List<String> expected) {
return wrongValueInLineInTransactionsTab(null, line, actual, expected);
}

public static String wrongValueInLineInTransactionsTab(String resourceId, int line, List<List<String>> actual, List<String> expected) {
String lineStr = String.valueOf(line);
String expectedStr = expected.toString();
StringBuilder sb = new StringBuilder();
for (List<String> innerList : actual) {
sb.append(innerList.toString());
sb.append(System.lineSeparator());
}

return String.format(
"%nWrong value in Transactions tab line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s",
lineStr, sb.toString(), expectedStr);
String prefx = "%nWrong value in Transactions tab";
String postfx = " line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s";
if (resourceId != null) {
return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr);
}
return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr);
}

public static String nrOfLinesWrongInTransactionsTab(int actual, int expected) {
return nrOfLinesWrongInTransactionsTab(null, actual, expected);
}

public static String nrOfLinesWrongInTransactionsTab(String resourceId, int actual, int expected) {
String actualStr = String.valueOf(actual);
String expectedStr = String.valueOf(expected);

return String.format(
"%nNumber of lines does not match in Transactions tab and expected datatable. %nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n",
actualStr, expectedStr);
String prefx = "%nNumber of lines does not match in Transactions tab and expected datatable";
String postfx = ". %nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n";
if (resourceId != null) {
return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr);
}
return String.format(prefx + postfx, actualStr, expectedStr);
}

public static String wrongValueInLineInChargesTab(int line, List<List<String>> actual, List<String> expected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,49 @@ public void initialize() throws Exception {
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT,
responseLoanProductsRequestLP2AdvancedpaymentInterestRecalculation36030MultiDisburseDownPayment);

// LP2 with progressive loan schedule + horizontal + interest recalculation daily EMI + 360/30 + multi
// disbursement + custom default payment allocation order
// (LP2_ADV_CUSTOM_PAYMENT_ALLOC_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE)
String name50 = DefaultLoanProduct.LP2_ADV_CUSTOM_PAYMENT_ALLOC_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE.getName();
PostLoanProductsRequest loanProductsRequestLP2AdvCustomPaymentAllocationInterestRecalculationDailyEmi36030MultiDisburse = loanProductsRequestFactory
.defaultLoanProductsRequestLP2Emi()//
.name(name50)//
.daysInYearType(DaysInYearType.DAYS360.value)//
.daysInMonthType(DaysInMonthType.DAYS30.value)//
.isInterestRecalculationEnabled(true)//
.preClosureInterestCalculationStrategy(1)//
.rescheduleStrategyMethod(4)//
.interestRecalculationCompoundingMethod(0)//
.recalculationRestFrequencyType(2)//
.recalculationRestFrequencyInterval(1)//
.paymentAllocation(List.of(//
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT",
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST, //
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL), //
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")))//
.multiDisburseLoan(true)//
.disallowExpectedDisbursements(true)//
.maxTrancheCount(10)//
.outstandingLoanBalance(10000.0);//
Response<PostLoanProductsResponse> responseLoanProductsRequestLP2AdvCustomPaymentAllocationInterestRecalculationDaily36030MultiDisburse = loanProductsApi
.createLoanProduct(loanProductsRequestLP2AdvCustomPaymentAllocationInterestRecalculationDailyEmi36030MultiDisburse)
.execute();
TestContext.INSTANCE.set(
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE,
responseLoanProductsRequestLP2AdvCustomPaymentAllocationInterestRecalculationDaily36030MultiDisburse);
}

public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
Expand Down
Loading

0 comments on commit c51a4c7

Please sign in to comment.