FINERACT-2060: Accrual reverse replay logic and Handling
This commit is contained in:
committed by
Adam Saghy
parent
8d25b0c765
commit
d57e72ec2f
+2
-1
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.charge.service;
|
package org.apache.fineract.portfolio.charge.service;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ public interface ChargeReadPlatformService {
|
|||||||
* Excludes Given List of Charge Types from the response
|
* Excludes Given List of Charge Types from the response
|
||||||
* @return
|
* @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)
|
* Returns all charges applicable for a given loan product (filter based on Currency of Selected Loan Product)
|
||||||
|
|||||||
+35
-22
@@ -193,11 +193,23 @@ public final class DateUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int compare(OffsetDateTime first, OffsetDateTime second, ChronoUnit truncate) {
|
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) {
|
if (first == null) {
|
||||||
return second == null ? 0 : -1;
|
return second == null ? 0 : (nullFirst ? -1 : 1);
|
||||||
}
|
}
|
||||||
if (second == null) {
|
if (second == null) {
|
||||||
return 1;
|
return nullFirst ? 1 : -1;
|
||||||
}
|
}
|
||||||
first = first.withOffsetSameInstant(ZoneOffset.UTC);
|
first = first.withOffsetSameInstant(ZoneOffset.UTC);
|
||||||
second = second.withOffsetSameInstant(ZoneOffset.UTC);
|
second = second.withOffsetSameInstant(ZoneOffset.UTC);
|
||||||
@@ -291,7 +303,23 @@ public final class DateUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int compare(LocalDate first, LocalDate second) {
|
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) {
|
public static boolean isEqual(LocalDate first, LocalDate second) {
|
||||||
@@ -325,6 +353,10 @@ public final class DateUtils {
|
|||||||
return getExactDifference(first, second, DAYS);
|
return getExactDifference(first, second, DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LocalDate minusDays(LocalDate first, int days) {
|
||||||
|
return first == null ? null : first.minusDays(days);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse, format
|
// Parse, format
|
||||||
|
|
||||||
public static LocalDate parseLocalDate(String stringDate) {
|
public static LocalDate parseLocalDate(String stringDate) {
|
||||||
@@ -426,23 +458,4 @@ public final class DateUtils {
|
|||||||
}
|
}
|
||||||
return formatter;
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-1
@@ -232,7 +232,7 @@ public final class MathUtil {
|
|||||||
* if true then null parameter is omitted, otherwise returns null
|
* if true then null parameter is omitted, otherwise returns null
|
||||||
*/
|
*/
|
||||||
public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) {
|
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;
|
: isLessThan(first, second) ? first : second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +291,11 @@ public final class MathUtil {
|
|||||||
return subtract(first, second, MoneyHelper.getMathContext());
|
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 */
|
/** @return first minus second considering null values, maybe negative */
|
||||||
public static BigDecimal subtract(BigDecimal first, BigDecimal second, MathContext mc) {
|
public static BigDecimal subtract(BigDecimal first, BigDecimal second, MathContext mc) {
|
||||||
return first == null ? null : second == null ? first : first.subtract(second, mc);
|
return first == null ? null : second == null ? first : first.subtract(second, mc);
|
||||||
@@ -336,6 +341,10 @@ public final class MathUtil {
|
|||||||
return amount == null ? null : amount.toPlainString();
|
return amount == null ? null : amount.toPlainString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Money toMoney(BigDecimal amount, @NotNull MonetaryCurrency currency) {
|
||||||
|
return amount == null ? null : Money.of(currency, amount);
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------- Money -----------------
|
// ----------------- Money -----------------
|
||||||
|
|
||||||
public static BigDecimal toBigDecimal(Money value) {
|
public static BigDecimal toBigDecimal(Money value) {
|
||||||
@@ -454,6 +463,16 @@ public final class MathUtil {
|
|||||||
return min(min(first, second, notNull), third, 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
|
* Calculate percentage of a value
|
||||||
*
|
*
|
||||||
|
|||||||
+1
@@ -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_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_DAILY_TILL_REST_FREQUENCY_DATE, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SARP_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_ACTUAL_ACTUAL_INTEREST_REFUND_FULL, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT, //
|
||||||
|
|||||||
+41
-13
@@ -447,13 +447,26 @@ public final class ErrorMessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String wrongNumberOfLinesInRepaymentSchedule(int actual, int 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 actualStr = String.valueOf(actual);
|
||||||
String expectedStr = String.valueOf(expected);
|
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,
|
String prefx = "Number of lines in Repayment schedule";
|
||||||
expectedStr);
|
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) {
|
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 lineStr = String.valueOf(line);
|
||||||
String expectedStr = expected.toString();
|
String expectedStr = expected.toString();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -461,13 +474,19 @@ public final class ErrorMessageHelper {
|
|||||||
sb.append(innerList.toString());
|
sb.append(innerList.toString());
|
||||||
sb.append(System.lineSeparator());
|
sb.append(System.lineSeparator());
|
||||||
}
|
}
|
||||||
|
String prefx = "%nWrong value in Repayment schedule";
|
||||||
return String.format(
|
String postfx = " tab line %s. %nActual values in line (with the same due date) are: %n%s %nExpected values in line: %n%s";
|
||||||
"%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",
|
if (resourceId != null) {
|
||||||
lineStr, sb.toString(), expectedStr);
|
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) {
|
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 lineStr = String.valueOf(line);
|
||||||
String expectedStr = expected.toString();
|
String expectedStr = expected.toString();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -475,19 +494,28 @@ public final class ErrorMessageHelper {
|
|||||||
sb.append(innerList.toString());
|
sb.append(innerList.toString());
|
||||||
sb.append(System.lineSeparator());
|
sb.append(System.lineSeparator());
|
||||||
}
|
}
|
||||||
|
String prefx = "%nWrong value in Transactions tab";
|
||||||
return String.format(
|
String postfx = " line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s";
|
||||||
"%nWrong value in Transactions tab line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s",
|
if (resourceId != null) {
|
||||||
lineStr, sb.toString(), expectedStr);
|
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) {
|
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 actualStr = String.valueOf(actual);
|
||||||
String expectedStr = String.valueOf(expected);
|
String expectedStr = String.valueOf(expected);
|
||||||
|
|
||||||
return String.format(
|
String prefx = "%nNumber of lines does not match in Transactions tab and expected datatable";
|
||||||
"%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",
|
String postfx = ". %nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n";
|
||||||
actualStr, expectedStr);
|
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) {
|
public static String wrongValueInLineInChargesTab(int line, List<List<String>> actual, List<String> expected) {
|
||||||
|
|||||||
+43
@@ -1045,6 +1045,49 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
TestContext.INSTANCE.set(
|
TestContext.INSTANCE.set(
|
||||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT,
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT,
|
||||||
responseLoanProductsRequestLP2AdvancedpaymentInterestRecalculation36030MultiDisburseDownPayment);
|
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,
|
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||||
|
|||||||
+13
-7
@@ -1613,8 +1613,10 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
|
|
||||||
@Then("Loan Repayment schedule has {int} periods, with the following data for periods:")
|
@Then("Loan Repayment schedule has {int} periods, with the following data for periods:")
|
||||||
public void loanRepaymentSchedulePeriodsCheck(int linesExpected, DataTable table) throws IOException {
|
public void loanRepaymentSchedulePeriodsCheck(int linesExpected, DataTable table) throws IOException {
|
||||||
|
|
||||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||||
long loanId = loanCreateResponse.body().getLoanId();
|
long loanId = loanCreateResponse.body().getLoanId();
|
||||||
|
String resourceId = String.valueOf(loanId);
|
||||||
|
|
||||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "repaymentSchedule", "", "").execute();
|
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "repaymentSchedule", "", "").execute();
|
||||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||||
@@ -1634,9 +1636,9 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
|
|
||||||
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
||||||
assertThat(containsExpectedValues)
|
assertThat(containsExpectedValues)
|
||||||
.as(ErrorMessageHelper.wrongValueInLineInRepaymentSchedule(i, actualValuesList, expectedValues)).isTrue();
|
.as(ErrorMessageHelper.wrongValueInLineInRepaymentSchedule(resourceId, i, actualValuesList, expectedValues)).isTrue();
|
||||||
|
|
||||||
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInRepaymentSchedule(linesActual, linesExpected))
|
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInRepaymentSchedule(resourceId, linesActual, linesExpected))
|
||||||
.isEqualTo(linesExpected);
|
.isEqualTo(linesExpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1660,6 +1662,7 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
public void loanTransactionsTransactionWithGivenDateDataCheck(String date, DataTable table) throws IOException {
|
public void loanTransactionsTransactionWithGivenDateDataCheck(String date, DataTable table) throws IOException {
|
||||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||||
long loanId = loanCreateResponse.body().getLoanId();
|
long loanId = loanCreateResponse.body().getLoanId();
|
||||||
|
String resourceId = String.valueOf(loanId);
|
||||||
|
|
||||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||||
@@ -1673,14 +1676,16 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
.map(t -> fetchValuesOfTransaction(data.get(0), t)).collect(Collectors.toList());
|
.map(t -> fetchValuesOfTransaction(data.get(0), t)).collect(Collectors.toList());
|
||||||
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
||||||
|
|
||||||
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInTransactionsTab(1, actualValuesList, expectedValues))
|
assertThat(containsExpectedValues)
|
||||||
.isTrue();
|
.as(ErrorMessageHelper.wrongValueInLineInTransactionsTab(resourceId, 1, actualValuesList, expectedValues)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Then("Loan Transactions tab has the following data:")
|
@Then("Loan Transactions tab has the following data:")
|
||||||
public void loanTransactionsTabCheck(DataTable table) throws IOException {
|
public void loanTransactionsTabCheck(DataTable table) throws IOException {
|
||||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||||
long loanId = loanCreateResponse.body().getLoanId();
|
long loanId = loanCreateResponse.body().getLoanId();
|
||||||
|
String resourceId = String.valueOf(loanId);
|
||||||
|
|
||||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||||
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
||||||
@@ -1694,10 +1699,11 @@ public class LoanStepDef extends AbstractStepDef {
|
|||||||
.collect(Collectors.toList());//
|
.collect(Collectors.toList());//
|
||||||
boolean containsExpectedValues = actualValuesList.stream()//
|
boolean containsExpectedValues = actualValuesList.stream()//
|
||||||
.anyMatch(actualValues -> actualValues.equals(expectedValues));//
|
.anyMatch(actualValues -> actualValues.equals(expectedValues));//
|
||||||
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInTransactionsTab(i, actualValuesList, expectedValues))
|
assertThat(containsExpectedValues)
|
||||||
.isTrue();
|
.as(ErrorMessageHelper.wrongValueInLineInTransactionsTab(resourceId, i, actualValuesList, expectedValues)).isTrue();
|
||||||
}
|
}
|
||||||
assertThat(transactions.size()).as(ErrorMessageHelper.nrOfLinesWrongInTransactionsTab(transactions.size(), data.size() - 1))
|
assertThat(transactions.size())
|
||||||
|
.as(ErrorMessageHelper.nrOfLinesWrongInTransactionsTab(resourceId, transactions.size(), data.size() - 1))
|
||||||
.isEqualTo(data.size() - 1);
|
.isEqualTo(data.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -96,6 +96,7 @@ public abstract class TestContextKey {
|
|||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_REST_FREQUENCY_DATE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillRestFrequencyDate";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_SAME_AS_REP_TILL_REST_FREQUENCY_DATE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillRestFrequencyDate";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefundFull";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefundFull";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburse";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburse";
|
||||||
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE = "loanProductCreateResponseLP2AdvancedCustomPaymentInterestRecalculationDailyEmi36030MultiDisburse";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburseDownPayment";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030MultiDisburseDownPayment";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi365Actual";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_365_ACTUAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi365Actual";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030DownPayment";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_DOWNPAYMENT = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030DownPayment";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5989,6 +5989,7 @@ Feature: Loan
|
|||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
||||||
| 15 February 2024 | Repayment | 83.81 | 83.57 | 0.24 | 0.0 | 0.0 | 0.0 | false |
|
| 15 February 2024 | Repayment | 83.81 | 83.57 | 0.24 | 0.0 | 0.0 | 0.0 | false |
|
||||||
|
| 15 February 2024 | Accrual | 0.82 | 0.0 | 0.82 | 0.0 | 0.0 | 0.0 | false |
|
||||||
Then Loan's all installments have obligations met
|
Then Loan's all installments have obligations met
|
||||||
|
|
||||||
Scenario: Early pay-off loan with interest, TILL_REST_FREQUENCY_DATE product
|
Scenario: Early pay-off loan with interest, TILL_REST_FREQUENCY_DATE product
|
||||||
@@ -6049,6 +6050,7 @@ Feature: Loan
|
|||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||||
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
|
||||||
| 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
|
| 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
|
||||||
|
| 15 February 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false |
|
||||||
Then Loan's all installments have obligations met
|
Then Loan's all installments have obligations met
|
||||||
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
|
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
|
||||||
|
|
||||||
|
|||||||
+192
-24
@@ -1329,12 +1329,12 @@ Feature: LoanAccrualActivity
|
|||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 1000.0 | 4.1 | 0.0 | 0.0 | 1004.1 | 0.0 | 0.0 | 0.0 | 1004.1 |
|
| 1000.0 | 4.1 | 0.0 | 0.0 | 1004.1 | 0.0 | 0.0 | 0.0 | 1004.1 |
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false |
|
| 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false |
|
||||||
| 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false |
|
| 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
# --- Accrual activity ---
|
# --- Accrual activity ---
|
||||||
When Admin sets the business date to "07 January 2024"
|
When Admin sets the business date to "07 January 2024"
|
||||||
When Admin runs inline COB job for Loan
|
When Admin runs inline COB job for Loan
|
||||||
@@ -1349,15 +1349,15 @@ Feature: LoanAccrualActivity
|
|||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 1000.0 | 4.1 | 0.0 | 0.0 | 1004.1 | 0.0 | 0.0 | 0.0 | 1004.1 |
|
| 1000.0 | 4.1 | 0.0 | 0.0 | 1004.1 | 0.0 | 0.0 | 0.0 | 1004.1 |
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
| 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 02 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 03 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false |
|
| 04 January 2024 | Repayment | 1004.1 | 1000.0 | 4.1 | 0.0 | 0.0 | 0.0 | true | false |
|
||||||
| 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false |
|
| 04 January 2024 | Accrual | 0.32 | 0.0 | 0.32 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 05 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 05 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 06 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
| 06 January 2024 | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 06 January 2024 | Accrual Activity | 1.64 | 0.0 | 1.64 | 0.0 | 0.0 | 0.0 | false | false |
|
| 06 January 2024 | Accrual Activity | 1.64 | 0.0 | 1.64 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
|
||||||
@TestRailId:C3188
|
@TestRailId:C3188
|
||||||
Scenario: Verify accrual activity - UC16: Preclose, loan account partially paid before first installment due date, fully paid after first installment date, reopen by undo 1st repayment
|
Scenario: Verify accrual activity - UC16: Preclose, loan account partially paid before first installment due date, fully paid after first installment date, reopen by undo 1st repayment
|
||||||
@@ -1775,15 +1775,15 @@ Feature: LoanAccrualActivity
|
|||||||
When Admin sets the business date to "07 January 2024"
|
When Admin sets the business date to "07 January 2024"
|
||||||
When Admin runs inline COB job for Loan
|
When Admin runs inline COB job for Loan
|
||||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
Then Loan Repayment schedule has 4 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 |
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
| | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
| 1 | 5 | 06 January 2024 | | 750.64 | 249.36 | 1.64 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
| 1 | 5 | 06 January 2024 | | 750.64 | 249.36 | 1.64 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
||||||
| 2 | 5 | 11 January 2024 | | 500.96 | 249.68 | 1.32 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
| 2 | 5 | 11 January 2024 | | 500.96 | 249.68 | 1.32 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
||||||
| 3 | 5 | 16 January 2024 | | 250.78 | 250.18 | 0.82 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
| 3 | 5 | 16 January 2024 | | 250.78 | 250.18 | 0.82 | 0.0 | 0.0 | 251.0 | 0.0 | 0.0 | 0.0 | 251.0 |
|
||||||
| 4 | 5 | 21 January 2024 | | 0.0 | 250.78 | 0.41 | 0.0 | 0.0 | 251.19| 0.0 | 0.0 | 0.0 | 251.19 |
|
| 4 | 5 | 21 January 2024 | | 0.0 | 250.78 | 0.41 | 0.0 | 0.0 | 251.19 | 0.0 | 0.0 | 0.0 | 251.19 |
|
||||||
Then Loan Repayment schedule has the following data in Total row:
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 1000.0 | 4.19 | 0.0 | 0.0 | 1004.19| 0.0 | 0.0 | 0.0 | 1004.19 |
|
| 1000.0 | 4.19 | 0.0 | 0.0 | 1004.19 | 0.0 | 0.0 | 0.0 | 1004.19 |
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
@@ -2045,7 +2045,175 @@ Feature: LoanAccrualActivity
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
| 22 April 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | false |
|
| 22 April 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | false |
|
||||||
| 22 April 2024 | Accrual | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 0.0 | false | false |
|
|
||||||
| 22 April 2024 | Repayment | 600.0 | 400.0 | 0.0 | 0.0 | 30.0 | 0.0 | true | true |
|
| 22 April 2024 | Repayment | 600.0 | 400.0 | 0.0 | 0.0 | 30.0 | 0.0 | true | true |
|
||||||
| 22 April 2024 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true |
|
| 22 April 2024 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true |
|
||||||
|
| 09 October 2024 | Accrual | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 0.0 | false | false |
|
||||||
| 10 October 2024 | Charge Adjustment | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true |
|
| 10 October 2024 | Charge Adjustment | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 400.0 | false | true |
|
||||||
|
|
||||||
|
@TestRailId:C3274
|
||||||
|
Scenario: Verify accrual activity for repayment reversal on the progressive loan
|
||||||
|
When Admin sets the business date to "01 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_CUSTOM_PAYMENT_ALLOC_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE | 01 January 2024 | 1000 | 26 | 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 "1000" amount and expected disbursement date on "01 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
When Admin sets the business date to "14 January 2024"
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
When Admin sets the business date to "15 January 2024"
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
When Customer makes "AUTOPAY" repayment on "15 January 2024" with 263.69 EUR transaction amount
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | 15 January 2024 | 746.1 | 253.9 | 9.79 | 0.0 | 0.0 | 263.69 | 263.69 | 263.69 | 0.0 | 0.0 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 507.44 | 238.66 | 25.03 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 254.74 | 252.7 | 10.99 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 254.74 | 5.52 | 0.0 | 0.0 | 260.26 | 0.0 | 0.0 | 0.0 | 260.26 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 51.33 | 0.0 | 0.0 | 1051.33 | 263.69 | 263.69 | 0.0 | 787.64 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 15 January 2024 | Repayment | 263.69 | 253.9 | 9.79 | 0.0 | 0.0 | 746.1 | false | false |
|
||||||
|
When Admin sets the business date to "21 January 2024"
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 15 January 2024 | Repayment | 263.69 | 253.9 | 9.79 | 0.0 | 0.0 | 746.1 | false | false |
|
||||||
|
| 15 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 16 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 17 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 18 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 19 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 20 January 2024 | Accrual | 0.53 | 0.0 | 0.53 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
When Customer undo "1"th repayment on "15 January 2024"
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 15 January 2024 | Repayment | 263.69 | 253.9 | 9.79 | 0.0 | 0.0 | 746.1 | true | false |
|
||||||
|
| 15 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 16 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 17 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 18 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 19 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 20 January 2024 | Accrual | 0.53 | 0.0 | 0.53 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
When Admin sets the business date to "23 January 2024"
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 15 January 2024 | Repayment | 263.69 | 253.9 | 9.79 | 0.0 | 0.0 | 746.1 | true | false |
|
||||||
|
| 15 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 16 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 17 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 18 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 19 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 20 January 2024 | Accrual | 0.53 | 0.0 | 0.53 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 21 January 2024 | Accrual | 1.58 | 0.0 | 1.58 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 22 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
When Admin sets the business date to "28 January 2024"
|
||||||
|
And Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 4 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 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 757.98 | 242.02 | 21.67 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 510.71 | 247.27 | 16.42 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 258.09 | 252.62 | 11.07 | 0.0 | 0.0 | 263.69 | 0.0 | 0.0 | 0.0 | 263.69 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 0.0 | 258.09 | 5.59 | 0.0 | 0.0 | 263.68 | 0.0 | 0.0 | 0.0 | 263.68 |
|
||||||
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 1000.0 | 54.75 | 0.0 | 0.0 | 1054.75 | 0.0 | 0.0 | 0.0 | 1054.75 |
|
||||||
|
Then Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||||
|
| 13 January 2024 | Accrual | 8.39 | 0.0 | 8.39 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 14 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 15 January 2024 | Repayment | 263.69 | 253.9 | 9.79 | 0.0 | 0.0 | 746.1 | true | false |
|
||||||
|
| 15 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 16 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 17 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 18 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 19 January 2024 | Accrual | 0.52 | 0.0 | 0.52 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 20 January 2024 | Accrual | 0.53 | 0.0 | 0.53 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 21 January 2024 | Accrual | 1.58 | 0.0 | 1.58 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 22 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 23 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 24 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 25 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 26 January 2024 | Accrual | 0.7 | 0.0 | 0.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
| 27 January 2024 | Accrual | 0.69 | 0.0 | 0.69 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
+5
-4
@@ -881,7 +881,8 @@ Feature: LoanAccrualTransaction
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 17 April 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 |
|
| 17 April 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 |
|
||||||
| 17 April 2024 | Waive loan charges | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 |
|
| 17 April 2024 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 |
|
||||||
|
| 17 April 2024 | Waive loan charges | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 750.0 |
|
||||||
| 19 April 2024 | Accrual | 55.0 | 0.0 | 0.0 | 0.0 | 55.0 | 0.0 |
|
| 19 April 2024 | Accrual | 55.0 | 0.0 | 0.0 | 0.0 | 55.0 | 0.0 |
|
||||||
| 20 April 2024 | Repayment | 55.0 | 55.0 | 0.0 | 0.0 | 0.0 | 695.0 |
|
| 20 April 2024 | Repayment | 55.0 | 55.0 | 0.0 | 0.0 | 0.0 | 695.0 |
|
||||||
| 20 April 2024 | Repayment | 810.0 | 695.0 | 0.0 | 0.0 | 115.0 | 0.0 |
|
| 20 April 2024 | Repayment | 810.0 | 695.0 | 0.0 | 0.0 | 115.0 | 0.0 |
|
||||||
@@ -921,8 +922,8 @@ Feature: LoanAccrualTransaction
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 05 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 05 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
||||||
|
| 20 April 2024 | Accrual | 2.47 | 0.0 | 2.47 | 0.0 | 0.0 | 0.0 |
|
||||||
| 24 April 2024 | Accrual | 0.66 | 0.0 | 0.66 | 0.0 | 0.0 | 0.0 |
|
| 24 April 2024 | Accrual | 0.66 | 0.0 | 0.66 | 0.0 | 0.0 | 0.0 |
|
||||||
| 25 April 2024 | Accrual | 2.47 | 0.0 | 2.47 | 0.0 | 0.0 | 0.0 |
|
|
||||||
Then Loan Repayment schedule has the following data in Total row:
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 500.0 | 5.77 | 0.0 | 0.0 | 505.77 | 0.0 | 0.0 | 0.0 | 505.77 |
|
| 500.0 | 5.77 | 0.0 | 0.0 | 505.77 | 0.0 | 0.0 | 0.0 | 505.77 |
|
||||||
@@ -938,9 +939,9 @@ Feature: LoanAccrualTransaction
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 05 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 05 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
||||||
| 25 April 2024 | Accrual | 2.47 | 0.0 | 2.47 | 0.0 | 0.0 | 0.0 |
|
| 20 April 2024 | Accrual | 2.47 | 0.0 | 2.47 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| 24 April 2024 | Accrual | 1.05 | 0.0 | 1.05 | 0.0 | 0.0 | 0.0 |
|
||||||
| 26 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 26 April 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 26 April 2024 | Accrual | 2.47 | 0.0 | 2.47 | 0.0 | 0.0 | 0.0 |
|
|
||||||
Then Loan Repayment schedule has the following data in Total row:
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 1000.0 | 8.06 | 0.0 | 0.0 | 1008.06 | 0.0 | 0.0 | 0.0 | 1008.06 |
|
| 1000.0 | 8.06 | 0.0 | 0.0 | 1008.06 | 0.0 | 0.0 | 0.0 | 1008.06 |
|
||||||
|
|||||||
@@ -926,8 +926,8 @@ Feature: Charge-off
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 01 June 2023 | Charge-off | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 0.0 |
|
| 01 June 2023 | Charge-off | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 0.0 |
|
||||||
When Admin sets the business date to "10 June 2023"
|
When Admin sets the business date to "10 June 2023"
|
||||||
When Customer undo "1"th "Repayment" transaction made on "10 February 2023"
|
When Customer undo "1"th "Repayment" transaction made on "10 February 2023"
|
||||||
@@ -942,8 +942,8 @@ Feature: Charge-off
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 01 June 2023 | Charge-off | 1035.0 | 1000.0 | 0.0 | 20.0 | 15.0 | 0.0 |
|
| 01 June 2023 | Charge-off | 1035.0 | 1000.0 | 0.0 | 20.0 | 15.0 | 0.0 |
|
||||||
Then On Loan Transactions tab the "Repayment" Transaction with date "10 February 2023" is reverted
|
Then On Loan Transactions tab the "Repayment" Transaction with date "10 February 2023" is reverted
|
||||||
|
|
||||||
@@ -973,8 +973,8 @@ Feature: Charge-off
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 01 June 2023 | Charge-off | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 0.0 |
|
| 01 June 2023 | Charge-off | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 0.0 |
|
||||||
When Admin sets the business date to "10 June 2023"
|
When Admin sets the business date to "10 June 2023"
|
||||||
When Customer undo "1"th "Repayment" transaction made on "10 February 2023"
|
When Customer undo "1"th "Repayment" transaction made on "10 February 2023"
|
||||||
@@ -989,8 +989,8 @@ Feature: Charge-off
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 February 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 February 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 01 June 2023 | Charge-off | 1035.0 | 1000.0 | 0.0 | 20.0 | 15.0 | 0.0 |
|
| 01 June 2023 | Charge-off | 1035.0 | 1000.0 | 0.0 | 20.0 | 15.0 | 0.0 |
|
||||||
Then On Loan Transactions tab the "Repayment" Transaction with date "10 February 2023" is reverted
|
Then On Loan Transactions tab the "Repayment" Transaction with date "10 February 2023" is reverted
|
||||||
|
|
||||||
|
|||||||
@@ -2514,10 +2514,6 @@ Feature: LoanChargeback
|
|||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | | 250.0 |
|
| ASSET | 112601 | Loans Receivable | | 250.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 250.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 250.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 April 2024" which has the following Journal entries:
|
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 30.0 | |
|
|
||||||
| INCOME | 404007 | Fee Income | | 30.0 |
|
|
||||||
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 April 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 April 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | | 280.0 |
|
| LIABILITY | 145023 | Suspense/Clearing account | | 280.0 |
|
||||||
@@ -2563,10 +2559,6 @@ Feature: LoanChargeback
|
|||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | | 250.0 |
|
| ASSET | 112601 | Loans Receivable | | 250.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 250.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 250.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 April 2024" which has the following Journal entries:
|
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 30.0 | |
|
|
||||||
| INCOME | 404007 | Fee Income | | 30.0 |
|
|
||||||
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 April 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 April 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | | 280.0 |
|
| LIABILITY | 145023 | Suspense/Clearing account | | 280.0 |
|
||||||
@@ -2967,7 +2959,6 @@ Feature: LoanChargeback
|
|||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 |
|
| 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 |
|
||||||
| 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 |
|
| 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 15 April 2024 | Chargeback | 7.0 | 2.0 | 0.0 | 0.0 | 5.0 | 0.0 | 7.0 |
|
| 15 April 2024 | Chargeback | 7.0 | 2.0 | 0.0 | 0.0 | 5.0 | 0.0 | 7.0 |
|
||||||
| 16 April 2024 | Chargeback | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | 4.0 | 3.0 |
|
| 16 April 2024 | Chargeback | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | 4.0 | 3.0 |
|
||||||
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "16 April 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "16 April 2024" which has the following Journal entries:
|
||||||
@@ -3041,7 +3032,6 @@ Feature: LoanChargeback
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
| 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
||||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
@@ -3052,10 +3042,6 @@ Feature: LoanChargeback
|
|||||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries:
|
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
|
||||||
| INCOME | 404007 | Fee Income | | 5.0 |
|
|
||||||
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 March 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 March 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | | 3.0 |
|
| LIABILITY | 145023 | Suspense/Clearing account | | 3.0 |
|
||||||
@@ -3079,7 +3065,6 @@ Feature: LoanChargeback
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
| 01 March 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
||||||
| 05 March 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 |
|
| 05 March 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 |
|
||||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
||||||
@@ -3091,10 +3076,6 @@ Feature: LoanChargeback
|
|||||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries:
|
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
|
||||||
| INCOME | 404007 | Fee Income | | 5.0 |
|
|
||||||
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 March 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "CHARGEBACK" transaction with date "01 March 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | | 3.0 |
|
| LIABILITY | 145023 | Suspense/Clearing account | | 3.0 |
|
||||||
@@ -3156,6 +3137,8 @@ Feature: LoanChargeback
|
|||||||
# ---Chargeback with amount < fee ---
|
# ---Chargeback with amount < fee ---
|
||||||
When Admin sets the business date to "15 April 2024"
|
When Admin sets the business date to "15 April 2024"
|
||||||
When Admin makes "REPAYMENT_ADJUSTMENT_REFUND" chargeback with 3 EUR transaction amount for Payment nr. 1
|
When Admin makes "REPAYMENT_ADJUSTMENT_REFUND" chargeback with 3 EUR transaction amount for Payment nr. 1
|
||||||
|
When Admin sets the business date to "16 April 2024"
|
||||||
|
When Admin runs inline COB job for Loan
|
||||||
Then Loan Repayment schedule has 5 periods, with the following data for periods:
|
Then Loan Repayment schedule has 5 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 |
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
@@ -3171,8 +3154,8 @@ Feature: LoanChargeback
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
||||||
|
| 15 April 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||||
@@ -3182,7 +3165,7 @@ Feature: LoanChargeback
|
|||||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
||||||
| INCOME | 404007 | Fee Income | | 5.0 |
|
| INCOME | 404007 | Fee Income | | 5.0 |
|
||||||
@@ -3210,8 +3193,8 @@ Feature: LoanChargeback
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
| 01 February 2024 | Repayment | 105.0 | 100.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 |
|
||||||
|
| 15 April 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
||||||
| 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 |
|
| 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 8.0 | 0.0 |
|
||||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
@@ -3222,7 +3205,7 @@ Feature: LoanChargeback
|
|||||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
||||||
| INCOME | 404007 | Fee Income | | 5.0 |
|
| INCOME | 404007 | Fee Income | | 5.0 |
|
||||||
@@ -3370,7 +3353,6 @@ Feature: LoanChargeback
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment |
|
||||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 |
|
||||||
| 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 |
|
| 19 January 2024 | Merchant Issued Refund | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | 0.0 |
|
||||||
| 01 February 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | 0.0 |
|
|
||||||
| 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 |
|
| 01 February 2024 | Repayment | 105.0 | 90.0 | 0.0 | 0.0 | 5.0 | 0.0 | 10.0 |
|
||||||
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 3.0 |
|
| 15 April 2024 | Chargeback | 3.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 3.0 |
|
||||||
| 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 3.0 | 7.0 |
|
| 20 April 2024 | Chargeback | 10.0 | 8.0 | 0.0 | 0.0 | 2.0 | 3.0 | 7.0 |
|
||||||
@@ -3382,10 +3364,6 @@ Feature: LoanChargeback
|
|||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | | 10.0 |
|
| ASSET | 112601 | Loans Receivable | | 10.0 |
|
||||||
| LIABILITY | 145023 | Suspense/Clearing account | 10.0 | |
|
| LIABILITY | 145023 | Suspense/Clearing account | 10.0 | |
|
||||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "01 February 2024" which has the following Journal entries:
|
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
|
||||||
| ASSET | 112603 | Interest/Fee Receivable | 5.0 | |
|
|
||||||
| INCOME | 404007 | Fee Income | | 5.0 |
|
|
||||||
Then Loan Transactions tab has a "REPAYMENT" transaction with date "01 February 2024" which has the following Journal entries:
|
Then Loan Transactions tab has a "REPAYMENT" transaction with date "01 February 2024" which has the following Journal entries:
|
||||||
| Type | Account code | Account name | Debit | Credit |
|
| Type | Account code | Account name | Debit | Credit |
|
||||||
| ASSET | 112601 | Loans Receivable | | 90.0 |
|
| ASSET | 112601 | Loans Receivable | | 90.0 |
|
||||||
|
|||||||
@@ -875,9 +875,9 @@ Feature: LoanRepayment
|
|||||||
Then Loan Repayment schedule has 3 periods, with the following data for periods:
|
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 |
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| | | 01 January 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
| | | 01 January 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
| 1 | 31 | 01 February 2023 | | 666.67 | 333.33 | 10.0 | 103.0 | 10.0 | 456.33 | 400.0 | 400.0 | 0.0 | 56.33 |
|
| 1 | 31 | 01 February 2023 | | 667.0 | 333.0 | 10.0 | 103.0 | 10.0 | 456.0 | 400.0 | 400.0 | 0.0 | 56.0 |
|
||||||
| 2 | 28 | 01 March 2023 | | 333.34 | 333.33 | 10.0 | 0.0 | 0.0 | 343.33 | 0.0 | 0.0 | 0.0 | 343.33 |
|
| 2 | 28 | 01 March 2023 | | 334.0 | 333.0 | 10.0 | 0.0 | 0.0 | 343.0 | 0.0 | 0.0 | 0.0 | 343.0 |
|
||||||
| 3 | 31 | 01 April 2023 | | 0.0 | 333.34 | 10.0 | 0.0 | 0.0 | 343.34 | 0.0 | 0.0 | 0.0 | 343.34 |
|
| 3 | 31 | 01 April 2023 | | 0.0 | 334.0 | 10.0 | 0.0 | 0.0 | 344.0 | 0.0 | 0.0 | 0.0 | 344.0 |
|
||||||
Then Loan Repayment schedule has the following data in Total row:
|
Then Loan Repayment schedule has the following data in Total row:
|
||||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
| 1000 | 30 | 103 | 10 | 1143 | 400 | 400 | 0 | 743 |
|
| 1000 | 30 | 103 | 10 | 1143 | 400 | 400 | 0 | 743 |
|
||||||
@@ -1281,6 +1281,7 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 10 January 2023 | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
||||||
| 17 January 2023 | Repayment | 550.0 | 500.0 | 0.0 | 0.0 | 50.0 | 0.0 |
|
| 17 January 2023 | Repayment | 550.0 | 500.0 | 0.0 | 0.0 | 50.0 | 0.0 |
|
||||||
|
| 17 January 2023 | Accrual | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 0.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 20 January 2023 | Flat | 50.0 | 50.0 | 0.0 | 0.0 |
|
| NSF fee | true | Specified due date | 20 January 2023 | Flat | 50.0 | 50.0 | 0.0 | 0.0 |
|
||||||
@@ -1887,15 +1888,15 @@ Feature: LoanRepayment
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 04 January 2023 | Repayment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 800.0 |
|
| 04 January 2023 | Repayment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 800.0 |
|
||||||
| 12 January 2023 | Repayment | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 12 January 2023 | Repayment | 300.0 | 290.0 | 0.0 | 10.0 | 0.0 | 510.0 |
|
||||||
And Customer makes "AUTOPAY" repayment on "12 January 2023" with 510 EUR transaction amount
|
And Customer makes "AUTOPAY" repayment on "12 January 2023" with 510 EUR transaction amount
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 04 January 2023 | Repayment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 800.0 |
|
| 04 January 2023 | Repayment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 800.0 |
|
||||||
| 12 January 2023 | Repayment | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 500.0 |
|
| 12 January 2023 | Repayment | 300.0 | 290.0 | 0.0 | 10.0 | 0.0 | 510.0 |
|
||||||
| 12 January 2023 | Accrual | 10.0 | 0.0 | 0.0 | 10.0 | 0.0 | 0.0 |
|
| 12 January 2023 | Accrual | 10.0 | 0.0 | 0.0 | 10.0 | 0.0 | 0.0 |
|
||||||
| 12 January 2023 | Repayment | 510.0 | 500.0 | 0.0 | 10.0 | 0.0 | 0.0 |
|
| 12 January 2023 | Repayment | 510.0 | 510.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
|
|
||||||
@TestRailId:C2694 @PaymentStrategyDueInAdvancePenaltyInterestPrincipalFee
|
@TestRailId:C2694 @PaymentStrategyDueInAdvancePenaltyInterestPrincipalFee
|
||||||
Scenario: Verify the due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee-strategy payment strategy: UC1 - no fees or penalties, due payment
|
Scenario: Verify the due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee-strategy payment strategy: UC1 - no fees or penalties, due payment
|
||||||
@@ -2358,8 +2359,8 @@ Feature: LoanRepayment
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 01 February 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
| NSF fee | true | Specified due date | 01 February 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
||||||
@@ -2472,8 +2473,8 @@ Feature: LoanRepayment
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 28 January 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
| NSF fee | true | Specified due date | 28 January 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
||||||
@@ -2563,8 +2564,8 @@ Feature: LoanRepayment
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
|
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 28 January 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
| NSF fee | true | Specified due date | 28 January 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
||||||
@@ -2601,10 +2602,10 @@ Feature: LoanRepayment
|
|||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
|
||||||
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
| 10 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 28 January 2023 | Repayment | 1040.0 | 1000.0 | 0.0 | 20.0 | 20.0 | 0.0 |
|
| 10 January 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||||
| 28 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 |
|
| 28 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 |
|
||||||
|
| 28 January 2023 | Repayment | 1040.0 | 1000.0 | 0.0 | 20.0 | 20.0 | 0.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
| NSF fee | true | Specified due date | 01 February 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
| NSF fee | true | Specified due date | 01 February 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 |
|
||||||
@@ -2750,8 +2751,8 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
||||||
|
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
||||||
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
||||||
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
|
||||||
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
Then Loan Charges tab has the following data:
|
Then Loan Charges tab has the following data:
|
||||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||||
@@ -2770,8 +2771,8 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
||||||
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
|
||||||
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
||||||
|
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
||||||
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
And Customer makes "AUTOPAY" repayment on "01 February 2023" with 1000 EUR transaction amount
|
And Customer makes "AUTOPAY" repayment on "01 February 2023" with 1000 EUR transaction amount
|
||||||
@@ -2780,8 +2781,8 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
||||||
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
|
||||||
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
||||||
|
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
||||||
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 01 February 2023 | Repayment | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
| 01 February 2023 | Repayment | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
@@ -2791,8 +2792,8 @@ Feature: LoanRepayment
|
|||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 10 January 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
| 15 January 2023 | Repayment | 500.0 | 470.0 | 10.0 | 0.0 | 20.0 | 530.0 |
|
||||||
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
|
||||||
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
| 25 January 2023 | Accrual | 50.0 | 0.0 | 10.0 | 20.0 | 20.0 | 0.0 |
|
||||||
|
| 25 January 2023 | Repayment | 530.0 | 500.0 | 10.0 | 0.0 | 20.0 | 500.0 |
|
||||||
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 1000.0 |
|
||||||
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 February 2023 | Repayment | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 01 February 2023 | Repayment | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
| 01 February 2023 | Repayment | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
@@ -2830,15 +2831,15 @@ Feature: LoanRepayment
|
|||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 04 January 2023 | Repayment | 200.0 | 190.0 | 10.0 | 0.0 | 0.0 | 810.0 |
|
| 04 January 2023 | Repayment | 200.0 | 190.0 | 10.0 | 0.0 | 0.0 | 810.0 |
|
||||||
| 12 January 2023 | Repayment | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 510.0 |
|
| 12 January 2023 | Repayment | 300.0 | 289.9 | 0.0 | 10.1 | 0.0 | 520.1 |
|
||||||
And Customer makes "AUTOPAY" repayment on "12 January 2023" with 520.10 EUR transaction amount
|
And Customer makes "AUTOPAY" repayment on "12 January 2023" with 520.10 EUR transaction amount
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
|
||||||
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
| 01 January 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||||
| 04 January 2023 | Repayment | 200.0 | 190.0 | 10.0 | 0.0 | 0.0 | 810.0 |
|
| 04 January 2023 | Repayment | 200.0 | 190.0 | 10.0 | 0.0 | 0.0 | 810.0 |
|
||||||
| 12 January 2023 | Repayment | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 510.0 |
|
| 12 January 2023 | Repayment | 300.0 | 289.9 | 0.0 | 10.1 | 0.0 | 520.1 |
|
||||||
|
| 12 January 2023 | Repayment | 520.1 | 520.1 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
| 12 January 2023 | Accrual | 20.1 | 0.0 | 10.0 | 10.1 | 0.0 | 0.0 |
|
| 12 January 2023 | Accrual | 20.1 | 0.0 | 10.0 | 10.1 | 0.0 | 0.0 |
|
||||||
| 12 January 2023 | Repayment | 520.1 | 510.0 | 0.0 | 10.1 | 0.0 | 0.0 |
|
|
||||||
|
|
||||||
@TestRailId:C2810
|
@TestRailId:C2810
|
||||||
Scenario: As a user I would like to adjust an existing repayment and validate the event
|
Scenario: As a user I would like to adjust an existing repayment and validate the event
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ public class ExternalAssetOwnerLoanStatusChangePlatformServiceImpl implements Ex
|
|||||||
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, event -> {
|
businessEventNotifierService.addPostBusinessEventListener(LoanStatusChangedBusinessEvent.class, event -> {
|
||||||
final Loan loan = event.get();
|
final Loan loan = event.get();
|
||||||
if (configurationReadPlatformService.retrieveGlobalConfiguration(ASSET_EXTERNALIZATION_OF_NON_ACTIVE_LOANS).isEnabled()
|
if (configurationReadPlatformService.retrieveGlobalConfiguration(ASSET_EXTERNALIZATION_OF_NON_ACTIVE_LOANS).isEnabled()
|
||||||
&& (loan.isClosed() || loan.getStatus().isOverpaid())) {
|
&& (event.getOldStatus().isActive() && (loan.isClosed() || loan.getStatus().isOverpaid()))) {
|
||||||
loanAccountOwnerTransferService.handleLoanClosedOrOverpaid(loan);
|
loanAccountOwnerTransferService.handleLoanClosedOrOverpaid(loan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+8
-1
@@ -19,17 +19,24 @@
|
|||||||
package org.apache.fineract.infrastructure.event.business.domain.loan;
|
package org.apache.fineract.infrastructure.event.business.domain.loan;
|
||||||
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
|
||||||
public class LoanStatusChangedBusinessEvent extends LoanBusinessEvent {
|
public class LoanStatusChangedBusinessEvent extends LoanBusinessEvent {
|
||||||
|
|
||||||
private static final String TYPE = "LoanStatusChangedBusinessEvent";
|
private static final String TYPE = "LoanStatusChangedBusinessEvent";
|
||||||
|
private final LoanStatus oldStatus;
|
||||||
|
|
||||||
public LoanStatusChangedBusinessEvent(Loan value) {
|
public LoanStatusChangedBusinessEvent(Loan value, LoanStatus oldStatus) {
|
||||||
super(value);
|
super(value);
|
||||||
|
this.oldStatus = oldStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanStatus getOldStatus() {
|
||||||
|
return oldStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.event.business.domain.loan.transaction;
|
||||||
|
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
|
public class LoanAccrualAdjustmentTransactionBusinessEvent extends LoanTransactionBusinessEvent {
|
||||||
|
|
||||||
|
private static final String TYPE = "LoanAccrualAdjustmentTransactionBusinessEvent";
|
||||||
|
|
||||||
|
public LoanAccrualAdjustmentTransactionBusinessEvent(LoanTransaction value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccrualChargeData {
|
||||||
|
|
||||||
|
private final Long loanChargeId;
|
||||||
|
private final Long loanInstallmentChargeId;
|
||||||
|
private final boolean isPenalty;
|
||||||
|
private Money chargeAmount;
|
||||||
|
private Money chargeAccruable;
|
||||||
|
private Money chargeAccrued;
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccrualPeriodData {
|
||||||
|
|
||||||
|
private final Integer installmentNumber;
|
||||||
|
private final boolean isFirstPeriod;
|
||||||
|
private final LocalDate startDate;
|
||||||
|
private final LocalDate dueDate;
|
||||||
|
private Money interestAmount;
|
||||||
|
private Money interestAccruable;
|
||||||
|
private Money interestAccrued;
|
||||||
|
private Money unrecognizedWaive;
|
||||||
|
private Money transactionAccrued;
|
||||||
|
private final List<AccrualChargeData> charges = new ArrayList<>();
|
||||||
|
|
||||||
|
public AccrualPeriodData addCharge(AccrualChargeData charge) {
|
||||||
|
charges.add(charge);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getChargeAmount() {
|
||||||
|
return charges.stream().map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getFeeAmount() {
|
||||||
|
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getPenaltyAmount() {
|
||||||
|
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getChargeAccrued() {
|
||||||
|
return charges.stream().map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getFeeAccrued() {
|
||||||
|
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getPenaltyAccrued() {
|
||||||
|
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getChargeAccruable() {
|
||||||
|
return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getFeeAccruable() {
|
||||||
|
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccruable).reduce(null,
|
||||||
|
MathUtil::plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getPenaltyAccruable() {
|
||||||
|
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccruable).reduce(null,
|
||||||
|
MathUtil::plus);
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccrualPeriodsData {
|
||||||
|
|
||||||
|
private final MonetaryCurrency currency;
|
||||||
|
private final List<AccrualPeriodData> periods = new ArrayList<>();
|
||||||
|
|
||||||
|
public AccrualPeriodsData addPeriod(AccrualPeriodData period) {
|
||||||
|
periods.add(period);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccrualPeriodsData create(@NotNull List<LoanRepaymentScheduleInstallment> installments, Integer firstInstallmentNumber,
|
||||||
|
MonetaryCurrency currency) {
|
||||||
|
AccrualPeriodsData accrualPeriods = new AccrualPeriodsData(currency);
|
||||||
|
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
|
Integer installmentNumber = installment.getInstallmentNumber();
|
||||||
|
boolean isFirst = installmentNumber.equals(firstInstallmentNumber);
|
||||||
|
accrualPeriods
|
||||||
|
.addPeriod(new AccrualPeriodData(installmentNumber, isFirst, installment.getFromDate(), installment.getDueDate()));
|
||||||
|
}
|
||||||
|
return accrualPeriods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccrualPeriodData getPeriodByInstallmentNumber(Integer installmentNumber) {
|
||||||
|
return installmentNumber == null ? null
|
||||||
|
: periods.stream().filter(p -> installmentNumber.equals(p.getInstallmentNumber())).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFirstInstallmentNumber() {
|
||||||
|
return periods.stream().filter(AccrualPeriodData::isFirstPeriod).map(AccrualPeriodData::getInstallmentNumber).findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-9
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.List;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -66,7 +66,7 @@ public class LoanChargeData {
|
|||||||
|
|
||||||
private final BigDecimal amountOrPercentage;
|
private final BigDecimal amountOrPercentage;
|
||||||
|
|
||||||
private final Collection<ChargeData> chargeOptions;
|
private final List<ChargeData> chargeOptions;
|
||||||
|
|
||||||
private final boolean penalty;
|
private final boolean penalty;
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public class LoanChargeData {
|
|||||||
|
|
||||||
private final BigDecimal maxCap;
|
private final BigDecimal maxCap;
|
||||||
|
|
||||||
private final Collection<LoanInstallmentChargeData> installmentChargeData;
|
private final List<LoanInstallmentChargeData> installmentChargeData;
|
||||||
|
|
||||||
private BigDecimal amountAccrued;
|
private BigDecimal amountAccrued;
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ public class LoanChargeData {
|
|||||||
|
|
||||||
private final ExternalId externalLoanId;
|
private final ExternalId externalLoanId;
|
||||||
|
|
||||||
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
|
public static LoanChargeData template(final List<ChargeData> chargeOptions) {
|
||||||
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null,
|
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null,
|
||||||
ExternalId.empty(), null, null, null, null, ExternalId.empty());
|
ExternalId.empty(), null, null, null, null, ExternalId.empty());
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ public class LoanChargeData {
|
|||||||
final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
|
final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
|
||||||
final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
||||||
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
||||||
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.chargeId = chargeId;
|
this.chargeId = chargeId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -160,9 +160,9 @@ public class LoanChargeData {
|
|||||||
|
|
||||||
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
|
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
|
||||||
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
|
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
|
||||||
final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
final List<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
||||||
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
||||||
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.chargeId = chargeId;
|
this.chargeId = chargeId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -207,7 +207,7 @@ public class LoanChargeData {
|
|||||||
|
|
||||||
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final LocalDate submittedOnDate, final BigDecimal amountOutstanding,
|
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final LocalDate submittedOnDate, final BigDecimal amountOutstanding,
|
||||||
EnumOptionData chargeTimeType, final Long loanId, final ExternalId externalLoanId,
|
EnumOptionData chargeTimeType, final Long loanId, final ExternalId externalLoanId,
|
||||||
Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.chargeId = null;
|
this.chargeId = null;
|
||||||
this.name = null;
|
this.name = null;
|
||||||
@@ -308,7 +308,7 @@ public class LoanChargeData {
|
|||||||
this.externalId = chargeData.externalId;
|
this.externalId = chargeData.externalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
|
public LoanChargeData(LoanChargeData chargeData, List<LoanInstallmentChargeData> installmentChargeData) {
|
||||||
this.id = chargeData.id;
|
this.id = chargeData.id;
|
||||||
this.chargeId = chargeData.chargeId;
|
this.chargeId = chargeData.chargeId;
|
||||||
this.name = chargeData.name;
|
this.name = chargeData.name;
|
||||||
|
|||||||
+2
@@ -61,6 +61,7 @@ public class LoanTransactionEnumData {
|
|||||||
private final boolean reAmortize;
|
private final boolean reAmortize;
|
||||||
private final boolean accrualActivity;
|
private final boolean accrualActivity;
|
||||||
private final boolean interestRefund;
|
private final boolean interestRefund;
|
||||||
|
private final boolean accrualAdjustment;
|
||||||
|
|
||||||
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -96,6 +97,7 @@ public class LoanTransactionEnumData {
|
|||||||
this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
|
this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
|
||||||
this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
|
this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
|
||||||
this.interestRefund = Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
|
this.interestRefund = Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
|
||||||
|
this.accrualAdjustment = Long.valueOf(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue()).equals(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRepaymentType() {
|
public boolean isRepaymentType() {
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
|
|
||||||
if (isNotLoanCreation(loanEvent)) {
|
if (isNotLoanCreation(loanEvent)) {
|
||||||
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
|
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan, oldStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
// set mandatory field states based on new status after the transition
|
// set mandatory field states based on new status after the transition
|
||||||
|
|||||||
+83
-150
@@ -749,11 +749,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ChangedTransactionDetail reprocessTransactions() {
|
public ChangedTransactionDetail reprocessTransactions() {
|
||||||
ChangedTransactionDetail changedTransactionDetail;
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||||
|
getActiveCharges());
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
mapEntry.getValue().updateLoan(this);
|
mapEntry.getValue().updateLoan(this);
|
||||||
}
|
}
|
||||||
@@ -778,10 +778,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
|
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
|
||||||
final Money chargeAmount = loanCharge.getAmount(getCurrency());
|
final Money chargeAmount = loanCharge.getAmount(getCurrency());
|
||||||
Money feeCharges = chargeAmount;
|
Money feeCharges = chargeAmount;
|
||||||
Money penaltyCharges = Money.zero(loanCurrency());
|
Money penaltyCharges = Money.zero(getCurrency());
|
||||||
if (loanCharge.isPenaltyCharge()) {
|
if (loanCharge.isPenaltyCharge()) {
|
||||||
penaltyCharges = chargeAmount;
|
penaltyCharges = chargeAmount;
|
||||||
feeCharges = Money.zero(loanCurrency());
|
feeCharges = Money.zero(getCurrency());
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalDate transactionDate;
|
LocalDate transactionDate;
|
||||||
@@ -898,8 +898,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
|
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(getCurrency())) {
|
||||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
|
||||||
/*
|
/*
|
||||||
* TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no
|
* TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no
|
||||||
* need to adjust any loan transactions).
|
* need to adjust any loan transactions).
|
||||||
@@ -907,9 +906,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||||
* affected Transactions
|
* affected Transactions
|
||||||
*/
|
*/
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
reprocessTransactions();
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
}
|
}
|
||||||
this.charges.remove(loanCharge);
|
this.charges.remove(loanCharge);
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -922,7 +919,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
for (final LoanTransaction transaction : transactions) {
|
for (final LoanTransaction transaction : transactions) {
|
||||||
if (transaction.isRepaymentAtDisbursement()
|
if (transaction.isRepaymentAtDisbursement()
|
||||||
&& doesLoanChargePaidByContainLoanCharge(transaction.getLoanChargesPaid(), loanCharge)) {
|
&& doesLoanChargePaidByContainLoanCharge(transaction.getLoanChargesPaid(), loanCharge)) {
|
||||||
final MonetaryCurrency currency = loanCurrency();
|
final MonetaryCurrency currency = getCurrency();
|
||||||
final Money chargeAmount = Money.of(currency, loanCharge.amount());
|
final Money chargeAmount = Money.of(currency, loanCharge.amount());
|
||||||
if (transaction.isGreaterThan(chargeAmount)) {
|
if (transaction.isGreaterThan(chargeAmount)) {
|
||||||
final Money principalPortion = Money.zero(currency);
|
final Money principalPortion = Money.zero(currency);
|
||||||
@@ -969,9 +966,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||||
* affected Transactions
|
* affected Transactions
|
||||||
*/
|
*/
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
reprocessTransactions();
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
} else {
|
} else {
|
||||||
// reprocess loan schedule based on charge been waived.
|
// reprocess loan schedule based on charge been waived.
|
||||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||||
@@ -1067,7 +1062,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
final ExternalId externalId) {
|
final ExternalId externalId) {
|
||||||
validateLoanIsNotClosed(loanCharge);
|
validateLoanIsNotClosed(loanCharge);
|
||||||
|
|
||||||
final Money amountWaived = loanCharge.waive(loanCurrency(), loanInstallmentNumber);
|
final Money amountWaived = loanCharge.waive(getCurrency(), loanInstallmentNumber);
|
||||||
changes.put("amount", amountWaived.getAmount());
|
changes.put("amount", amountWaived.getAmount());
|
||||||
|
|
||||||
Money unrecognizedIncome = amountWaived.zero();
|
Money unrecognizedIncome = amountWaived.zero();
|
||||||
@@ -1089,10 +1084,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Money feeChargesWaived = chargeComponent;
|
Money feeChargesWaived = chargeComponent;
|
||||||
Money penaltyChargesWaived = Money.zero(loanCurrency());
|
Money penaltyChargesWaived = Money.zero(getCurrency());
|
||||||
if (loanCharge.isPenaltyCharge()) {
|
if (loanCharge.isPenaltyCharge()) {
|
||||||
penaltyChargesWaived = chargeComponent;
|
penaltyChargesWaived = chargeComponent;
|
||||||
feeChargesWaived = Money.zero(loanCurrency());
|
feeChargesWaived = Money.zero(getCurrency());
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalDate transactionDate = getDisbursementDate();
|
LocalDate transactionDate = getDisbursementDate();
|
||||||
@@ -1132,7 +1127,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
|
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
|
||||||
// to be reprocessed.
|
// to be reprocessed.
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(getCurrency())) {
|
||||||
/*
|
/*
|
||||||
* TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan
|
* TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan
|
||||||
* charges only waives the remaining amount.
|
* charges only waives the remaining amount.
|
||||||
@@ -1140,9 +1135,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||||
* affected Transactions
|
* affected Transactions
|
||||||
*/
|
*/
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
reprocessTransactions();
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
} else {
|
} else {
|
||||||
// reprocess loan schedule based on charge been waived.
|
// reprocess loan schedule based on charge been waived.
|
||||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||||
@@ -1289,7 +1282,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
addLoanRepaymentScheduleInstallment(installment);
|
addLoanRepaymentScheduleInstallment(installment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLoanScheduleDependentDerivedFields();
|
updateLoanScheduleDependentDerivedFields();
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
}
|
}
|
||||||
@@ -1310,7 +1302,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
updateLoanScheduleDependentDerivedFields();
|
updateLoanScheduleDependentDerivedFields();
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanRepaymentScheduleInstallment findByInstallmentNumber(Collection<LoanRepaymentScheduleInstallment> installments,
|
private LoanRepaymentScheduleInstallment findByInstallmentNumber(Collection<LoanRepaymentScheduleInstallment> installments,
|
||||||
@@ -1347,8 +1338,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
|
this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
|
||||||
|
|
||||||
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
|
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||||
this.summary.updateSummary(loanCurrency(), principal, getRepaymentScheduleInstallments(), this.loanSummaryWrapper,
|
this.summary.updateSummary(getCurrency(), principal, getRepaymentScheduleInstallments(), this.loanSummaryWrapper, this.charges);
|
||||||
this.charges);
|
|
||||||
updateLoanOutstandingBalances();
|
updateLoanOutstandingBalances();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1923,7 +1913,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
|
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
|
||||||
repaymentPeriod.updateObligationsMet(loanCurrency(), actualDisbursementDate);
|
repaymentPeriod.updateObligationsMet(getCurrency(), actualDisbursementDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2002,7 +1992,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
* TODO Vishwas: do we need to be able to pass in payment type details for repayments at disbursements too?
|
* TODO Vishwas: do we need to be able to pass in payment type details for repayments at disbursements too?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
|
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(getCurrency());
|
||||||
/*
|
/*
|
||||||
* all Charges repaid at disbursal is marked as repaid and "APPLY Charge" transactions are created for all other
|
* all Charges repaid at disbursal is marked as repaid and "APPLY Charge" transactions are created for all other
|
||||||
* fees ( which are created during disbursal but not repaid)
|
* fees ( which are created during disbursal but not repaid)
|
||||||
@@ -2221,7 +2211,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (charge.isOverdueInstallmentCharge()) {
|
if (charge.isOverdueInstallmentCharge()) {
|
||||||
charge.setActive(false);
|
charge.setActive(false);
|
||||||
} else {
|
} else {
|
||||||
charge.resetToOriginal(loanCurrency());
|
charge.resetToOriginal(getCurrency());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
@@ -2354,7 +2344,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
loanTransaction.updateLoan(this);
|
loanTransaction.updateLoan(this);
|
||||||
|
|
||||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
if (loanTransaction.isNotZero()) {
|
||||||
addLoanTransaction(loanTransaction);
|
addLoanTransaction(loanTransaction);
|
||||||
}
|
}
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -2371,7 +2361,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loanTransaction.isRecoveryRepayment()
|
if (loanTransaction.isRecoveryRepayment()
|
||||||
&& loanTransaction.getAmount(loanCurrency()).getAmount().compareTo(getSummary().getTotalWrittenOff()) > 0) {
|
&& loanTransaction.getAmount(getCurrency()).getAmount().compareTo(getSummary().getTotalWrittenOff()) > 0) {
|
||||||
final String errorMessage = "The transaction amount cannot greater than the remaining written off amount.";
|
final String errorMessage = "The transaction amount cannot greater than the remaining written off amount.";
|
||||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.greater.than.total.written.off", errorMessage);
|
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.greater.than.total.written.off", errorMessage);
|
||||||
}
|
}
|
||||||
@@ -2380,7 +2370,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
|
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
|
||||||
|
|
||||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
if (loanTransaction.isNotZero()) {
|
||||||
addLoanTransaction(loanTransaction);
|
addLoanTransaction(loanTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2400,12 +2390,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
|
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
|
||||||
if (adjustedTransaction != null) {
|
if (adjustedTransaction != null) {
|
||||||
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(loanCurrency()));
|
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(getCurrency()));
|
||||||
}
|
}
|
||||||
if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
|
if (loanTransaction.getAmount(getCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
|
||||||
final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
|
final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
|
||||||
throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage,
|
throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage,
|
||||||
loanTransaction.getAmount(loanCurrency()), totalInterestOutstandingOnLoan.getAmount());
|
loanTransaction.getAmount(getCurrency()), totalInterestOutstandingOnLoan.getAmount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2450,17 +2440,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
&& !getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) {
|
&& !getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
changedTransactionDetail = reprocessTransactions();
|
||||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(this);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Commented since throwing exception if external id present for one of the transactions. for this need to
|
|
||||||
* save the reversed transactions first and then new transactions.
|
|
||||||
*/
|
|
||||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -2531,7 +2511,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
// FIXME - kw - update account balance to negative amount.
|
// FIXME - kw - update account balance to negative amount.
|
||||||
handleLoanOverpayment(transactionDate, loanLifecycleStateMachine);
|
handleLoanOverpayment(transactionDate, loanLifecycleStateMachine);
|
||||||
statusChanged = true;
|
statusChanged = true;
|
||||||
} else if (this.summary.isRepaidInFull(loanCurrency())) {
|
} else if (this.summary.isRepaidInFull(getCurrency())) {
|
||||||
handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine);
|
handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine);
|
||||||
statusChanged = true;
|
statusChanged = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -2645,7 +2625,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
LocalDate lastTransactionDate = null;
|
LocalDate lastTransactionDate = null;
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
if (transaction.isRepaymentLikeType() && transaction.isNonZero()) {
|
if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
|
||||||
lastTransactionDate = transaction.getTransactionDate();
|
lastTransactionDate = transaction.getTransactionDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2669,7 +2649,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||||
|
|
||||||
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
|
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(getCurrency());
|
||||||
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate()) && scheduledRepayment.isNotFullyPaidOff()
|
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate()) && scheduledRepayment.isNotFullyPaidOff()
|
||||||
&& outstandingForPeriod.isGreaterThanZero()) {
|
&& outstandingForPeriod.isGreaterThanZero()) {
|
||||||
transactionDate = scheduledRepayment.getDueDate();
|
transactionDate = scheduledRepayment.getDueDate();
|
||||||
@@ -2692,16 +2672,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
final LoanTransaction writeOffTransaction = findWriteOffTransaction();
|
final LoanTransaction writeOffTransaction = findWriteOffTransaction();
|
||||||
writeOffTransaction.reverse();
|
writeOffTransaction.reverse();
|
||||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this);
|
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this);
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
return reprocessTransactions();
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
|
||||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
|
||||||
}
|
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
|
||||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
|
||||||
getActiveCharges());
|
|
||||||
updateLoanSummaryDerivedFields();
|
|
||||||
return changedTransactionDetail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanTransaction findWriteOffTransaction() {
|
public LoanTransaction findWriteOffTransaction() {
|
||||||
@@ -2718,7 +2689,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
private Money calculateTotalOverpayment() {
|
private Money calculateTotalOverpayment() {
|
||||||
Money totalPaidInRepayments = getTotalPaidInRepayments();
|
Money totalPaidInRepayments = getTotalPaidInRepayments();
|
||||||
|
|
||||||
final MonetaryCurrency currency = loanCurrency();
|
final MonetaryCurrency currency = getCurrency();
|
||||||
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
||||||
Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
|
Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
@@ -2759,10 +2730,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return getTotalRecoveredPayments();
|
return getTotalRecoveredPayments();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MonetaryCurrency loanCurrency() {
|
|
||||||
return this.loanRepaymentScheduleDetail.getCurrency();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
|
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
|
||||||
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||||
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||||
@@ -2822,7 +2789,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLoanTransaction(loanTransaction);
|
addLoanTransaction(loanTransaction);
|
||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -2844,14 +2811,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
changedTransactionDetail = reprocessTransactions();
|
||||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(this);
|
|
||||||
addLoanTransaction(mapEntry.getValue());
|
|
||||||
}
|
|
||||||
updateLoanSummaryDerivedFields();
|
|
||||||
LocalDate lastLoanTransactionDate = getLatestTransactionDate();
|
LocalDate lastLoanTransactionDate = getLatestTransactionDate();
|
||||||
doPostLoanTransactionChecks(lastLoanTransactionDate, loanLifecycleStateMachine);
|
doPostLoanTransactionChecks(lastLoanTransactionDate, loanLifecycleStateMachine);
|
||||||
}
|
}
|
||||||
@@ -2904,7 +2864,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
LoanTransaction loanTransaction = null;
|
LoanTransaction loanTransaction = null;
|
||||||
if (isOpen()) {
|
if (isOpen()) {
|
||||||
final Money totalOutstanding = this.summary.getTotalOutstanding(loanCurrency());
|
final Money totalOutstanding = this.summary.getTotalOutstanding(getCurrency());
|
||||||
if (totalOutstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) {
|
if (totalOutstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) {
|
||||||
|
|
||||||
this.closedOnDate = closureDate;
|
this.closedOnDate = closureDate;
|
||||||
@@ -2923,7 +2883,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLoanTransaction(loanTransaction);
|
addLoanTransaction(loanTransaction);
|
||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -3123,11 +3083,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Money getTotalPaidInRepayments() {
|
private Money getTotalPaidInRepayments() {
|
||||||
Money cumulativePaid = Money.zero(loanCurrency());
|
Money cumulativePaid = Money.zero(getCurrency());
|
||||||
|
|
||||||
for (final LoanTransaction repayment : this.loanTransactions) {
|
for (final LoanTransaction repayment : this.loanTransactions) {
|
||||||
if (repayment.isRepaymentLikeType() && !repayment.isReversed()) {
|
if (repayment.isRepaymentLikeType() && !repayment.isReversed()) {
|
||||||
cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
|
cumulativePaid = cumulativePaid.plus(repayment.getAmount(getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3135,11 +3095,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Money getTotalRecoveredPayments() {
|
public Money getTotalRecoveredPayments() {
|
||||||
Money cumulativePaid = Money.zero(loanCurrency());
|
Money cumulativePaid = Money.zero(getCurrency());
|
||||||
|
|
||||||
for (final LoanTransaction recoveredPayment : this.loanTransactions) {
|
for (final LoanTransaction recoveredPayment : this.loanTransactions) {
|
||||||
if (recoveredPayment.isRecoveryRepayment()) {
|
if (recoveredPayment.isRecoveryRepayment()) {
|
||||||
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(loanCurrency()));
|
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cumulativePaid;
|
return cumulativePaid;
|
||||||
@@ -3148,16 +3108,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public Money getTotalPrincipalOutstandingUntil(LocalDate date) {
|
public Money getTotalPrincipalOutstandingUntil(LocalDate date) {
|
||||||
return getRepaymentScheduleInstallments().stream()
|
return getRepaymentScheduleInstallments().stream()
|
||||||
.filter(installment -> installment.getDueDate().isBefore(date) || installment.getDueDate().isEqual(date))
|
.filter(installment -> installment.getDueDate().isBefore(date) || installment.getDueDate().isEqual(date))
|
||||||
.map(installment -> installment.getPrincipalOutstanding(loanCurrency())).reduce(Money.zero(loanCurrency()), Money::add);
|
.map(installment -> installment.getPrincipalOutstanding(getCurrency())).reduce(Money.zero(getCurrency()), Money::add);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money getTotalInterestOutstandingOnLoan() {
|
private Money getTotalInterestOutstandingOnLoan() {
|
||||||
Money cumulativeInterest = Money.zero(loanCurrency());
|
Money cumulativeInterest = Money.zero(getCurrency());
|
||||||
|
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||||
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
|
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(getCurrency()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cumulativeInterest;
|
return cumulativeInterest;
|
||||||
@@ -3169,7 +3129,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||||
|
|
||||||
final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
|
final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(getCurrency());
|
||||||
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate())) {
|
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate())) {
|
||||||
cumulativeInterestOverdue = cumulativeInterestOverdue.plus(interestOutstandingForPeriod);
|
cumulativeInterestOverdue = cumulativeInterestOverdue.plus(interestOutstandingForPeriod);
|
||||||
}
|
}
|
||||||
@@ -3515,7 +3475,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
&& !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
|
&& !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
|
||||||
if (transaction.isAccrual()) {
|
if (transaction.isAccrual()) {
|
||||||
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
|
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
|
||||||
} else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver()) {
|
} else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver() || transaction.isAccrualAdjustment()) {
|
||||||
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
|
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3841,8 +3801,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public LocalDate getLastUserTransactionDate() {
|
public LocalDate getLastUserTransactionDate() {
|
||||||
LocalDate currentTransactionDate = getDisbursementDate();
|
LocalDate currentTransactionDate = getDisbursementDate();
|
||||||
for (final LoanTransaction previousTransaction : this.loanTransactions) {
|
for (final LoanTransaction previousTransaction : this.loanTransactions) {
|
||||||
if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting()
|
if (!(previousTransaction.isReversed() || previousTransaction.isAccrualRelated() || previousTransaction.isIncomePosting())
|
||||||
|| previousTransaction.isAccrualActivity())
|
|
||||||
&& DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) {
|
&& DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) {
|
||||||
currentTransactionDate = previousTransaction.getTransactionDate();
|
currentTransactionDate = previousTransaction.getTransactionDate();
|
||||||
}
|
}
|
||||||
@@ -4131,17 +4090,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
return reprocessTransactions();
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
|
||||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
|
||||||
getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(this);
|
|
||||||
addLoanTransaction(mapEntry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return changedTransactionDetail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getPrincipalAmountForRepaymentSchedule() {
|
public BigDecimal getPrincipalAmountForRepaymentSchedule() {
|
||||||
@@ -4219,7 +4168,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
} else {
|
} else {
|
||||||
regenerateRepaymentSchedule(generatorDTO);
|
regenerateRepaymentSchedule(generatorDTO);
|
||||||
}
|
}
|
||||||
return processTransactions();
|
return reprocessTransactions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4229,33 +4178,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
} else {
|
} else {
|
||||||
regenerateRepaymentSchedule(generatorDTO);
|
regenerateRepaymentSchedule(generatorDTO);
|
||||||
}
|
}
|
||||||
return processTransactions();
|
return reprocessTransactions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
||||||
return processTransactions();
|
return reprocessTransactions();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangedTransactionDetail processTransactions() {
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
|
||||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
|
||||||
getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(this);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
|
||||||
* the reversed transactions first and then new transactions.
|
|
||||||
*/
|
|
||||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
|
||||||
updateLoanSummaryDerivedFields();
|
|
||||||
|
|
||||||
return changedTransactionDetail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||||
@@ -4417,17 +4346,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
|
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
|
||||||
Money totalPrincipal = Money.zero(loanCurrency());
|
Money totalPrincipal = Money.zero(getCurrency());
|
||||||
Money totalInterest = Money.zero(loanCurrency());
|
Money totalInterest = Money.zero(getCurrency());
|
||||||
Money feeCharges = Money.zero(loanCurrency());
|
Money feeCharges = Money.zero(getCurrency());
|
||||||
Money penaltyCharges = Money.zero(loanCurrency());
|
Money penaltyCharges = Money.zero(getCurrency());
|
||||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
||||||
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency()));
|
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(getCurrency()));
|
||||||
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
|
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(getCurrency()));
|
||||||
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
|
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(getCurrency()));
|
||||||
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
|
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(getCurrency()));
|
||||||
}
|
}
|
||||||
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
|
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
|
||||||
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
|
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
|
||||||
@@ -4533,6 +4462,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null);
|
return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param predicate
|
||||||
|
* filter of the installments
|
||||||
|
* @return the installments matching the filter
|
||||||
|
**/
|
||||||
|
public List<LoanRepaymentScheduleInstallment> getRepaymentScheduleInstallments(
|
||||||
|
@NotNull Predicate<LoanRepaymentScheduleInstallment> predicate) {
|
||||||
|
return getRepaymentScheduleInstallments().stream().filter(predicate).toList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return loan disbursement data
|
* @return loan disbursement data
|
||||||
**/
|
**/
|
||||||
@@ -4691,17 +4630,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.createStandingInstructionAtDisbursement != null && this.createStandingInstructionAtDisbursement;
|
return this.createStandingInstructionAtDisbursement != null && this.createStandingInstructionAtDisbursement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<LoanCharge> getLoanCharges(LocalDate dueDate) {
|
public List<LoanCharge> getLoanChargesByDueDate(LocalDate dueDate) {
|
||||||
Collection<LoanCharge> loanCharges = new ArrayList<>();
|
return getLoanCharges(
|
||||||
|
loanCharge -> loanCharge.getDueLocalDate() != null && DateUtils.isEqual(loanCharge.getDueLocalDate(), dueDate));
|
||||||
|
}
|
||||||
|
|
||||||
for (LoanCharge loanCharge : charges) {
|
/**
|
||||||
|
* @param predicate
|
||||||
if (loanCharge.getDueLocalDate() != null && loanCharge.getDueLocalDate().equals(dueDate)) {
|
* filter of the charges
|
||||||
loanCharges.add(loanCharge);
|
* @return the loan charges matching the filter
|
||||||
}
|
**/
|
||||||
}
|
public List<LoanCharge> getLoanCharges(@NotNull Predicate<LoanCharge> predicate) {
|
||||||
|
return getLoanCharges().stream().filter(predicate).toList();
|
||||||
return loanCharges;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGuaranteeAmount(BigDecimal guaranteeAmountDerived) {
|
public void setGuaranteeAmount(BigDecimal guaranteeAmountDerived) {
|
||||||
@@ -4796,7 +4736,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
|
throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
if (loanTransaction.isNotZero()) {
|
||||||
addLoanTransaction(loanTransaction);
|
addLoanTransaction(loanTransaction);
|
||||||
}
|
}
|
||||||
if (loanTransaction.isNotRefundForActiveLoan()) {
|
if (loanTransaction.isNotRefundForActiveLoan()) {
|
||||||
@@ -4828,13 +4768,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||||
} else {
|
} else {
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
changedTransactionDetail = reprocessTransactions();
|
||||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
@@ -4869,7 +4803,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
LocalDate lastTransactionDate = null;
|
LocalDate lastTransactionDate = null;
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
if ((transaction.isRepaymentLikeType() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
|
if ((transaction.isRepaymentLikeType() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
|
||||||
&& transaction.isNonZero() && transaction.isNotReversed()) {
|
&& transaction.isGreaterThanZero() && transaction.isNotReversed()) {
|
||||||
lastTransactionDate = transaction.getTransactionDate();
|
lastTransactionDate = transaction.getTransactionDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4979,7 +4913,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
charge.setActive(false);
|
charge.setActive(false);
|
||||||
} else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
|
} else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
|
||||||
.equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
|
.equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
|
||||||
charge.resetToOriginal(loanCurrency());
|
charge.resetToOriginal(getCurrency());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(disbursementDetail.principal()));
|
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(disbursementDetail.principal()));
|
||||||
@@ -5211,7 +5145,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateForForeclosure(final LocalDate transactionDate) {
|
public void validateForForeclosure(final LocalDate transactionDate) {
|
||||||
if (isInterestRecalculationEnabledForProduct()) {
|
if (getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||||
final String defaultUserMessage = "The loan with interest recalculation enabled cannot be foreclosed.";
|
final String defaultUserMessage = "The loan with interest recalculation enabled cannot be foreclosed.";
|
||||||
throw new LoanForeclosureException("loan.with.interest.recalculation.enabled.cannot.be.foreclosured", defaultUserMessage,
|
throw new LoanForeclosureException("loan.with.interest.recalculation.enabled.cannot.be.foreclosured", defaultUserMessage,
|
||||||
getId());
|
getId());
|
||||||
@@ -5461,8 +5395,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
public LoanTransaction getLastUserTransaction() {
|
public LoanTransaction getLastUserTransaction() {
|
||||||
return getLoanTransactions().stream() //
|
return getLoanTransactions().stream() //
|
||||||
.filter(LoanTransaction::isNotReversed) //
|
.filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting())) //
|
||||||
.filter(t -> !(t.isAccrualTransaction() || t.isIncomePosting())) //
|
|
||||||
.reduce((first, second) -> second) //
|
.reduce((first, second) -> second) //
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-53
@@ -28,6 +28,7 @@ import jakarta.persistence.OneToMany;
|
|||||||
import jakarta.persistence.OneToOne;
|
import jakarta.persistence.OneToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -40,7 +41,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.function.Predicate;
|
||||||
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
@@ -58,6 +60,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||||
|
|
||||||
|
@Getter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
|
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
|
||||||
public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||||
@@ -305,10 +308,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getAmountPercentageAppliedTo() {
|
|
||||||
return this.amountPercentageAppliedTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
|
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
|
||||||
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
|
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
|
||||||
}
|
}
|
||||||
@@ -509,15 +508,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LocalDate getDueLocalDate() {
|
public LocalDate getDueLocalDate() {
|
||||||
return this.dueDate;
|
return this.dueDate; // TODO delete duplicated method
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDate getDueDate() {
|
|
||||||
return this.dueDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDate getSubmittedOnDate() {
|
|
||||||
return submittedOnDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean determineIfFullyPaid() {
|
private boolean determineIfFullyPaid() {
|
||||||
@@ -597,11 +588,11 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal amount() {
|
public BigDecimal amount() {
|
||||||
return this.amount;
|
return this.amount; // TODO delete duplicated method
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal amountOutstanding() {
|
public BigDecimal amountOutstanding() {
|
||||||
return this.amountOutstanding;
|
return this.amountOutstanding; // TODO delete duplicated method
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getAmountOutstanding(final MonetaryCurrency currency) {
|
public Money getAmountOutstanding(final MonetaryCurrency currency) {
|
||||||
@@ -644,14 +635,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.waived;
|
return this.waived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getMinCap() {
|
|
||||||
return this.minCap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMaxCap() {
|
|
||||||
return this.maxCap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
|
public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
|
||||||
|
|
||||||
final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency));
|
final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency));
|
||||||
@@ -729,10 +712,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.charge.getCurrencyCode();
|
return this.charge.getCurrencyCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Charge getCharge() {
|
|
||||||
return this.charge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return
|
* @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return
|
||||||
* true; } if (obj.getClass() != getClass()) { return false; } final LoanCharge rhs = (LoanCharge) obj; return new
|
* true; } if (obj.getClass() != getClass()) { return false; } final LoanCharge rhs = (LoanCharge) obj; return new
|
||||||
@@ -756,10 +735,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return ChargeCalculationType.fromInt(this.chargeCalculation);
|
return ChargeCalculationType.fromInt(this.chargeCalculation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getPercentage() {
|
|
||||||
return this.percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateAmount(final BigDecimal amount) {
|
public void updateAmount(final BigDecimal amount) {
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
calculateOutstanding();
|
calculateOutstanding();
|
||||||
@@ -851,7 +826,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal amountOrPercentage() {
|
public BigDecimal amountOrPercentage() {
|
||||||
return this.amountOrPercentage;
|
return this.amountOrPercentage; // TODO delete duplicated method
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal chargeAmount() {
|
public BigDecimal chargeAmount() {
|
||||||
@@ -906,12 +881,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
|
|
||||||
return this.overdueInstallmentCharge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() {
|
public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() {
|
||||||
return this.loanTrancheDisbursementCharge;
|
return this.loanTrancheDisbursementCharge; // TODO delete duplicated method
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
|
public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
|
||||||
@@ -960,14 +931,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return paidChargePerInstallment;
|
return paidChargePerInstallment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<LoanChargePaidBy> getLoanChargePaidBySet() {
|
|
||||||
return this.loanChargePaidBySet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Loan getLoan() {
|
|
||||||
return this.loan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDisbursementCharge() {
|
public boolean isDisbursementCharge() {
|
||||||
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
|
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
|
||||||
}
|
}
|
||||||
@@ -988,10 +951,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.waived = false;
|
this.waived = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExternalId getExternalId() {
|
|
||||||
return externalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChargeTimeType getChargeTimeType() {
|
public ChargeTimeType getChargeTimeType() {
|
||||||
return ChargeTimeType.fromInt(this.chargeTime);
|
return ChargeTimeType.fromInt(this.chargeTime);
|
||||||
}
|
}
|
||||||
@@ -1013,6 +972,11 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return dueDate;
|
return dueDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public List<LoanChargePaidBy> getLoanChargePaidBy(@NotNull Predicate<LoanChargePaidBy> filter) {
|
||||||
|
return getLoanChargePaidBySet().stream().filter(filter).toList();
|
||||||
|
}
|
||||||
|
|
||||||
public LoanChargeData toData() {
|
public LoanChargeData toData() {
|
||||||
EnumOptionData chargeTimeTypeData = new EnumOptionData((long) getChargeTimeType().ordinal(), getChargeTimeType().getCode(),
|
EnumOptionData chargeTimeTypeData = new EnumOptionData((long) getChargeTimeType().ordinal(), getChargeTimeType().getCode(),
|
||||||
String.valueOf(getChargeTimeType().getValue()));
|
String.valueOf(getChargeTimeType().getValue()));
|
||||||
@@ -1020,8 +984,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
getChargeCalculation().getCode(), String.valueOf(getChargeCalculation().getValue()));
|
getChargeCalculation().getCode(), String.valueOf(getChargeCalculation().getValue()));
|
||||||
EnumOptionData chargePaymentModeData = new EnumOptionData((long) getChargePaymentMode().ordinal(), getChargePaymentMode().getCode(),
|
EnumOptionData chargePaymentModeData = new EnumOptionData((long) getChargePaymentMode().ordinal(), getChargePaymentMode().getCode(),
|
||||||
String.valueOf(getChargePaymentMode().getValue()));
|
String.valueOf(getChargePaymentMode().getValue()));
|
||||||
Set<LoanInstallmentChargeData> loanInstallmentChargeDataSet = installmentCharges().stream().map(LoanInstallmentCharge::toData)
|
List<LoanInstallmentChargeData> loanInstallmentChargeDataList = installmentCharges().stream().map(LoanInstallmentCharge::toData)
|
||||||
.collect(Collectors.toSet());
|
.toList();
|
||||||
|
|
||||||
return LoanChargeData.builder().id(getId()).chargeId(getCharge().getId()).name(getCharge().getName())
|
return LoanChargeData.builder().id(getId()).chargeId(getCharge().getId()).name(getCharge().getName())
|
||||||
.currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid).amountWaived(amountWaived)
|
.currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid).amountWaived(amountWaived)
|
||||||
@@ -1029,6 +993,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
.submittedOnDate(submittedOnDate).dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).percentage(percentage)
|
.submittedOnDate(submittedOnDate).dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).percentage(percentage)
|
||||||
.amountPercentageAppliedTo(amountPercentageAppliedTo).amountOrPercentage(amountOrPercentage).penalty(penaltyCharge)
|
.amountPercentageAppliedTo(amountPercentageAppliedTo).amountOrPercentage(amountOrPercentage).penalty(penaltyCharge)
|
||||||
.chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
|
.chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
|
||||||
.installmentChargeData(loanInstallmentChargeDataSet).externalId(externalId).build();
|
.installmentChargeData(loanInstallmentChargeDataList).externalId(externalId).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -188,7 +188,7 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCompoundingToBePostedAsTransaction() {
|
public boolean isCompoundingToBePostedAsTransaction() {
|
||||||
return null == this.isCompoundingToBePostedAsTransaction ? false : this.isCompoundingToBePostedAsTransaction;
|
return this.isCompoundingToBePostedAsTransaction != null && this.isCompoundingToBePostedAsTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowCompoundingOnEod() {
|
public boolean allowCompoundingOnEod() {
|
||||||
|
|||||||
+19
-15
@@ -34,6 +34,7 @@ import java.util.Set;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||||
@@ -692,29 +693,32 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
|
|
||||||
public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
||||||
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
||||||
this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.getAmount());
|
this.feeChargesCharged = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesDue));
|
||||||
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
|
this.feeChargesWaived = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesWaived));
|
||||||
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
|
this.feeChargesWrittenOff = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesWrittenOff));
|
||||||
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
|
this.penaltyCharges = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesDue));
|
||||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
|
this.penaltyChargesWaived = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesWaived));
|
||||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
|
this.penaltyChargesWrittenOff = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesWrittenOff));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
public void addToChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
||||||
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
||||||
this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.plus(this.feeChargesCharged).getAmount());
|
this.feeChargesCharged = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesDue), this.feeChargesCharged));
|
||||||
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.plus(this.feeChargesWaived).getAmount());
|
this.feeChargesWaived = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesWaived), this.feeChargesWaived));
|
||||||
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.plus(this.feeChargesWrittenOff).getAmount());
|
this.feeChargesWrittenOff = MathUtil
|
||||||
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount());
|
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesWrittenOff), this.feeChargesWrittenOff));
|
||||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
|
this.penaltyCharges = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesDue), this.penaltyCharges));
|
||||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).getAmount());
|
this.penaltyChargesWaived = MathUtil
|
||||||
|
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesWaived), this.penaltyChargesWaived));
|
||||||
|
this.penaltyChargesWrittenOff = MathUtil
|
||||||
|
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesWrittenOff), this.penaltyChargesWrittenOff));
|
||||||
checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency());
|
checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
|
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
|
||||||
this.interestAccrued = defaultToNullIfZero(interest.getAmount());
|
this.interestAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(interest));
|
||||||
this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount());
|
this.feeAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeCharges));
|
||||||
this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
|
this.penaltyAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(penalityCharges));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateObligationsMet(final MonetaryCurrency currency, final LocalDate transactionDate) {
|
public void updateObligationsMet(final MonetaryCurrency currency, final LocalDate transactionDate) {
|
||||||
|
|||||||
+9
@@ -253,6 +253,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
|
|||||||
: DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate);
|
: DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBeforePeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) {
|
||||||
|
LocalDate fromDate = installment.getFromDate();
|
||||||
|
return isFirstPeriod ? DateUtils.isBefore(targetDate, fromDate) : !DateUtils.isAfter(targetDate, fromDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAfterPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment) {
|
||||||
|
return DateUtils.isAfter(targetDate, installment.getDueDate());
|
||||||
|
}
|
||||||
|
|
||||||
public static Optional<LoanRepaymentScheduleInstallment> findInPeriod(LocalDate targetDate,
|
public static Optional<LoanRepaymentScheduleInstallment> findInPeriod(LocalDate targetDate,
|
||||||
List<LoanRepaymentScheduleInstallment> installments) {
|
List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
int firstNumber = fetchFirstNormalInstallmentNumber(installments);
|
int firstNumber = fetchFirstNormalInstallmentNumber(installments);
|
||||||
|
|||||||
+20
@@ -101,6 +101,18 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
|||||||
|
|
||||||
String FIND_ALL_LOAN_IDS_BY_STATUS_ID = "SELECT loan.id FROM Loan loan WHERE loan.loanStatus = :statusId";
|
String FIND_ALL_LOAN_IDS_BY_STATUS_ID = "SELECT loan.id FROM Loan loan WHERE loan.loanStatus = :statusId";
|
||||||
|
|
||||||
|
String LOANS_FOR_ACCRUAL = "select l from Loan l left join l.loanInterestRecalculationDetails recalcDetails "
|
||||||
|
+ "where l.loanStatus = 300 and l.isNpa = false and l.chargedOff = false "
|
||||||
|
+ "and l.loanProduct.accountingRule = :accountingType "
|
||||||
|
+ "and (recalcDetails.isCompoundingToBePostedAsTransaction is null or recalcDetails.isCompoundingToBePostedAsTransaction = false) "
|
||||||
|
+ "and (exists (select ls.id from LoanRepaymentScheduleInstallment ls where ls.loan.id = l.id and ls.isDownPayment = false "
|
||||||
|
+ "and ((coalesce(ls.interestCharged, 0) - coalesce(ls.interestWaived, 0)) <> coalesce(ls.interestAccrued, 0) "
|
||||||
|
+ "or (coalesce(ls.feeChargesCharged, 0) - coalesce(ls.feeChargesWaived, 0)) <> coalesce(ls.feeAccrued, 0) "
|
||||||
|
+ "or (coalesce(ls.penaltyCharges, 0) - coalesce(ls.penaltyChargesWaived, 0)) <> coalesce(ls.penaltyAccrued, 0)) ";
|
||||||
|
String FIND_LOANS_FOR_PERIODIC_ACCRUAL = LOANS_FOR_ACCRUAL
|
||||||
|
+ "and (:futureCharges = true or ls.fromDate < :tillDate or (ls.installmentNumber = (select min(lsi.installmentNumber) from LoanRepaymentScheduleInstallment lsi where lsi.loan.id = l.id and lsi.isDownPayment = false) and ls.fromDate = :tillDate))))";
|
||||||
|
String FIND_LOANS_FOR_ADD_ACCRUAL = LOANS_FOR_ACCRUAL + "and (:futureCharges = true or ls.dueDate <= :tillDate)))";
|
||||||
|
|
||||||
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
||||||
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
|
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
|
||||||
@Param("loanType") Integer loanType);
|
@Param("loanType") Integer loanType);
|
||||||
@@ -227,4 +239,12 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
|||||||
|
|
||||||
@Query(FIND_ALL_LOAN_IDS_BY_STATUS_ID)
|
@Query(FIND_ALL_LOAN_IDS_BY_STATUS_ID)
|
||||||
List<Long> findLoanIdByStatusId(@Param("statusId") Integer statusId);
|
List<Long> findLoanIdByStatusId(@Param("statusId") Integer statusId);
|
||||||
|
|
||||||
|
@Query(FIND_LOANS_FOR_PERIODIC_ACCRUAL)
|
||||||
|
List<Loan> findLoansForPeriodicAccrual(@Param("accountingType") Integer accountingType, @Param("tillDate") LocalDate tillDate,
|
||||||
|
@Param("futureCharges") boolean futureCharges);
|
||||||
|
|
||||||
|
@Query(FIND_LOANS_FOR_ADD_ACCRUAL)
|
||||||
|
List<Loan> findLoansForAddAccrual(@Param("accountingType") Integer accountingType, @Param("tillDate") LocalDate tillDate,
|
||||||
|
@Param("futureCharges") boolean futureCharges);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -263,4 +263,12 @@ public class LoanRepositoryWrapper {
|
|||||||
return repository.findLoanIdByStatusId(statusId);
|
return repository.findLoanIdByStatusId(statusId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Loan> findLoansForPeriodicAccrual(Integer accountingType, LocalDate tillDate, boolean futureCharges) {
|
||||||
|
return repository.findLoansForPeriodicAccrual(accountingType, tillDate, futureCharges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Loan> findLoansForAddAccrual(Integer accountingType, LocalDate tillDate, boolean futureCharges) {
|
||||||
|
return repository.findLoansForAddAccrual(accountingType, tillDate, futureCharges);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-91
@@ -252,26 +252,29 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
|
|
||||||
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
|
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
|
||||||
final LocalDate interestAppliedDate, final ExternalId externalId) {
|
final LocalDate interestAppliedDate, final ExternalId externalId) {
|
||||||
BigDecimal principalPortion = null;
|
|
||||||
BigDecimal feesPortion = null;
|
|
||||||
BigDecimal penaltiesPortion = null;
|
|
||||||
BigDecimal interestPortion = amount.getAmount();
|
BigDecimal interestPortion = amount.getAmount();
|
||||||
BigDecimal overPaymentPortion = null;
|
return accrueTransaction(loan, office, interestAppliedDate, interestPortion, interestPortion, null, null, externalId);
|
||||||
boolean reversed = false;
|
}
|
||||||
PaymentDetail paymentDetail = null;
|
|
||||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion,
|
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
|
||||||
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
|
final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
|
||||||
|
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
|
||||||
|
externalId);
|
||||||
|
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
|
||||||
|
return applyCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||||
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
|
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
|
||||||
final ExternalId externalId) {
|
final ExternalId externalId) {
|
||||||
BigDecimal principalPortion = null;
|
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, null, interestPortion,
|
||||||
BigDecimal overPaymentPortion = null;
|
feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||||
boolean reversed = false;
|
}
|
||||||
PaymentDetail paymentDetail = null;
|
|
||||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion,
|
public static LoanTransaction accrualAdjustment(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||||
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
|
final BigDecimal interestPortion, final BigDecimal feePortion, final BigDecimal penaltyPortion, final ExternalId externalId) {
|
||||||
|
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue(), dateOf, amount, null, interestPortion,
|
||||||
|
feePortion, penaltyPortion, null, false, null, externalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
||||||
@@ -325,14 +328,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return newTransaction;
|
return newTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
|
|
||||||
final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
|
|
||||||
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
|
|
||||||
externalId);
|
|
||||||
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
|
|
||||||
return applyCharge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
|
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
|
||||||
final ExternalId externalId, PaymentDetail paymentDetail) {
|
final ExternalId externalId, PaymentDetail paymentDetail) {
|
||||||
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, amount.getAmount(),
|
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, amount.getAmount(),
|
||||||
@@ -492,47 +487,41 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
* penaltyCharges
|
* penaltyCharges
|
||||||
*/
|
*/
|
||||||
public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
||||||
final MonetaryCurrency currency = principal.getCurrency();
|
this.principalPortion = MathUtil.zeroToNull(MathUtil.add(getPrincipalPortion(), MathUtil.toBigDecimal(principal)));
|
||||||
this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal).getAmount());
|
this.interestPortion = MathUtil.zeroToNull(MathUtil.add(getInterestPortion(), MathUtil.toBigDecimal(interest)));
|
||||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
|
|
||||||
updateChargesComponents(feeCharges, penaltyCharges);
|
updateChargesComponents(feeCharges, penaltyCharges);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
|
public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
|
||||||
final MonetaryCurrency currency = feeCharges.getCurrency();
|
this.feeChargesPortion = MathUtil.zeroToNull(MathUtil.add(getFeeChargesPortion(), MathUtil.toBigDecimal(feeCharges)));
|
||||||
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
|
this.penaltyChargesPortion = MathUtil.zeroToNull(MathUtil.add(getPenaltyChargesPortion(), MathUtil.toBigDecimal(penaltyCharges)));
|
||||||
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges, final Money unrecognizedCharges) {
|
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges, final Money unrecognizedCharges) {
|
||||||
final MonetaryCurrency currency = feeCharges.getCurrency();
|
this.feeChargesPortion = MathUtil.zeroToNull(MathUtil.add(getFeeChargesPortion(), MathUtil.toBigDecimal(feeCharges)));
|
||||||
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
|
this.penaltyChargesPortion = MathUtil.zeroToNull(MathUtil.add(getPenaltyChargesPortion(), MathUtil.toBigDecimal(penaltyCharges)));
|
||||||
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
|
this.unrecognizedIncomePortion = MathUtil
|
||||||
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedCharges).getAmount());
|
.zeroToNull(MathUtil.add(getUnrecognizedIncomePortion(), MathUtil.toBigDecimal(unrecognizedCharges)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInterestComponent(final Money interest, final Money unrecognizedInterest) {
|
private void updateInterestComponent(final Money interest, final Money unrecognizedInterest) {
|
||||||
final MonetaryCurrency currency = interest.getCurrency();
|
this.interestPortion = MathUtil.zeroToNull(MathUtil.add(getInterestPortion(), MathUtil.toBigDecimal(interest)));
|
||||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
|
this.unrecognizedIncomePortion = MathUtil
|
||||||
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
|
.zeroToNull(MathUtil.add(getUnrecognizedIncomePortion(), MathUtil.toBigDecimal(unrecognizedInterest)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void adjustInterestComponent(final MonetaryCurrency currency) {
|
public void adjustInterestComponent() {
|
||||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount());
|
this.interestPortion = MathUtil.zeroToNull(MathUtil.subtract(getInterestPortion(), getUnrecognizedIncomePortion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
||||||
updateComponents(principal, interest, feeCharges, penaltyCharges);
|
updateComponents(principal, interest, feeCharges, penaltyCharges);
|
||||||
|
this.amount = MathUtil
|
||||||
final MonetaryCurrency currency = principal.getCurrency();
|
.nullToZero(MathUtil.add(getPrincipalPortion(), getInterestPortion(), getFeeChargesPortion(), getPenaltyChargesPortion()));
|
||||||
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(getFeeChargesPortion(currency))
|
|
||||||
.plus(getPenaltyChargesPortion(currency)).getAmount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOverPayments(final Money overPayment) {
|
public void setOverPayments(final Money overPayment) {
|
||||||
if (overPayment != null) {
|
this.overPaymentPortion = MathUtil.zeroToNull(MathUtil.toBigDecimal(overPayment));
|
||||||
this.overPaymentPortion = defaultToNullIfZero(overPayment.getAmount());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getPrincipalPortion(final MonetaryCurrency currency) {
|
public Money getPrincipalPortion(final MonetaryCurrency currency) {
|
||||||
@@ -720,48 +709,20 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return getTypeOf().isReAmortize() && isNotReversed();
|
return getTypeOf().isReAmortize() && isNotReversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAccrualActivity() {
|
|
||||||
return getTypeOf().isAccrualActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIdentifiedBy(final Long identifier) {
|
|
||||||
return getId().equals(identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBelongingToLoanOf(final Loan check) {
|
|
||||||
return this.loan.getId().equals(check.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNotBelongingToLoanOf(final Loan check) {
|
|
||||||
return !isBelongingToLoanOf(check);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNonZero() {
|
|
||||||
return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isGreaterThan(final Money monetaryAmount) {
|
public boolean isGreaterThan(final Money monetaryAmount) {
|
||||||
return getAmount(monetaryAmount.getCurrency()).isGreaterThan(monetaryAmount);
|
return MathUtil.isGreaterThan(amount, MathUtil.toBigDecimal(monetaryAmount));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGreaterThanZero(final MonetaryCurrency currency) {
|
public boolean isGreaterThanZero() {
|
||||||
return getAmount(currency).isGreaterThanZero();
|
return MathUtil.isGreaterThanZero(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
|
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
|
||||||
return isNonZero() && this.amount.compareTo(totalOverpaid) <= 0;
|
return isGreaterThanZero() && !MathUtil.isGreaterThan(amount, totalOverpaid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNotZero(final MonetaryCurrency currency) {
|
public boolean isNotZero() {
|
||||||
return !getAmount(currency).isZero();
|
return !MathUtil.isEmpty(amount);
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
|
|
||||||
BigDecimal result = value;
|
|
||||||
if (BigDecimal.ZERO.compareTo(value) == 0) {
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanTransactionData toData(final CurrencyData currencyData, final AccountTransferData transfer) {
|
public LoanTransactionData toData(final CurrencyData currencyData, final AccountTransferData transfer) {
|
||||||
@@ -785,7 +746,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
thisTransactionData.put("id", getId());
|
thisTransactionData.put("id", getId());
|
||||||
thisTransactionData.put("officeId", this.office.getId());
|
thisTransactionData.put("officeId", this.office.getId());
|
||||||
thisTransactionData.put("type", transactionType);
|
thisTransactionData.put("type", transactionType);
|
||||||
thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
|
thisTransactionData.put("reversed", isReversed());
|
||||||
thisTransactionData.put("date", getTransactionDate());
|
thisTransactionData.put("date", getTransactionDate());
|
||||||
thisTransactionData.put("currencyCode", currencyCode);
|
thisTransactionData.put("currencyCode", currencyCode);
|
||||||
thisTransactionData.put("amount", this.amount);
|
thisTransactionData.put("amount", this.amount);
|
||||||
@@ -871,16 +832,37 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAccrual() {
|
public boolean isAccrual() {
|
||||||
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
|
return getTypeOf().isAccrual();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualAdjustment() {
|
||||||
|
return getTypeOf().isAccrualAdjustment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualActivity() {
|
||||||
|
return getTypeOf().isAccrualActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualRelated() {
|
||||||
|
return isAccrual() || isAccrualAdjustment() || isAccrualActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWaiveCharge() {
|
||||||
|
return getTypeOf().isWaiveCharges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWaiveInterest() {
|
||||||
|
return getTypeOf().isWaiveInterest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNonMonetaryTransaction() {
|
public boolean isNonMonetaryTransaction() {
|
||||||
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|
LoanTransactionType type = getTypeOf();
|
||||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf()) || LoanTransactionType.ACCRUAL.equals(getTypeOf())
|
return isNotReversed() && (type == LoanTransactionType.CONTRA || type == LoanTransactionType.MARKED_FOR_RESCHEDULING
|
||||||
|| LoanTransactionType.ACCRUAL_ACTIVITY.equals(getTypeOf()) || LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
|
|| type == LoanTransactionType.ACCRUAL || type == LoanTransactionType.ACCRUAL_ADJUSTMENT
|
||||||
|| LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf())
|
|| type == LoanTransactionType.ACCRUAL_ACTIVITY || type == LoanTransactionType.APPROVE_TRANSFER
|
||||||
|| LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()) || LoanTransactionType.CHARGE_OFF.equals(getTypeOf())
|
|| type == LoanTransactionType.INITIATE_TRANSFER || type == LoanTransactionType.REJECT_TRANSFER
|
||||||
|| LoanTransactionType.REAMORTIZE.equals(getTypeOf()) || LoanTransactionType.REAGE.equals(getTypeOf()));
|
|| type == LoanTransactionType.WITHDRAW_TRANSFER || type == LoanTransactionType.CHARGE_OFF
|
||||||
|
|| type == LoanTransactionType.REAMORTIZE || type == LoanTransactionType.REAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||||
@@ -985,11 +967,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
||||||
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement() || isRepayment() || isAccrualActivity();
|
return isNotReversed() && (isDisbursement() || isAccrualActivity() || isRepaymentAtDisbursement() || isRepayment());
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAccrualTransaction() {
|
|
||||||
return isAccrual();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
|
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
|
||||||
|
|||||||
+14
-14
@@ -24,12 +24,12 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_loan_transaction_repayment_schedule_mapping")
|
@Table(name = "m_loan_transaction_repayment_schedule_mapping")
|
||||||
@@ -86,21 +86,13 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
|||||||
return (value == null || value.isZero()) ? null : value.getAmount();
|
return (value == null || value.isZero()) ? null : value.getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal defaultToZeroIfNull(final BigDecimal value) {
|
|
||||||
BigDecimal result = value;
|
|
||||||
if (value == null) {
|
|
||||||
result = BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
|
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
|
||||||
return this.installment;
|
return this.installment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateComponents(@NotNull Money principal, @NotNull Money interest, @NotNull Money feeCharges,
|
public void updateComponents(Money principal, Money interest, Money feeCharges, Money penaltyCharges) {
|
||||||
@NotNull Money penaltyCharges) {
|
updateComponents(MathUtil.toBigDecimal(principal), MathUtil.toBigDecimal(interest), MathUtil.toBigDecimal(feeCharges),
|
||||||
updateComponents(principal.getAmount(), interest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount());
|
MathUtil.toBigDecimal(penaltyCharges));
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
void updateComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||||
@@ -112,8 +104,7 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAmount() {
|
private void updateAmount() {
|
||||||
this.amount = defaultToZeroIfNull(getPrincipalPortion()).add(defaultToZeroIfNull(getInterestPortion()))
|
this.amount = MathUtil.add(getPrincipalPortion(), getInterestPortion(), getFeeChargesPortion(), getPenaltyChargesPortion());
|
||||||
.add(defaultToZeroIfNull(getFeeChargesPortion())).add(defaultToZeroIfNull(getPenaltyChargesPortion()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
public void setComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||||
@@ -146,6 +137,15 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
|||||||
return Money.of(currency, this.penaltyChargesPortion);
|
return Money.of(currency, this.penaltyChargesPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getPortion(AllocationType allocationType) {
|
||||||
|
return switch (allocationType) {
|
||||||
|
case PRINCIPAL -> getPrincipalPortion();
|
||||||
|
case INTEREST -> getInterestPortion();
|
||||||
|
case FEE -> getFeeChargesPortion();
|
||||||
|
case PENALTY -> getPenaltyChargesPortion();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public LoanTransaction getLoanTransaction() {
|
public LoanTransaction getLoanTransaction() {
|
||||||
return loanTransaction;
|
return loanTransaction;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -66,6 +66,7 @@ public enum LoanTransactionType {
|
|||||||
INTEREST_PAYMENT_WAIVER(31, "loanTransactionType.interestPaymentWaiver"), //
|
INTEREST_PAYMENT_WAIVER(31, "loanTransactionType.interestPaymentWaiver"), //
|
||||||
ACCRUAL_ACTIVITY(32, "loanTransactionType.accrualActivity"), //
|
ACCRUAL_ACTIVITY(32, "loanTransactionType.accrualActivity"), //
|
||||||
INTEREST_REFUND(33, "loanTransactionType.interestRefund"), //
|
INTEREST_REFUND(33, "loanTransactionType.interestRefund"), //
|
||||||
|
ACCRUAL_ADJUSTMENT(34, "loanTransactionType.accrualAdjustment"), //
|
||||||
;
|
;
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
@@ -115,6 +116,7 @@ public enum LoanTransactionType {
|
|||||||
case 31 -> LoanTransactionType.INTEREST_PAYMENT_WAIVER;
|
case 31 -> LoanTransactionType.INTEREST_PAYMENT_WAIVER;
|
||||||
case 32 -> LoanTransactionType.ACCRUAL_ACTIVITY;
|
case 32 -> LoanTransactionType.ACCRUAL_ACTIVITY;
|
||||||
case 33 -> LoanTransactionType.INTEREST_REFUND;
|
case 33 -> LoanTransactionType.INTEREST_REFUND;
|
||||||
|
case 34 -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
|
||||||
default -> LoanTransactionType.INVALID;
|
default -> LoanTransactionType.INVALID;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -228,4 +230,7 @@ public enum LoanTransactionType {
|
|||||||
return this.equals(LoanTransactionType.INTEREST_REFUND);
|
return this.equals(LoanTransactionType.INTEREST_REFUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualAdjustment() {
|
||||||
|
return this == LoanTransactionType.ACCRUAL_ADJUSTMENT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-9
@@ -45,14 +45,14 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
|||||||
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
|
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
|
||||||
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
||||||
}
|
}
|
||||||
LoanChargePaidBy accrualBy = null;
|
List<LoanChargePaidBy> accruals = null;
|
||||||
if (loanCharge.isSpecifiedDueDate()) {
|
if (loanCharge.isSpecifiedDueDate()) {
|
||||||
LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, installments);
|
LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, installments);
|
||||||
if (addedPeriod != null) {
|
if (addedPeriod != null) {
|
||||||
addedPeriod.updateObligationsMet(currency, disbursementDate);
|
addedPeriod.updateObligationsMet(currency, disbursementDate);
|
||||||
}
|
}
|
||||||
accrualBy = loanCharge.getLoanChargePaidBySet().stream().filter(e -> e.getLoanTransaction().isAccrual()).findFirst()
|
accruals = loanCharge.getLoanChargePaidBySet().stream().filter(e -> !e.getLoanTransaction().isReversed()
|
||||||
.orElse(null);
|
&& (e.getLoanTransaction().isAccrual() || e.getLoanTransaction().isAccrualAdjustment())).toList();
|
||||||
}
|
}
|
||||||
LocalDate startDate = disbursementDate;
|
LocalDate startDate = disbursementDate;
|
||||||
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
@@ -61,7 +61,8 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent();
|
boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent();
|
||||||
boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
Integer installmentNumber = installment.getInstallmentNumber();
|
||||||
|
boolean isFirstPeriod = installmentNumber.equals(firstNormalInstallmentNumber);
|
||||||
Predicate<LoanCharge> feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement();
|
Predicate<LoanCharge> feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement();
|
||||||
LocalDate dueDate = installment.getDueDate();
|
LocalDate dueDate = installment.getDueDate();
|
||||||
final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest,
|
final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest,
|
||||||
@@ -82,13 +83,19 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
|||||||
installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived,
|
installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived,
|
||||||
penaltyChargesWrittenOff);
|
penaltyChargesWrittenOff);
|
||||||
|
|
||||||
if (accrualBy != null && installment.isAdditional() && loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) {
|
if (accruals != null && !accruals.isEmpty() && installment.isAdditional()
|
||||||
Money amount = Money.of(currency, accrualBy.getAmount());
|
&& loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) {
|
||||||
|
BigDecimal amount = null;
|
||||||
|
for (LoanChargePaidBy accrual : accruals) {
|
||||||
|
accrual.setInstallmentNumber(installmentNumber);
|
||||||
|
amount = accrual.getLoanTransaction().isAccrual() ? MathUtil.add(amount, accrual.getAmount())
|
||||||
|
: MathUtil.subtract(amount, accrual.getAmount());
|
||||||
|
}
|
||||||
|
Money accruedAmount = Money.of(currency, MathUtil.negativeToZero(amount));
|
||||||
boolean isFee = loanCharge.isFeeCharge();
|
boolean isFee = loanCharge.isFeeCharge();
|
||||||
installment.updateAccrualPortion(installment.getInterestAccrued(currency),
|
installment.updateAccrualPortion(installment.getInterestAccrued(currency),
|
||||||
MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? amount : null)),
|
MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? accruedAmount : null)),
|
||||||
MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : amount)));
|
MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : accruedAmount)));
|
||||||
accrualBy.setInstallmentNumber(installment.getInstallmentNumber());
|
|
||||||
}
|
}
|
||||||
startDate = dueDate;
|
startDate = dueDate;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -166,7 +166,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
// pass through for new transactions
|
// pass through for new transactions
|
||||||
if (loanTransaction.getId() == null) {
|
if (loanTransaction.getId() == null) {
|
||||||
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||||
loanTransaction.adjustInterestComponent(currency);
|
loanTransaction.adjustInterestComponent();
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties)
|
* For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties)
|
||||||
@@ -178,7 +178,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
// re-process transaction
|
// re-process transaction
|
||||||
processLatestTransaction(newLoanTransaction,
|
processLatestTransaction(newLoanTransaction,
|
||||||
new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||||
newLoanTransaction.adjustInterestComponent(currency);
|
newLoanTransaction.adjustInterestComponent();
|
||||||
/**
|
/**
|
||||||
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
||||||
* changedTransactionDetail accordingly
|
* changedTransactionDetail accordingly
|
||||||
|
|||||||
+39
@@ -18,6 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isAfterPeriod;
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isBeforePeriod;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -2805,4 +2809,39 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
|||||||
.feeCharges(feeCharges) //
|
.feeCharges(feeCharges) //
|
||||||
.penaltyCharges(penaltyCharges);
|
.penaltyCharges(penaltyCharges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate targetDate) {
|
||||||
|
if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isBeforePeriod(targetDate, installment, false)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Loan loan = installment.getLoan();
|
||||||
|
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
||||||
|
BigDecimal interest = installment.getInterestCharged();
|
||||||
|
Money zero = Money.zero(currency);
|
||||||
|
if (MathUtil.isEmpty(interest)) {
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
|
if (isAfterPeriod(targetDate, installment) || DateUtils.isEqual(targetDate, installment.getDueDate())) {
|
||||||
|
return installment.getInterestCharged(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal interestPortion;
|
||||||
|
LocalDate startDate = installment.getFromDate();
|
||||||
|
LocalDate dueDate = installment.getDueDate();
|
||||||
|
if (DateUtils.isBefore(startDate, loan.getInterestChargedFromDate())) {
|
||||||
|
startDate = loan.getInterestChargedFromDate();
|
||||||
|
}
|
||||||
|
if (!DateUtils.isBefore(startDate, dueDate) || !DateUtils.isBefore(startDate, targetDate)) {
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
|
int totalNumberOfDays = DateUtils.getExactDifferenceInDays(startDate, dueDate);
|
||||||
|
int daysToBeAccrued = DateUtils.getExactDifferenceInDays(startDate, targetDate);
|
||||||
|
double interestPerDay = interest.doubleValue() / totalNumberOfDays;
|
||||||
|
interestPortion = BigDecimal.valueOf(interestPerDay * daysToBeAccrued);
|
||||||
|
return Money.of(currency, interestPortion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -18,14 +18,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||||
|
|
||||||
@@ -42,4 +45,5 @@ public interface LoanScheduleGenerator {
|
|||||||
MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||||
|
|
||||||
|
Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate targetDate);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-12
@@ -18,30 +18,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
public interface LoanAccrualsProcessingService {
|
public interface LoanAccrualsProcessingService {
|
||||||
|
|
||||||
void addPeriodicAccruals(LocalDate tilldate) throws MultiException;
|
void addPeriodicAccruals(@NotNull LocalDate tilldate) throws MultiException;
|
||||||
|
|
||||||
void addPeriodicAccruals(LocalDate tilldate, Loan loan) throws MultiException;
|
void addPeriodicAccruals(@NotNull LocalDate tilldate, @NotNull Loan loan) throws MultiException;
|
||||||
|
|
||||||
void addAccrualAccounting(Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
|
void addAccruals(@NotNull LocalDate tilldate) throws MultiException;
|
||||||
|
|
||||||
|
void reprocessExistingAccruals(@NotNull Loan loan);
|
||||||
|
|
||||||
|
void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled);
|
||||||
|
|
||||||
|
void processIncomePostingAndAccruals(@NotNull Loan loan);
|
||||||
|
|
||||||
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
||||||
|
|
||||||
void reprocessExistingAccruals(Loan loan);
|
void processAccrualsOnLoanClosure(@NotNull Loan loan);
|
||||||
|
|
||||||
void processAccrualsForInterestRecalculation(Loan loan, boolean isInterestRecalculationEnabled);
|
void processAccrualsOnLoanReopen(@NotNull Loan loan);
|
||||||
|
|
||||||
void processIncomePostingAndAccruals(Loan loan);
|
void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate,
|
||||||
|
@NotNull List<LoanTransaction> newAccrualTransactions);
|
||||||
void processAccrualsForLoanClosure(Loan loan);
|
|
||||||
|
|
||||||
void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate, Collection<LoanTransaction> newAccrualTransactions);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||||
|
import org.apache.fineract.portfolio.fund.domain.Fund;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
public interface LoanAssembler {
|
||||||
|
|
||||||
|
Loan assembleFrom(Long accountId);
|
||||||
|
|
||||||
|
Loan assembleFrom(JsonCommand command);
|
||||||
|
|
||||||
|
void setHelpers(Loan loanAccount);
|
||||||
|
|
||||||
|
void accountNumberGeneration(JsonCommand command, Loan loan);
|
||||||
|
|
||||||
|
CodeValue findCodeValueByIdIfProvided(Long codeValueId);
|
||||||
|
|
||||||
|
Fund findFundByIdIfProvided(Long fundId);
|
||||||
|
|
||||||
|
Staff findLoanOfficerByIdIfProvided(Long loanOfficerId);
|
||||||
|
|
||||||
|
Map<String, Object> updateFrom(JsonCommand command, Loan loan);
|
||||||
|
|
||||||
|
Map<String, Object> updateLoanApplicationAttributesForWithdrawal(Loan loan, JsonCommand command, AppUser currentUser);
|
||||||
|
|
||||||
|
Map<String, Object> updateLoanApplicationAttributesForRejection(Loan loan, JsonCommand command, AppUser currentUser);
|
||||||
|
}
|
||||||
+2
-3
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||||
@@ -38,12 +39,10 @@ public interface LoanChargeReadPlatformService {
|
|||||||
|
|
||||||
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
|
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
|
||||||
|
|
||||||
Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
|
List<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
|
||||||
|
|
||||||
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Loan loan, Charge charge, Integer periodNumber);
|
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Loan loan, Charge charge, Integer periodNumber);
|
||||||
|
|
||||||
Collection<LoanChargeData> retrieveLoanChargesForAccrual(Long loanId);
|
|
||||||
|
|
||||||
Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
|
Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
|
||||||
Integer installmentNumber);
|
Integer installmentNumber);
|
||||||
|
|
||||||
|
|||||||
+2
@@ -323,6 +323,8 @@ public final class LoanEnumerations {
|
|||||||
LoanTransactionType.ACCRUAL_ACTIVITY.getCode(), "Accrual Activity");
|
LoanTransactionType.ACCRUAL_ACTIVITY.getCode(), "Accrual Activity");
|
||||||
case INTEREST_REFUND -> new LoanTransactionEnumData(LoanTransactionType.INTEREST_REFUND.getValue().longValue(),
|
case INTEREST_REFUND -> new LoanTransactionEnumData(LoanTransactionType.INTEREST_REFUND.getValue().longValue(),
|
||||||
LoanTransactionType.INTEREST_REFUND.getCode(), "Interest Refund");
|
LoanTransactionType.INTEREST_REFUND.getCode(), "Interest Refund");
|
||||||
|
case ACCRUAL_ADJUSTMENT -> new LoanTransactionEnumData(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue().longValue(),
|
||||||
|
LoanTransactionType.ACCRUAL_ADJUSTMENT.getCode(), "Accrual Adjustment");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -46,4 +46,5 @@
|
|||||||
<include relativeToChangelogFile="true" file="parts/1021_add_loan_status_change_history.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1021_add_loan_status_change_history.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/1022_add_interest_refund_support.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1022_add_interest_refund_support.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/1023_add_charge_off_behaviour.xml"/>
|
<include relativeToChangelogFile="true" file="parts/1023_add_charge_off_behaviour.xml"/>
|
||||||
|
<include relativeToChangelogFile="true" file="parts/1024_accrual_adjustment_transaction_type.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||||
|
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<insert tableName="m_external_event_configuration">
|
||||||
|
<column name="type" value="LoanAccrualAdjustmentTransactionBusinessEvent"/>
|
||||||
|
<column name="enabled" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="fineract" id="2">
|
||||||
|
<insert tableName="r_enum_value">
|
||||||
|
<column name="enum_name" value="transaction_type_enum"/>
|
||||||
|
<column name="enum_id" valueNumeric="34"/>
|
||||||
|
<column name="enum_message_property" value="Accrual Adjustment"/>
|
||||||
|
<column name="enum_value" value="Accrual Adjustment"/>
|
||||||
|
<column name="enum_type" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+228
-197
@@ -73,6 +73,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
||||||
@@ -95,6 +96,8 @@ import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocat
|
|||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -104,6 +107,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy";
|
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy";
|
||||||
|
|
||||||
public final EMICalculator emiCalculator;
|
public final EMICalculator emiCalculator;
|
||||||
|
public final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
public final InterestRefundService interestRefundService;
|
public final InterestRefundService interestRefundService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -152,7 +156,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
// only for progressive loans
|
// only for progressive loans
|
||||||
public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactions(
|
public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessProgressiveLoanTransactions(
|
||||||
LocalDate disbursementDate, LocalDate currentDate, List<LoanTransaction> loanTransactions, MonetaryCurrency currency,
|
LocalDate disbursementDate, LocalDate targetDate, List<LoanTransaction> loanTransactions, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
if (loanTransactions.isEmpty()) {
|
if (loanTransactions.isEmpty()) {
|
||||||
@@ -179,8 +183,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
final Loan loan = loanTransactions.get(0).getLoan();
|
final Loan loan = loanTransactions.get(0).getLoan();
|
||||||
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||||
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
|
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
|
||||||
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail,
|
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
|
||||||
installmentAmountInMultiplesOf, installments, overpaymentHolder.getMoneyObject().getMc());
|
loanProductRelatedDetail, installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc());
|
||||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
||||||
changedTransactionDetail, scheduleModel);
|
changedTransactionDetail, scheduleModel);
|
||||||
|
|
||||||
@@ -217,7 +221,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LoanTransaction newTransaction = newTransactionMappings.get(oldTransactionId);
|
LoanTransaction newTransaction = newTransactionMappings.get(oldTransactionId);
|
||||||
createNewTransaction(oldTransaction, newTransaction, ctx);
|
createNewTransaction(oldTransaction, newTransaction, ctx);
|
||||||
}
|
}
|
||||||
recalculateInterestForDate(currentDate, ctx);
|
recalculateInterestForDate(targetDate, ctx);
|
||||||
List<LoanTransaction> txs = changeOperations.stream() //
|
List<LoanTransaction> txs = changeOperations.stream() //
|
||||||
.filter(ChangeOperation::isTransaction) //
|
.filter(ChangeOperation::isTransaction) //
|
||||||
.map(e -> e.getLoanTransaction().get()).toList();
|
.map(e -> e.getLoanTransaction().get()).toList();
|
||||||
@@ -225,6 +229,32 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return Pair.of(changedTransactionDetail, scheduleModel);
|
return Pair.of(changedTransactionDetail, scheduleModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
||||||
|
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||||
|
LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||||
|
return reprocessProgressiveLoanTransactions(disbursementDate, currentDate, loanTransactions, currency, installments, charges)
|
||||||
|
.getLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
|
||||||
|
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
|
||||||
|
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
|
||||||
|
List<LoanTransaction> transactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||||
|
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||||
|
Set<LoanCharge> charges = loan.getActiveCharges();
|
||||||
|
return reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), targetDate, transactions, currency, installments, charges)
|
||||||
|
.getRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static LoanTransaction getProcessedTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction transaction) {
|
||||||
|
LoanTransaction newTransaction = changedTransactionDetail.getNewTransactionMappings().get(transaction.getId());
|
||||||
|
return newTransaction == null ? transaction : newTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
private void processInterestRateChange(final List<LoanRepaymentScheduleInstallment> installments,
|
private void processInterestRateChange(final List<LoanRepaymentScheduleInstallment> installments,
|
||||||
final LoanTermVariationsData interestRateChange, final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
final LoanTermVariationsData interestRateChange, final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||||
final LocalDate interestRateChangeSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
final LocalDate interestRateChangeSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom();
|
||||||
@@ -249,29 +279,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
|
|
||||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
|
||||||
LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
|
||||||
return reprocessProgressiveLoanTransactions(disbursementDate, currentDate, loanTransactions, currency, installments, charges)
|
|
||||||
.getLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static LoanTransaction getProcessedTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction transaction) {
|
|
||||||
LoanTransaction newTransaction = changedTransactionDetail.getNewTransactionMappings().get(transaction.getId());
|
|
||||||
return newTransaction == null ? transaction : newTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
public void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||||
switch (loanTransaction.getTypeOf()) {
|
switch (loanTransaction.getTypeOf()) {
|
||||||
case DISBURSEMENT -> handleDisbursement(loanTransaction, ctx);
|
case DISBURSEMENT -> handleDisbursement(loanTransaction, ctx);
|
||||||
case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
case WRITEOFF -> handleWriteOff(loanTransaction, ctx);
|
||||||
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges());
|
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx);
|
||||||
case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
|
case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
|
||||||
case CREDIT_BALANCE_REFUND ->
|
case CREDIT_BALANCE_REFUND -> handleCreditBalanceRefund(loanTransaction, ctx);
|
||||||
handleCreditBalanceRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
|
|
||||||
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
|
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
|
||||||
WAIVE_INTEREST, RECOVERY_REPAYMENT, INTEREST_PAYMENT_WAIVER ->
|
WAIVE_INTEREST, RECOVERY_REPAYMENT, INTEREST_PAYMENT_WAIVER ->
|
||||||
handleRepayment(loanTransaction, ctx);
|
handleRepayment(loanTransaction, ctx);
|
||||||
@@ -281,7 +296,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
||||||
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
|
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
|
||||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||||
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
|
||||||
// TODO: Cover rest of the transaction types
|
// TODO: Cover rest of the transaction types
|
||||||
default -> {
|
default -> {
|
||||||
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||||
@@ -357,9 +372,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
processCreditTransaction(loanTransaction, ctx);
|
processCreditTransaction(loanTransaction, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handleCreditBalanceRefund(LoanTransaction transaction, TransactionCtx ctx) {
|
||||||
|
super.handleCreditBalanceRefund(transaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) {
|
private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) {
|
||||||
return (loanTransaction.getLoan().getCreditAllocationRules() == null || !loanTransaction.getLoan().getCreditAllocationRules()
|
List<LoanCreditAllocationRule> creditAllocationRules = loanTransaction.getLoan().getCreditAllocationRules();
|
||||||
.stream().anyMatch(e -> e.getTransactionType().getLoanTransactionType().equals(loanTransaction.getTypeOf())));
|
return (creditAllocationRules == null || creditAllocationRules.stream()
|
||||||
|
.noneMatch(e -> e.getTransactionType().getLoanTransactionType().equals(loanTransaction.getTypeOf())));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LoanTransaction findChargebackOriginalTransaction(LoanTransaction chargebackTransaction, TransactionCtx ctx) {
|
protected LoanTransaction findChargebackOriginalTransaction(LoanTransaction chargebackTransaction, TransactionCtx ctx) {
|
||||||
@@ -396,77 +416,76 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
|
super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
|
||||||
} else {
|
} else {
|
||||||
loanTransaction.resetDerivedComponents();
|
loanTransaction.resetDerivedComponents();
|
||||||
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||||
ctx.getInstallments().sort(byDate);
|
ctx.getInstallments().sort(byDate);
|
||||||
final Money zeroMoney = Money.zero(ctx.getCurrency());
|
final Money zeroMoney = Money.zero(currency);
|
||||||
Money transactionAmount = loanTransaction.getAmount(ctx.getCurrency());
|
Money transactionAmount = loanTransaction.getAmount(currency);
|
||||||
Money totalOverpaid = ctx.getOverpaymentHolder().getMoneyObject();
|
Money totalOverpaid = ctx.getOverpaymentHolder().getMoneyObject();
|
||||||
Money amountToDistribute = MathUtil.negativeToZero(loanTransaction.getAmount(ctx.getCurrency()).minus(totalOverpaid));
|
Money amountToDistribute = MathUtil.negativeToZero(transactionAmount).minus(totalOverpaid);
|
||||||
Money overpaymentAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
|
Money overpaymentAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
|
||||||
loanTransaction.setOverPayments(overpaymentAmount);
|
loanTransaction.setOverPayments(overpaymentAmount);
|
||||||
|
if (!transactionAmount.isGreaterThanZero()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!loanTransaction.isChargeback()) {
|
||||||
|
throw new RuntimeException("Unsupported transaction " + loanTransaction.getTypeOf().name());
|
||||||
|
}
|
||||||
|
|
||||||
if (transactionAmount.isGreaterThanZero()) {
|
LoanTransaction originalTransaction = findChargebackOriginalTransaction(loanTransaction, ctx);
|
||||||
if (loanTransaction.isChargeback()) {
|
// get the original allocation from the opriginal transaction
|
||||||
LoanTransaction originalTransaction = findChargebackOriginalTransaction(loanTransaction, ctx);
|
Map<AllocationType, Money> originalAllocationNotAdjusted = getOriginalAllocation(originalTransaction, currency);
|
||||||
// get the original allocation from the opriginal transaction
|
LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules(loanTransaction);
|
||||||
Map<AllocationType, Money> originalAllocationNotAdjusted = getOriginalAllocation(originalTransaction,
|
|
||||||
ctx.getCurrency());
|
|
||||||
LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules(loanTransaction);
|
|
||||||
|
|
||||||
// if there were earlier chargebacks then let's calculate the remaining amounts for each portion
|
// if there were earlier chargebacks then let's calculate the remaining amounts for each portion
|
||||||
Map<AllocationType, Money> originalAllocation = adjustOriginalAllocationWithFormerChargebacks(originalTransaction,
|
Map<AllocationType, Money> originalAllocation = adjustOriginalAllocationWithFormerChargebacks(originalTransaction,
|
||||||
originalAllocationNotAdjusted, loanTransaction, ctx, chargeBackAllocationRule);
|
originalAllocationNotAdjusted, loanTransaction, ctx, chargeBackAllocationRule);
|
||||||
|
|
||||||
// calculate the current chargeback allocation
|
// calculate the current chargeback allocation
|
||||||
Map<AllocationType, Money> chargebackAllocation = calculateChargebackAllocationMap(originalAllocation,
|
Map<AllocationType, Money> chargebackAllocation = calculateChargebackAllocationMap(originalAllocation,
|
||||||
transactionAmount.getAmount(), chargeBackAllocationRule.getAllocationTypes(), ctx.getCurrency());
|
transactionAmount.getAmount(), chargeBackAllocationRule.getAllocationTypes(), currency);
|
||||||
|
|
||||||
loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), chargebackAllocation.get(INTEREST),
|
loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), chargebackAllocation.get(INTEREST),
|
||||||
chargebackAllocation.get(FEE), chargebackAllocation.get(PENALTY));
|
chargebackAllocation.get(FEE), chargebackAllocation.get(PENALTY));
|
||||||
|
|
||||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||||
boolean loanTransactionMapped = false;
|
boolean loanTransactionMapped = false;
|
||||||
LocalDate pastDueDate = null;
|
LocalDate pastDueDate = null;
|
||||||
for (final LoanRepaymentScheduleInstallment currentInstallment : ctx.getInstallments()) {
|
for (final LoanRepaymentScheduleInstallment currentInstallment : ctx.getInstallments()) {
|
||||||
pastDueDate = currentInstallment.getDueDate();
|
pastDueDate = currentInstallment.getDueDate();
|
||||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
recognizeAmountsAfterChargeback(ctx, transactionDate, currentInstallment, chargebackAllocation);
|
||||||
loanTransactionMapped = true;
|
loanTransactionMapped = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// If already exists an additional installment just update the due date and
|
// If already exists an additional installment just update the due date and
|
||||||
// principal from the Loan chargeback / CBR transaction
|
// principal from the Loan chargeback / CBR transaction
|
||||||
} else if (currentInstallment.isAdditional()) {
|
} else if (currentInstallment.isAdditional()) {
|
||||||
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
|
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
|
||||||
currentInstallment.updateDueDate(transactionDate);
|
currentInstallment.updateDueDate(transactionDate);
|
||||||
}
|
|
||||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
|
||||||
loanTransactionMapped = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
recognizeAmountsAfterChargeback(ctx, transactionDate, currentInstallment, chargebackAllocation);
|
||||||
// New installment will be added (N+1 scenario)
|
loanTransactionMapped = true;
|
||||||
if (!loanTransactionMapped) {
|
break;
|
||||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
|
||||||
LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments()
|
|
||||||
.get(ctx.getInstallments().size() - 1);
|
|
||||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, currentInstallment, chargebackAllocation);
|
|
||||||
} else {
|
|
||||||
Loan loan = loanTransaction.getLoan();
|
|
||||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
|
|
||||||
(ctx.getInstallments().size() + 1), pastDueDate, transactionDate, zeroMoney.getAmount(),
|
|
||||||
zeroMoney.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(), false, null);
|
|
||||||
recognizeAmountsAfterChargeback(ctx.getCurrency(), transactionDate, installment, chargebackAllocation);
|
|
||||||
installment.markAsAdditional();
|
|
||||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allocateOverpayment(loanTransaction, ctx);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unsupported transaction " + loanTransaction.getTypeOf().name());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New installment will be added (N+1 scenario)
|
||||||
|
if (!loanTransactionMapped) {
|
||||||
|
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||||
|
LoanRepaymentScheduleInstallment currentInstallment = ctx.getInstallments().get(ctx.getInstallments().size() - 1);
|
||||||
|
recognizeAmountsAfterChargeback(ctx, transactionDate, currentInstallment, chargebackAllocation);
|
||||||
|
} else {
|
||||||
|
Loan loan = loanTransaction.getLoan();
|
||||||
|
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
|
||||||
|
(ctx.getInstallments().size() + 1), pastDueDate, transactionDate, zeroMoney.getAmount(), zeroMoney.getAmount(),
|
||||||
|
zeroMoney.getAmount(), zeroMoney.getAmount(), false, null);
|
||||||
|
recognizeAmountsAfterChargeback(ctx, transactionDate, installment, chargebackAllocation);
|
||||||
|
installment.markAsAdditional();
|
||||||
|
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allocateOverpayment(loanTransaction, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,14 +530,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return allocation;
|
return allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recognizeAmountsAfterChargeback(MonetaryCurrency currency, LocalDate localDate,
|
private void recognizeAmountsAfterChargeback(TransactionCtx ctx, LocalDate transactionDate,
|
||||||
LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
|
LoanRepaymentScheduleInstallment installment, Map<AllocationType, Money> chargebackAllocation) {
|
||||||
Money principal = chargebackAllocation.get(PRINCIPAL);
|
Money principal = chargebackAllocation.get(PRINCIPAL);
|
||||||
if (principal.isGreaterThanZero()) {
|
if (principal.isGreaterThanZero()) {
|
||||||
installment.addToCreditedPrincipal(principal.getAmount());
|
installment.addToCreditedPrincipal(principal.getAmount());
|
||||||
installment.addToPrincipal(localDate, principal);
|
installment.addToPrincipal(transactionDate, principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
Money fee = chargebackAllocation.get(FEE);
|
Money fee = chargebackAllocation.get(FEE);
|
||||||
if (fee.isGreaterThanZero()) {
|
if (fee.isGreaterThanZero()) {
|
||||||
installment.addToCreditedFee(fee.getAmount());
|
installment.addToCreditedFee(fee.getAmount());
|
||||||
@@ -577,9 +597,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return relation -> relation.getRelationType().equals(typeEnum) && Objects.equals(relation.getToTransaction().getId(), id);
|
return relation -> relation.getRelationType().equals(typeEnum) && Objects.equals(relation.getToTransaction().getId(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void handleRefund(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||||
protected void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
|
||||||
Money zero = Money.zero(currency);
|
Money zero = Money.zero(currency);
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||||
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
||||||
@@ -604,18 +623,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new, mapping(Function.identity(), toList())));
|
Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new, mapping(Function.identity(), toList())));
|
||||||
|
|
||||||
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
||||||
transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, currency, installments,
|
transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, ctx, transactionAmountUnprocessed,
|
||||||
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule,
|
paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule, transactionMappings, balances);
|
||||||
transactionMappings, charges, balances);
|
|
||||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (scheduleProcessingType.isVertical()) {
|
} else if (scheduleProcessingType.isVertical()) {
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||||
transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, currency, installments, zero,
|
transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, ctx, transactionMappings,
|
||||||
transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, charges, balances,
|
transactionAmountUnprocessed, futureInstallmentAllocationRule, balances, paymentAllocationType);
|
||||||
paymentAllocationType);
|
|
||||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -639,7 +656,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
// Reset derived component of new loan transaction and re-process transaction
|
// Reset derived component of new loan transaction and re-process transaction
|
||||||
processLatestTransaction(processTransaction, ctx);
|
processLatestTransaction(processTransaction, ctx);
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
processTransaction.adjustInterestComponent(ctx.getCurrency());
|
processTransaction.adjustInterestComponent();
|
||||||
}
|
}
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
checkRegisteredNewTransaction(loanTransaction, ctx);
|
checkRegisteredNewTransaction(loanTransaction, ctx);
|
||||||
@@ -684,7 +701,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
processTransaction.addLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
processTransaction.addLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
|
|
||||||
if (processTransaction.isInterestWaiver()) {
|
if (processTransaction.isInterestWaiver()) {
|
||||||
processTransaction.adjustInterestComponent(currency);
|
processTransaction.adjustInterestComponent();
|
||||||
}
|
}
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
processTransaction = checkRegisteredNewTransaction(transaction, ctx);
|
processTransaction = checkRegisteredNewTransaction(transaction, ctx);
|
||||||
@@ -810,51 +827,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return changeOperations;
|
return changeOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
|
||||||
ProgressiveTransactionCtx progressiveTransactionCtx = (ProgressiveTransactionCtx) transactionCtx;
|
|
||||||
if (progressiveTransactionCtx.getModel() == null) {
|
|
||||||
throw new IllegalStateException("TransactionCtx has no model");
|
|
||||||
}
|
|
||||||
disbursementTransaction.resetDerivedComponents();
|
|
||||||
final MathContext mc = MoneyHelper.getMathContext();
|
|
||||||
LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
|
|
||||||
Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
|
||||||
Money downPaymentAmount = Money.zero(progressiveTransactionCtx.getCurrency());
|
|
||||||
if (loanProductRelatedDetail.isEnableDownPayment()) {
|
|
||||||
LoanRepaymentScheduleInstallment downPaymentInstallment = progressiveTransactionCtx.getInstallments().stream()
|
|
||||||
.filter(i -> i.isDownPayment() && i.getPrincipal(progressiveTransactionCtx.getCurrency()).isZero()).findFirst()
|
|
||||||
.orElseThrow();
|
|
||||||
BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
|
|
||||||
loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
|
|
||||||
if (installmentAmountInMultiplesOf != null) {
|
|
||||||
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
|
|
||||||
}
|
|
||||||
downPaymentAmount = Money.of(transactionCtx.getCurrency(), downPaymentAmt);
|
|
||||||
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
Money amortizableAmount = disbursementTransaction.getAmount(transactionCtx.getCurrency()).minus(downPaymentAmount);
|
|
||||||
|
|
||||||
emiCalculator.addDisbursement(progressiveTransactionCtx.getModel(), disbursementTransaction.getTransactionDate(),
|
|
||||||
amortizableAmount);
|
|
||||||
|
|
||||||
if (amortizableAmount.isGreaterThanZero()) {
|
|
||||||
progressiveTransactionCtx.getModel().repaymentPeriods().forEach(rm -> {
|
|
||||||
LoanRepaymentScheduleInstallment installment = transactionCtx.getInstallments().stream()
|
|
||||||
.filter(ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment()
|
|
||||||
&& !ri.getDueDate().isBefore(disbursementTransaction.getTransactionDate()))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (installment != null) {
|
|
||||||
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
|
|
||||||
installment.updateInterestCharged(rm.getDueInterest().getAmount());
|
|
||||||
installment.updateObligationsMet(progressiveTransactionCtx.getCurrency(), disbursementTransaction.getTransactionDate());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
allocateOverpayment(disbursementTransaction, transactionCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||||
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
|
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
|
||||||
if (disbursementTransaction.getLoan().isInterestBearing()) {
|
if (disbursementTransaction.getLoan().isInterestBearing()) {
|
||||||
@@ -864,43 +836,90 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisbursementWithoutEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||||
disbursementTransaction.resetDerivedComponents();
|
ProgressiveLoanInterestScheduleModel model;
|
||||||
|
if (!(transactionCtx instanceof ProgressiveTransactionCtx)
|
||||||
|
|| (model = ((ProgressiveTransactionCtx) transactionCtx).getModel()) == null) {
|
||||||
|
throw new IllegalStateException("TransactionCtx has no model");
|
||||||
|
}
|
||||||
final MathContext mc = MoneyHelper.getMathContext();
|
final MathContext mc = MoneyHelper.getMathContext();
|
||||||
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = transactionCtx.getInstallments().stream().filter(
|
Loan loan = disbursementTransaction.getLoan();
|
||||||
i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
|
LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
|
||||||
.toList();
|
Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||||
int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
|
List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
|
||||||
LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
|
LocalDate transactionDate = disbursementTransaction.getTransactionDate();
|
||||||
Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
MonetaryCurrency currency = transactionCtx.getCurrency();
|
||||||
Money downPaymentAmount = Money.zero(transactionCtx.getCurrency());
|
Money downPaymentAmount = Money.zero(currency);
|
||||||
if (loanProductRelatedDetail.isEnableDownPayment()) {
|
if (loanProductRelatedDetail.isEnableDownPayment()) {
|
||||||
LoanRepaymentScheduleInstallment downPaymentInstallment = transactionCtx.getInstallments().stream()
|
|
||||||
.filter(i -> i.isDownPayment() && i.getPrincipal(transactionCtx.getCurrency()).isZero()).findFirst().orElseThrow();
|
|
||||||
BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
|
BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
|
||||||
loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
|
loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
|
||||||
if (installmentAmountInMultiplesOf != null) {
|
if (installmentAmountInMultiplesOf != null) {
|
||||||
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
|
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
|
||||||
}
|
}
|
||||||
downPaymentAmount = Money.of(transactionCtx.getCurrency(), downPaymentAmt);
|
downPaymentAmount = Money.of(currency, downPaymentAmt);
|
||||||
|
LoanRepaymentScheduleInstallment downPaymentInstallment = installments.stream()
|
||||||
|
.filter(i -> i.isDownPayment() && i.getPrincipal(currency).isZero()).findFirst().orElseThrow();
|
||||||
|
downPaymentInstallment.addToPrincipal(transactionDate, downPaymentAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
||||||
|
emiCalculator.addDisbursement(model, transactionDate, amortizableAmount);
|
||||||
|
|
||||||
|
disbursementTransaction.resetDerivedComponents();
|
||||||
|
if (amortizableAmount.isGreaterThanZero()) {
|
||||||
|
model.repaymentPeriods().forEach(rm -> {
|
||||||
|
LoanRepaymentScheduleInstallment installment = installments.stream().filter(
|
||||||
|
ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment() && !ri.getDueDate().isBefore(transactionDate))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
if (installment != null) {
|
||||||
|
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
|
||||||
|
installment.updateInterestCharged(rm.getDueInterest().getAmount());
|
||||||
|
installment.updateObligationsMet(currency, transactionDate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
allocateOverpayment(disbursementTransaction, transactionCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDisbursementWithoutEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||||
|
disbursementTransaction.resetDerivedComponents();
|
||||||
|
final MathContext mc = MoneyHelper.getMathContext();
|
||||||
|
MonetaryCurrency currency = transactionCtx.getCurrency();
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
|
||||||
|
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
|
||||||
|
i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
|
||||||
|
.toList();
|
||||||
|
int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
|
||||||
|
LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
|
||||||
|
Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||||
|
Money downPaymentAmount = Money.zero(currency);
|
||||||
|
if (loanProductRelatedDetail.isEnableDownPayment()) {
|
||||||
|
LoanRepaymentScheduleInstallment downPaymentInstallment = installments.stream()
|
||||||
|
.filter(i -> i.isDownPayment() && i.getPrincipal(currency).isZero()).findFirst().orElseThrow();
|
||||||
|
BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
|
||||||
|
loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
|
||||||
|
if (installmentAmountInMultiplesOf != null) {
|
||||||
|
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
|
||||||
|
}
|
||||||
|
downPaymentAmount = Money.of(currency, downPaymentAmt);
|
||||||
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
Money amortizableAmount = disbursementTransaction.getAmount(transactionCtx.getCurrency()).minus(downPaymentAmount);
|
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
||||||
|
|
||||||
if (amortizableAmount.isGreaterThanZero()) {
|
if (amortizableAmount.isGreaterThanZero()) {
|
||||||
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, MoneyHelper.getMathContext());
|
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, MoneyHelper.getMathContext());
|
||||||
MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount);
|
MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount);
|
||||||
|
|
||||||
candidateRepaymentInstallments.forEach(i -> {
|
candidateRepaymentInstallments.forEach(i -> {
|
||||||
Money previousPrincipal = i.getPrincipal(transactionCtx.getCurrency());
|
Money previousPrincipal = i.getPrincipal(currency);
|
||||||
Money newPrincipal = previousPrincipal.add(increasePrincipalBy);
|
Money newPrincipal = previousPrincipal.add(increasePrincipalBy);
|
||||||
if (installmentAmountInMultiplesOf != null) {
|
if (installmentAmountInMultiplesOf != null) {
|
||||||
newPrincipal = Money.roundToMultiplesOf(newPrincipal, installmentAmountInMultiplesOf);
|
newPrincipal = Money.roundToMultiplesOf(newPrincipal, installmentAmountInMultiplesOf);
|
||||||
}
|
}
|
||||||
i.updatePrincipal(newPrincipal.getAmount());
|
i.updatePrincipal(newPrincipal.getAmount());
|
||||||
moneyHolder.setMoneyObject(moneyHolder.getMoneyObject().minus(newPrincipal).plus(previousPrincipal));
|
moneyHolder.setMoneyObject(moneyHolder.getMoneyObject().minus(newPrincipal).plus(previousPrincipal));
|
||||||
i.updateObligationsMet(transactionCtx.getCurrency(), disbursementTransaction.getTransactionDate());
|
i.updateObligationsMet(currency, disbursementTransaction.getTransactionDate());
|
||||||
});
|
});
|
||||||
// Hence the rounding, we might need to amend the last installment amount
|
// Hence the rounding, we might need to amend the last installment amount
|
||||||
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
|
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
|
||||||
@@ -930,26 +949,30 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanRepaymentScheduleInstallment> findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(LocalDate currentDate,
|
protected void handleWriteOff(final LoanTransaction transaction, TransactionCtx ctx) {
|
||||||
|
super.handleWriteOff(transaction, ctx.getCurrency(), ctx.getInstallments());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LoanRepaymentScheduleInstallment> findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(LocalDate targetDate,
|
||||||
ProgressiveTransactionCtx transactionCtx) {
|
ProgressiveTransactionCtx transactionCtx) {
|
||||||
return transactionCtx.getInstallments().stream() //
|
return transactionCtx.getInstallments().stream() //
|
||||||
.filter(installment -> !installment.isDownPayment() && !installment.isAdditional())
|
.filter(installment -> !installment.isDownPayment() && !installment.isAdditional())
|
||||||
.filter(installment -> installment.isOverdueOn(currentDate))
|
.filter(installment -> installment.isOverdueOn(targetDate))
|
||||||
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recalculateInterestForDate(LocalDate currentDate, ProgressiveTransactionCtx ctx) {
|
private void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
|
||||||
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()
|
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()
|
||||||
&& ctx.getInstallments().get(0).getLoan().isInterestRecalculationEnabledForProduct()
|
&& ctx.getInstallments().get(0).getLoan().getLoanProductRelatedDetail().isInterestRecalculationEnabled()
|
||||||
&& !ctx.getInstallments().get(0).getLoan().isNpa() && !ctx.getInstallments().get(0).getLoan().isChargedOff()) {
|
&& !ctx.getInstallments().get(0).getLoan().isNpa() && !ctx.getInstallments().get(0).getLoan().isChargedOff()) {
|
||||||
List<LoanRepaymentScheduleInstallment> overdueInstallmentsSortedByInstallmentNumber = findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(
|
List<LoanRepaymentScheduleInstallment> overdueInstallmentsSortedByInstallmentNumber = findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(
|
||||||
currentDate, ctx);
|
targetDate, ctx);
|
||||||
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
|
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
|
||||||
List<LoanRepaymentScheduleInstallment> normalInstallments = ctx.getInstallments().stream() //
|
List<LoanRepaymentScheduleInstallment> normalInstallments = ctx.getInstallments().stream() //
|
||||||
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment()).toList();
|
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment()).toList();
|
||||||
|
|
||||||
Optional<LoanRepaymentScheduleInstallment> currentInstallmentOptional = normalInstallments.stream().filter(
|
Optional<LoanRepaymentScheduleInstallment> currentInstallmentOptional = normalInstallments.stream().filter(
|
||||||
installment -> installment.getFromDate().isBefore(currentDate) && !installment.getDueDate().isBefore(currentDate))
|
installment -> installment.getFromDate().isBefore(targetDate) && !installment.getDueDate().isBefore(targetDate))
|
||||||
.findAny();
|
.findAny();
|
||||||
|
|
||||||
// get DUE installment or last installment
|
// get DUE installment or last installment
|
||||||
@@ -962,7 +985,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
for (LoanRepaymentScheduleInstallment processingInstallment : overdueInstallmentsSortedByInstallmentNumber) {
|
for (LoanRepaymentScheduleInstallment processingInstallment : overdueInstallmentsSortedByInstallmentNumber) {
|
||||||
// add and subtract outstanding principal
|
// add and subtract outstanding principal
|
||||||
if (!overDuePrincipal.isZero()) {
|
if (!overDuePrincipal.isZero()) {
|
||||||
adjustOverduePrincipalForInstallment(currentDate, processingInstallment, overDuePrincipal,
|
adjustOverduePrincipalForInstallment(targetDate, processingInstallment, overDuePrincipal,
|
||||||
aggregatedOverDuePrincipal, ctx);
|
aggregatedOverDuePrincipal, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,11 +993,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
aggregatedOverDuePrincipal = aggregatedOverDuePrincipal.add(overDuePrincipal);
|
aggregatedOverDuePrincipal = aggregatedOverDuePrincipal.add(overDuePrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean adjustNeeded = !currentInstallment.equals(lastInstallment) || !lastInstallment.isOverdueOn(currentDate);
|
boolean adjustNeeded = !currentInstallment.equals(lastInstallment) || !lastInstallment.isOverdueOn(targetDate);
|
||||||
if (adjustNeeded) {
|
if (adjustNeeded) {
|
||||||
adjustOverduePrincipalForInstallment(currentDate, currentInstallment, overDuePrincipal, aggregatedOverDuePrincipal,
|
adjustOverduePrincipalForInstallment(targetDate, currentInstallment, overDuePrincipal, aggregatedOverDuePrincipal, ctx);
|
||||||
ctx);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1078,15 +1099,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
|
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
|
||||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Set<LoanCharge> chargesOfInstallment,
|
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Set<LoanCharge> chargesOfInstallment,
|
||||||
Balances balances, LoanRepaymentScheduleInstallment.PaymentAction action) {
|
Balances balances, LoanRepaymentScheduleInstallment.PaymentAction action) {
|
||||||
|
AllocationType allocationType = paymentAllocationType.getAllocationType();
|
||||||
|
MonetaryCurrency currency = loanTransaction.getLoan().getCurrency();
|
||||||
|
Money zero = Money.zero(currency);
|
||||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||||
Money zero = transactionAmountUnprocessed.zero();
|
LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment.getPaymentFunction(allocationType, action);
|
||||||
|
|
||||||
LoanRepaymentScheduleInstallment.PaymentFunction paymentFunction = currentInstallment
|
|
||||||
.getPaymentFunction(paymentAllocationType.getAllocationType(), action);
|
|
||||||
ChargesPaidByFunction chargesPaidByFunction = getChargesPaymentFunction(action);
|
ChargesPaidByFunction chargesPaidByFunction = getChargesPaymentFunction(action);
|
||||||
Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
|
Money portion = paymentFunction.accept(transactionDate, transactionAmountUnprocessed);
|
||||||
|
|
||||||
switch (paymentAllocationType.getAllocationType()) {
|
switch (allocationType) {
|
||||||
case PENALTY -> {
|
case PENALTY -> {
|
||||||
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(portion));
|
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(portion));
|
||||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, portion);
|
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, portion);
|
||||||
@@ -1109,7 +1130,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentInstallment.checkIfRepaymentPeriodObligationsAreMet(transactionDate, loanTransaction.getLoan().loanCurrency());
|
currentInstallment.checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency);
|
||||||
return portion;
|
return portion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,13 +1151,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, aggregatedInterest, aggregatedFee, aggregatedPenalty);
|
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, aggregatedInterest, aggregatedFee, aggregatedPenalty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction, MoneyHolder overpaymentHolder) {
|
private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||||
if (overpaymentPortion.isGreaterThanZero()) {
|
MoneyHolder overpaymentHolder = transactionCtx.getOverpaymentHolder();
|
||||||
|
if (MathUtil.isGreaterThanZero(overpaymentPortion)) {
|
||||||
onLoanOverpayment(loanTransaction, overpaymentPortion);
|
onLoanOverpayment(loanTransaction, overpaymentPortion);
|
||||||
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().add(overpaymentPortion));
|
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().add(overpaymentPortion));
|
||||||
loanTransaction.setOverPayments(overpaymentPortion);
|
loanTransaction.setOverPayments(overpaymentPortion);
|
||||||
} else {
|
} else {
|
||||||
overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
|
overpaymentHolder.setMoneyObject(Money.zero(transactionCtx.getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,11 +1232,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money refundTransactionHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money refundTransactionHorizontally(LoanTransaction loanTransaction, TransactionCtx ctx, Money transactionAmountUnprocessed,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
|
||||||
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
|
||||||
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
Money zero = Money.zero(currency);
|
Money zero = Money.zero(currency);
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||||
|
Set<LoanCharge> charges = ctx.getCharges();
|
||||||
Money refundedPortion;
|
Money refundedPortion;
|
||||||
outerLoop: do {
|
outerLoop: do {
|
||||||
LoanRepaymentScheduleInstallment latestPastDueInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency,
|
LoanRepaymentScheduleInstallment latestPastDueInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency,
|
||||||
@@ -1284,13 +1308,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return transactionAmountUnprocessed;
|
return transactionAmountUnprocessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money refundTransactionVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money refundTransactionVertically(LoanTransaction loanTransaction, TransactionCtx ctx,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money zero,
|
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
|
||||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Set<LoanCharge> charges, Balances balances,
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Balances balances,
|
||||||
PaymentAllocationType paymentAllocationType) {
|
PaymentAllocationType paymentAllocationType) {
|
||||||
LoanRepaymentScheduleInstallment currentInstallment = null;
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
|
Money zero = Money.zero(currency);
|
||||||
Money refundedPortion = zero;
|
Money refundedPortion = zero;
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||||
|
Set<LoanCharge> charges = ctx.getCharges();
|
||||||
|
LoanRepaymentScheduleInstallment currentInstallment = null;
|
||||||
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
do {
|
do {
|
||||||
switch (paymentAllocationType.getDueType()) {
|
switch (paymentAllocationType.getDueType()) {
|
||||||
@@ -1401,7 +1428,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||||
|
|
||||||
handleOverpayment(transactionAmountUnprocessed, loanTransaction, transactionCtx.getOverpaymentHolder());
|
handleOverpayment(transactionAmountUnprocessed, loanTransaction, transactionCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money processPeriods(LoanTransaction transaction, Money processAmount, Set<LoanCharge> charges,
|
private Money processPeriods(LoanTransaction transaction, Money processAmount, Set<LoanCharge> charges,
|
||||||
@@ -1413,7 +1440,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private Money processPeriods(LoanTransaction transaction, Money processAmount, LoanPaymentAllocationRule allocationRule,
|
private Money processPeriods(LoanTransaction transaction, Money processAmount, LoanPaymentAllocationRule allocationRule,
|
||||||
Set<LoanCharge> charges, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances,
|
Set<LoanCharge> charges, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances,
|
||||||
TransactionCtx transactionCtx) {
|
TransactionCtx transactionCtx) {
|
||||||
MonetaryCurrency currency = transactionCtx.getCurrency();
|
|
||||||
LoanScheduleProcessingType scheduleProcessingType = transaction.getLoan().getLoanProductRelatedDetail()
|
LoanScheduleProcessingType scheduleProcessingType = transaction.getLoan().getLoanProductRelatedDetail()
|
||||||
.getLoanScheduleProcessingType();
|
.getLoanScheduleProcessingType();
|
||||||
if (scheduleProcessingType.isHorizontal()) {
|
if (scheduleProcessingType.isHorizontal()) {
|
||||||
@@ -1421,8 +1447,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
balances);
|
balances);
|
||||||
}
|
}
|
||||||
if (scheduleProcessingType.isVertical()) {
|
if (scheduleProcessingType.isVertical()) {
|
||||||
return processPeriodsVertically(transaction, currency, transactionCtx.getInstallments(), processAmount, allocationRule,
|
return processPeriodsVertically(transaction, transactionCtx, processAmount, allocationRule, transactionMappings, charges,
|
||||||
transactionMappings, charges, balances);
|
balances);
|
||||||
}
|
}
|
||||||
return processAmount;
|
return processAmount;
|
||||||
}
|
}
|
||||||
@@ -1446,7 +1472,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
Money transactionAmountUnprocessed, List<PaymentAllocationType> paymentAllocationTypes,
|
Money transactionAmountUnprocessed, List<PaymentAllocationType> paymentAllocationTypes,
|
||||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges, Balances balances) {
|
||||||
if (transactionAmountUnprocessed.isZero()) {
|
if (MathUtil.isEmpty(transactionAmountUnprocessed)) {
|
||||||
return transactionAmountUnprocessed;
|
return transactionAmountUnprocessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1589,7 +1615,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
if (DueType.IN_ADVANCE.equals(paymentAllocationType.getDueType())) {
|
if (DueType.IN_ADVANCE.equals(paymentAllocationType.getDueType())) {
|
||||||
payDate = calculateNewPayDateInCaseOfInAdvancePayment(loanTransaction, installment);
|
payDate = calculateNewPayDateInCaseOfInAdvancePayment(loanTransaction, installment);
|
||||||
updateRepaymentPeriodBalances(paymentAllocationType, installment, model, payDate);
|
updateRepaymentPeriodBalances(paymentAllocationType, installment, ctx, payDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
paidPortion = processPaymentAllocation(paymentAllocationType, installment, loanTransaction, transactionAmountUnprocessed,
|
paidPortion = processPaymentAllocation(paymentAllocationType, installment, loanTransaction, transactionAmountUnprocessed,
|
||||||
@@ -1597,17 +1623,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
if (PRINCIPAL.equals(paymentAllocationType.getAllocationType())) {
|
if (PRINCIPAL.equals(paymentAllocationType.getAllocationType())) {
|
||||||
emiCalculator.payPrincipal(model, installment.getDueDate(), payDate, paidPortion);
|
emiCalculator.payPrincipal(model, installment.getDueDate(), payDate, paidPortion);
|
||||||
updateRepaymentPeriods(loanTransaction, ctx, model);
|
updateRepaymentPeriods(loanTransaction, ctx);
|
||||||
} else if (INTEREST.equals(paymentAllocationType.getAllocationType())) {
|
} else if (INTEREST.equals(paymentAllocationType.getAllocationType())) {
|
||||||
emiCalculator.payInterest(model, installment.getDueDate(), payDate, paidPortion);
|
emiCalculator.payInterest(model, installment.getDueDate(), payDate, paidPortion);
|
||||||
updateRepaymentPeriods(loanTransaction, ctx, model);
|
updateRepaymentPeriods(loanTransaction, ctx);
|
||||||
}
|
}
|
||||||
return paidPortion;
|
return paidPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRepaymentPeriods(LoanTransaction loanTransaction, ProgressiveTransactionCtx ctx,
|
private void updateRepaymentPeriods(LoanTransaction loanTransaction, ProgressiveTransactionCtx ctx) {
|
||||||
ProgressiveLoanInterestScheduleModel model) {
|
ctx.getModel().repaymentPeriods().forEach(rm -> {
|
||||||
model.repaymentPeriods().forEach(rm -> {
|
|
||||||
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
|
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
|
||||||
.filter(ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment()).findFirst().orElse(null);
|
.filter(ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment()).findFirst().orElse(null);
|
||||||
if (installment != null) {
|
if (installment != null) {
|
||||||
@@ -1619,8 +1644,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateRepaymentPeriodBalances(PaymentAllocationType paymentAllocationType,
|
private void updateRepaymentPeriodBalances(PaymentAllocationType paymentAllocationType,
|
||||||
LoanRepaymentScheduleInstallment inAdvanceInstallment, ProgressiveLoanInterestScheduleModel model, LocalDate payDate) {
|
LoanRepaymentScheduleInstallment inAdvanceInstallment, ProgressiveTransactionCtx ctx, LocalDate payDate) {
|
||||||
PeriodDueDetails payableDetails = emiCalculator.getDueAmounts(model, inAdvanceInstallment.getDueDate(), payDate);
|
PeriodDueDetails payableDetails = emiCalculator.getDueAmounts(ctx.getModel(), inAdvanceInstallment.getDueDate(), payDate);
|
||||||
|
|
||||||
switch (paymentAllocationType) {
|
switch (paymentAllocationType) {
|
||||||
case IN_ADVANCE_INTEREST -> inAdvanceInstallment.updateInterestCharged(payableDetails.getDueInterest().getAmount());
|
case IN_ADVANCE_INTEREST -> inAdvanceInstallment.updateInterestCharged(payableDetails.getDueInterest().getAmount());
|
||||||
@@ -1637,6 +1662,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
LocalDate payDate = switch (strategy) {
|
LocalDate payDate = switch (strategy) {
|
||||||
case TILL_PRE_CLOSURE_DATE -> loanTransaction.getTransactionDate();
|
case TILL_PRE_CLOSURE_DATE -> loanTransaction.getTransactionDate();
|
||||||
|
// TODO use isInPeriod
|
||||||
case TILL_REST_FREQUENCY_DATE -> loanTransaction.getTransactionDate().isAfter(inAdvanceInstallment.getFromDate()) //
|
case TILL_REST_FREQUENCY_DATE -> loanTransaction.getTransactionDate().isAfter(inAdvanceInstallment.getFromDate()) //
|
||||||
&& !loanTransaction.getTransactionDate().isAfter(inAdvanceInstallment.getDueDate()) //
|
&& !loanTransaction.getTransactionDate().isAfter(inAdvanceInstallment.getDueDate()) //
|
||||||
? inAdvanceInstallment.getDueDate() //
|
? inAdvanceInstallment.getDueDate() //
|
||||||
@@ -1655,10 +1681,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money processPeriodsVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
private Money processPeriodsVertically(LoanTransaction loanTransaction, TransactionCtx ctx, Money transactionAmountUnprocessed,
|
||||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
|
||||||
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
LoanPaymentAllocationRule paymentAllocationRule, List<LoanTransactionToRepaymentScheduleMapping> transactionMappings,
|
||||||
Set<LoanCharge> charges, Balances balances) {
|
Set<LoanCharge> charges, Balances balances) {
|
||||||
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||||
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
|
for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
|
||||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
|
FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
|
||||||
@@ -1823,6 +1850,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
reprocessInstallmentsOrder(installments);
|
reprocessInstallmentsOrder(installments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void calculateAccrualActivity(LoanTransaction transaction, TransactionCtx ctx) {
|
||||||
|
super.calculateAccrualActivity(transaction, ctx.getCurrency(), ctx.getInstallments());
|
||||||
|
}
|
||||||
|
|
||||||
private void reprocessInstallmentsOrder(List<LoanRepaymentScheduleInstallment> installments) {
|
private void reprocessInstallmentsOrder(List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
AtomicInteger counter = new AtomicInteger(1);
|
AtomicInteger counter = new AtomicInteger(1);
|
||||||
installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate)
|
installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate)
|
||||||
|
|||||||
+10
-8
@@ -84,10 +84,13 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Money getCalculatedDueInterest() {
|
public Money getCalculatedDueInterest() {
|
||||||
final BigDecimal interestDueTillRepaymentDueDate = getOutstandingLoanBalance()//
|
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
||||||
.multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() //
|
final BigDecimal interestDueTillRepaymentDueDate = lengthTillPeriodDueDate == 0 //
|
||||||
.divide(BigDecimal.valueOf(getLengthTillPeriodDueDate()), mc) //
|
? BigDecimal.ZERO //
|
||||||
.multiply(BigDecimal.valueOf(getLength()), mc); //
|
: getOutstandingLoanBalance() //
|
||||||
|
.multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() //
|
||||||
|
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
||||||
|
.multiply(BigDecimal.valueOf(getLength()), mc); //
|
||||||
return Money.of(outstandingLoanBalance.getCurrency(), interestDueTillRepaymentDueDate, mc);
|
return Money.of(outstandingLoanBalance.getCurrency(), interestDueTillRepaymentDueDate, mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +106,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
if (isFirstInterestPeriod()) {
|
if (isFirstInterestPeriod()) {
|
||||||
Optional<RepaymentPeriod> previousRepaymentPeriod = getRepaymentPeriod().getPrevious();
|
Optional<RepaymentPeriod> previousRepaymentPeriod = getRepaymentPeriod().getPrevious();
|
||||||
if (previousRepaymentPeriod.isPresent()) {
|
if (previousRepaymentPeriod.isPresent()) {
|
||||||
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getInterestPeriods()
|
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getLastInterestPeriod();
|
||||||
.get(previousRepaymentPeriod.get().getInterestPeriods().size() - 1);
|
|
||||||
this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()//
|
this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()//
|
||||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)//
|
.plus(previousInterestPeriod.getDisbursementAmount(), mc)//
|
||||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
|
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
|
||||||
@@ -120,7 +122,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFirstInterestPeriod() {
|
public boolean isFirstInterestPeriod() {
|
||||||
return getRepaymentPeriod().getInterestPeriods().get(0).equals(this);
|
return this.equals(getRepaymentPeriod().getFirstInterestPeriod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-4
@@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -83,7 +86,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
|
|
||||||
private BigDecimal findInterestRate(final LocalDate effectiveDate) {
|
private BigDecimal findInterestRate(final LocalDate effectiveDate) {
|
||||||
return interestRates.stream() //
|
return interestRates.stream() //
|
||||||
.filter(ir -> !ir.effectiveFrom().isAfter(effectiveDate)) //
|
.filter(ir -> !DateUtils.isAfter(ir.effectiveFrom(), effectiveDate)) //
|
||||||
.map(InterestRate::interestRate) //
|
.map(InterestRate::interestRate) //
|
||||||
.findFirst() //
|
.findFirst() //
|
||||||
.orElse(loanProductRelatedDetail.getAnnualNominalInterestRate()); //
|
.orElse(loanProductRelatedDetail.getAnnualNominalInterestRate()); //
|
||||||
@@ -93,12 +96,12 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
interestRates.add(new InterestRate(newInterestEffectiveDate, newInterestRate));
|
interestRates.add(new InterestRate(newInterestEffectiveDate, newInterestRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<RepaymentPeriod> findRepaymentPeriod(final LocalDate repaymentPeriodDueDate) {
|
public Optional<RepaymentPeriod> findRepaymentPeriodByDueDate(final LocalDate repaymentPeriodDueDate) {
|
||||||
if (repaymentPeriodDueDate == null) {
|
if (repaymentPeriodDueDate == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return repaymentPeriods.stream()//
|
return repaymentPeriods.stream()//
|
||||||
.filter(repaymentPeriodItem -> repaymentPeriodItem.getDueDate().isEqual(repaymentPeriodDueDate))//
|
.filter(repaymentPeriodItem -> DateUtils.isEqual(repaymentPeriodItem.getDueDate(), repaymentPeriodDueDate))//
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
return repaymentPeriods;
|
return repaymentPeriods;
|
||||||
}
|
}
|
||||||
return repaymentPeriods.stream()//
|
return repaymentPeriods.stream()//
|
||||||
.filter(period -> !period.getDueDate().isBefore(calculateFromRepaymentPeriodDueDate))//
|
.filter(period -> !DateUtils.isBefore(period.getDueDate(), calculateFromRepaymentPeriodDueDate))//
|
||||||
.toList();//
|
.toList();//
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +123,10 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
return DateUtils.getExactDifferenceInDays(firstPeriod.getFromDate(), lastPeriod.getDueDate());
|
return DateUtils.getExactDifferenceInDays(firstPeriod.getFromDate(), lastPeriod.getDueDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDate getStartDate() {
|
||||||
|
return !repaymentPeriods.isEmpty() ? repaymentPeriods.get(0).getFromDate() : null;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDate getMaturityDate() {
|
public LocalDate getMaturityDate() {
|
||||||
return !repaymentPeriods.isEmpty() ? repaymentPeriods.get(repaymentPeriods.size() - 1).getDueDate() : null;
|
return !repaymentPeriods.isEmpty() ? repaymentPeriods.get(repaymentPeriods.size() - 1).getDueDate() : null;
|
||||||
}
|
}
|
||||||
@@ -135,6 +142,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
if (balanceChangeDate == null) {
|
if (balanceChangeDate == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
// TODO use isInPeriod
|
||||||
return repaymentPeriods.stream()//
|
return repaymentPeriods.stream()//
|
||||||
.filter(repaymentPeriod -> {
|
.filter(repaymentPeriod -> {
|
||||||
final boolean isFirstPeriod = repaymentPeriod.getPrevious().isEmpty();
|
final boolean isFirstPeriod = repaymentPeriod.getPrevious().isEmpty();
|
||||||
@@ -214,4 +222,10 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
public Money getTotalPaidPrincipal() {
|
public Money getTotalPaidPrincipal() {
|
||||||
return repaymentPeriods().stream().map(RepaymentPeriod::getPaidPrincipal).reduce(getZero(), Money::plus);
|
return repaymentPeriods().stream().map(RepaymentPeriod::getPaidPrincipal).reduce(getZero(), Money::plus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
|
||||||
|
return repaymentPeriods.stream() //
|
||||||
|
.filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-17
@@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -106,6 +109,7 @@ public class RepaymentPeriod {
|
|||||||
return interestPeriods.stream().map(InterestPeriod::getRateFactor).reduce(BigDecimal.ONE, BigDecimal::add);
|
return interestPeriods.stream().map(InterestPeriod::getRateFactor).reduce(BigDecimal.ONE, BigDecimal::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public Money getCalculatedDueInterest() {
|
public Money getCalculatedDueInterest() {
|
||||||
if (calculatedDueInterestCalculation == null) {
|
if (calculatedDueInterestCalculation == null) {
|
||||||
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest,
|
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest,
|
||||||
@@ -123,23 +127,6 @@ public class RepaymentPeriod {
|
|||||||
return calculatedDueInterest;
|
return calculatedDueInterest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money getZero(MathContext mc) {
|
|
||||||
// EMI is always initiated
|
|
||||||
return this.emi.zero(mc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Money getCalculatedDuePrincipal() {
|
|
||||||
return getEmi().minus(getCalculatedDueInterest(), mc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Money getTotalPaidAmount() {
|
|
||||||
return getPaidPrincipal().plus(getPaidInterest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFullyPaid() {
|
|
||||||
return getEmi().isEqualTo(getTotalPaidAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Money getDueInterest() {
|
public Money getDueInterest() {
|
||||||
if (dueInterestCalculation == null) {
|
if (dueInterestCalculation == null) {
|
||||||
// Due interest might be the maximum paid if there is pay-off or early repayment
|
// Due interest might be the maximum paid if there is pay-off or early repayment
|
||||||
@@ -150,11 +137,23 @@ public class RepaymentPeriod {
|
|||||||
return dueInterestCalculation.get();
|
return dueInterestCalculation.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getCalculatedDuePrincipal() {
|
||||||
|
return getEmi().minus(getCalculatedDueInterest(), mc);
|
||||||
|
}
|
||||||
|
|
||||||
public Money getDuePrincipal() {
|
public Money getDuePrincipal() {
|
||||||
// Due principal might be the maximum paid if there is pay-off or early repayment
|
// Due principal might be the maximum paid if there is pay-off or early repayment
|
||||||
return MathUtil.max(getEmi().minus(getDueInterest(), mc), getPaidPrincipal(), false);
|
return MathUtil.max(getEmi().minus(getDueInterest(), mc), getPaidPrincipal(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getTotalPaidAmount() {
|
||||||
|
return getPaidPrincipal().plus(getPaidInterest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullyPaid() {
|
||||||
|
return getEmi().isEqualTo(getTotalPaidAmount());
|
||||||
|
}
|
||||||
|
|
||||||
public Money getUnrecognizedInterest() {
|
public Money getUnrecognizedInterest() {
|
||||||
return getCalculatedDueInterest().minus(getDueInterest(), mc);
|
return getCalculatedDueInterest().minus(getDueInterest(), mc);
|
||||||
}
|
}
|
||||||
@@ -193,4 +192,29 @@ public class RepaymentPeriod {
|
|||||||
(m1, m2) -> m1.plus(m2, mc));
|
(m1, m2) -> m1.plus(m2, mc));
|
||||||
return initialBalance.add(totalDisbursedAmount, mc);
|
return initialBalance.add(totalDisbursedAmount, mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Money getZero(MathContext mc) {
|
||||||
|
// EMI is always initiated
|
||||||
|
return this.emi.zero(mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InterestPeriod getFirstInterestPeriod() {
|
||||||
|
return getInterestPeriods().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InterestPeriod getLastInterestPeriod() {
|
||||||
|
List<InterestPeriod> interestPeriods = getInterestPeriods();
|
||||||
|
return interestPeriods.get(interestPeriods.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<InterestPeriod> findInterestPeriod(@NotNull LocalDate transactionDate) {
|
||||||
|
return interestPeriods.stream() //
|
||||||
|
.filter(interestPeriod -> isInPeriod(transactionDate, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
|
||||||
|
isFirstRepaymentPeriod() && interestPeriod.isFirstInterestPeriod()))//
|
||||||
|
.reduce((one, two) -> two);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirstRepaymentPeriod() {
|
||||||
|
return previous == null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-46
@@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.findInPeriod;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -39,7 +42,6 @@ import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||||
@@ -89,7 +91,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
// generate list of proposed schedule due dates
|
// generate list of proposed schedule due dates
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
|
||||||
periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
||||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
|
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(),
|
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(),
|
||||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
|
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
|
||||||
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
|
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
|
||||||
@@ -147,6 +149,61 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||||
|
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||||
|
LocalDate rescheduleFrom) {
|
||||||
|
LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
|
||||||
|
return LoanScheduleDTO.from(null, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||||
|
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||||
|
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||||
|
if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
|
||||||
|
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||||
|
LoanRepaymentScheduleInstallment actualInstallment = findInPeriod(onDate, installments).orElse(installments.get(0));
|
||||||
|
|
||||||
|
LocalDate transactionDate = switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) {
|
||||||
|
case TILL_PRE_CLOSURE_DATE -> onDate;
|
||||||
|
case TILL_REST_FREQUENCY_DATE -> actualInstallment.getDueDate(); // due date of current installment
|
||||||
|
case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgressiveLoanInterestScheduleModel model = processor.calculateInterestScheduleModel(loan.getId(), onDate);
|
||||||
|
OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate);
|
||||||
|
// TODO: We should add all the past due outstanding amounts as well
|
||||||
|
OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) //
|
||||||
|
.principal(outstandingAmounts.getOutstandingPrincipal()) //
|
||||||
|
.interest(outstandingAmounts.getOutstandingInterest());//
|
||||||
|
|
||||||
|
installments.forEach(installment -> result //
|
||||||
|
.plusFeeCharges(installment.getFeeChargesOutstanding(currency))
|
||||||
|
.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency)));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate targetDate) {
|
||||||
|
Loan loan = installment.getLoan();
|
||||||
|
LoanRepaymentScheduleTransactionProcessor transactionProcessor = loan.getTransactionProcessor();
|
||||||
|
if (!(transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
|
||||||
|
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
|
||||||
|
}
|
||||||
|
if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) {
|
||||||
|
return Money.zero(loan.getCurrency());
|
||||||
|
}
|
||||||
|
ProgressiveLoanInterestScheduleModel model = processor.calculateInterestScheduleModel(loan.getId(), targetDate);
|
||||||
|
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private, internal methods
|
||||||
|
|
||||||
private List<DisbursementData> getSortedDisbursementList(LoanApplicationTerms loanApplicationTerms) {
|
private List<DisbursementData> getSortedDisbursementList(LoanApplicationTerms loanApplicationTerms) {
|
||||||
final List<DisbursementData> disbursementDataList = new ArrayList<>(loanApplicationTerms.getDisbursementDatas());
|
final List<DisbursementData> disbursementDataList = new ArrayList<>(loanApplicationTerms.getDisbursementDatas());
|
||||||
disbursementDataList.sort(Comparator.comparing(DisbursementData::disbursementDate));
|
disbursementDataList.sort(Comparator.comparing(DisbursementData::disbursementDate));
|
||||||
@@ -238,49 +295,6 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
|
|
||||||
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
|
||||||
LocalDate rescheduleFrom) {
|
|
||||||
LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
|
|
||||||
return LoanScheduleDTO.from(null, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
|
||||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
|
||||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
|
||||||
if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
|
|
||||||
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
|
||||||
LoanRepaymentScheduleInstallment actualInstallment = LoanRepaymentScheduleProcessingWrapper.findInPeriod(onDate, installments)
|
|
||||||
.orElse(installments.get(0));
|
|
||||||
|
|
||||||
LocalDate transactionDate = switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) {
|
|
||||||
case TILL_PRE_CLOSURE_DATE -> onDate;
|
|
||||||
case TILL_REST_FREQUENCY_DATE -> actualInstallment.getDueDate(); // due date of current installment
|
|
||||||
case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
|
|
||||||
};
|
|
||||||
|
|
||||||
ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), onDate,
|
|
||||||
loan.retrieveListOfTransactionsForReprocessing(), currency, installments, loan.getActiveCharges()).getRight();
|
|
||||||
|
|
||||||
OutstandingDetails result = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate);
|
|
||||||
// TODO: We should add all the past due outstanding amounts as well
|
|
||||||
OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) //
|
|
||||||
.principal(result.getOutstandingPrincipal()) //
|
|
||||||
.interest(result.getOutstandingInterest());//
|
|
||||||
|
|
||||||
installments.forEach(installment -> amounts //
|
|
||||||
.plusFeeCharges(installment.getFeeChargesOutstanding(currency))
|
|
||||||
.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency)));
|
|
||||||
|
|
||||||
return amounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private, internal methods
|
|
||||||
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
||||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||||
if (loanCharges != null) {
|
if (loanCharges != null) {
|
||||||
@@ -430,5 +444,4 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
}
|
}
|
||||||
return interestCharges;
|
return interestCharges;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -34,11 +35,14 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
|
|||||||
|
|
||||||
public interface EMICalculator {
|
public interface EMICalculator {
|
||||||
|
|
||||||
ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List<LoanScheduleModelRepaymentPeriod> periods,
|
@NotNull
|
||||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc);
|
ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
|
||||||
|
@NotNull LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc);
|
||||||
|
|
||||||
ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
|
@NotNull
|
||||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc);
|
ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
|
||||||
|
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail,
|
||||||
|
Integer installmentAmountInMultiplesOf, MathContext mc);
|
||||||
|
|
||||||
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
|
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
|
||||||
|
|
||||||
@@ -56,7 +60,13 @@ public interface EMICalculator {
|
|||||||
void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
|
void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
|
||||||
Money principalAmount);
|
Money principalAmount);
|
||||||
|
|
||||||
PeriodDueDetails getDueAmounts(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate, LocalDate targetDate);
|
@NotNull
|
||||||
|
PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
|
@NotNull LocalDate targetDate);
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
|
@NotNull LocalDate targetDate);
|
||||||
|
|
||||||
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate,
|
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate,
|
||||||
LocalDate targetDate);
|
LocalDate targetDate);
|
||||||
|
|||||||
+71
-53
@@ -18,14 +18,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Year;
|
import java.time.Year;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
@@ -42,7 +44,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLo
|
|||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -53,20 +54,36 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
private static final BigDecimal ONE_WEEK_IN_DAYS = BigDecimal.valueOf(7);
|
private static final BigDecimal ONE_WEEK_IN_DAYS = BigDecimal.valueOf(7);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List<LoanScheduleModelRepaymentPeriod> periods,
|
@NotNull
|
||||||
final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
|
||||||
final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
@NotNull LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf,
|
||||||
final ArrayList<RepaymentPeriod> interestRepaymentModelList = new ArrayList<>(periods.size());
|
final MathContext mc) {
|
||||||
RepaymentPeriod previousPeriod = null;
|
return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate,
|
||||||
for (final LoanScheduleModelRepaymentPeriod period : periods) {
|
LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), zeroAmount,
|
}
|
||||||
mc);
|
|
||||||
previousPeriod = currentPeriod;
|
|
||||||
interestRepaymentModelList.add(currentPeriod);
|
|
||||||
|
|
||||||
}
|
@Override
|
||||||
return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail,
|
@NotNull
|
||||||
installmentAmountInMultiplesOf, mc);
|
public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
|
||||||
|
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail,
|
||||||
|
final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
||||||
|
installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList();
|
||||||
|
return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate,
|
||||||
|
LoanRepaymentScheduleInstallment::getDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from,
|
||||||
|
Function<T, LocalDate> to, @NotNull LoanProductRelatedDetail loanProductRelatedDetail,
|
||||||
|
final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
||||||
|
final Money zero = Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
||||||
|
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
|
||||||
|
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
|
||||||
|
RepaymentPeriod rp = new RepaymentPeriod(prev.get(), from.apply(e), to.apply(e), zero, mc);
|
||||||
|
prev.set(rp);
|
||||||
|
return rp;
|
||||||
|
}).toList();
|
||||||
|
return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,7 +92,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
if (scheduleModel == null) {
|
if (scheduleModel == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return scheduleModel.findRepaymentPeriod(repaymentPeriodDueDate);
|
return scheduleModel.findRepaymentPeriodByDueDate(repaymentPeriodDueDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,13 +156,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
@Override
|
@Override
|
||||||
public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate,
|
public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate,
|
||||||
LocalDate transactionDate, Money principalAmount) {
|
LocalDate transactionDate, Money principalAmount) {
|
||||||
|
if (MathUtil.isEmpty(principalAmount)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
|
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
|
||||||
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
|
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
|
||||||
LocalDate balanceCorrectionDate = transactionDate;
|
// If it is paid late, we need to calculate with the period due date
|
||||||
if (repaymentPeriodDueDate.isBefore(transactionDate)) {
|
LocalDate balanceCorrectionDate = DateUtils.isBefore(repaymentPeriodDueDate, transactionDate) ? repaymentPeriodDueDate
|
||||||
// If it is paid late, we need to calculate with the period due date
|
: transactionDate;
|
||||||
balanceCorrectionDate = repaymentPeriodDueDate;
|
|
||||||
}
|
|
||||||
addBalanceCorrection(scheduleModel, balanceCorrectionDate, principalAmount.negated());
|
addBalanceCorrection(scheduleModel, balanceCorrectionDate, principalAmount.negated());
|
||||||
|
|
||||||
repaymentPeriod.ifPresent(rp -> {
|
repaymentPeriod.ifPresent(rp -> {
|
||||||
@@ -159,11 +177,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PeriodDueDetails getDueAmounts(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate repaymentPeriodDueDate,
|
@NotNull
|
||||||
final LocalDate targetDate) {
|
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
|
@NotNull LocalDate targetDate) {
|
||||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
repaymentPeriodDueDate, targetDate);
|
periodDueDate, targetDate);
|
||||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriod(repaymentPeriodDueDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
boolean multiplePeriodIsUnpaid = recalculatedScheduleModelTillDate.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
boolean multiplePeriodIsUnpaid = recalculatedScheduleModelTillDate.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||||
.count() > 1L;
|
.count() > 1L;
|
||||||
if (multiplePeriodIsUnpaid && !targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
if (multiplePeriodIsUnpaid && !targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
||||||
@@ -176,11 +195,21 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate,
|
@NotNull
|
||||||
|
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||||
|
@NotNull LocalDate targetDate) {
|
||||||
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
|
periodDueDate, targetDate);
|
||||||
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
|
return repaymentPeriod.getCalculatedDueInterest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate,
|
||||||
LocalDate targetDate) {
|
LocalDate targetDate) {
|
||||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||||
repaymentPeriodDueDate, targetDate);
|
periodDueDate, targetDate);
|
||||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriod(repaymentPeriodDueDate).orElseThrow();
|
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
|
|
||||||
return repaymentPeriod.getOutstandingLoanBalance();
|
return repaymentPeriod.getOutstandingLoanBalance();
|
||||||
}
|
}
|
||||||
@@ -189,7 +218,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
public OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) {
|
public OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) {
|
||||||
MathContext mc = scheduleModel.mc();
|
MathContext mc = scheduleModel.mc();
|
||||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||||
|
// TODO use findInterestPeriod
|
||||||
scheduleModelCopy.repaymentPeriods().stream()//
|
scheduleModelCopy.repaymentPeriods().stream()//
|
||||||
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()//
|
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()//
|
||||||
.flatMap(rp -> rp.getInterestPeriods().stream()//
|
.flatMap(rp -> rp.getInterestPeriods().stream()//
|
||||||
@@ -214,25 +243,26 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(ProgressiveLoanInterestScheduleModel scheduleModel,
|
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(
|
||||||
LocalDate repaymentPeriodDueDate, LocalDate targetDate) {
|
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
|
||||||
MathContext mc = scheduleModel.mc();
|
MathContext mc = scheduleModel.mc();
|
||||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||||
RepaymentPeriod repaymentPeriod = scheduleModelCopy.repaymentPeriods().stream()
|
RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||||
.filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow();
|
|
||||||
|
|
||||||
LocalDate adjustedTargetDate = targetDate;
|
LocalDate adjustedTargetDate = targetDate;
|
||||||
InterestPeriod interestPeriod;
|
InterestPeriod interestPeriod;
|
||||||
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
||||||
interestPeriod = repaymentPeriod.getInterestPeriods().get(0);
|
interestPeriod = repaymentPeriod.getFirstInterestPeriod();
|
||||||
adjustedTargetDate = repaymentPeriod.getFromDate();
|
adjustedTargetDate = repaymentPeriod.getFromDate();
|
||||||
} else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
|
} else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
|
||||||
interestPeriod = repaymentPeriod.getInterestPeriods().get(repaymentPeriod.getInterestPeriods().size() - 1);
|
interestPeriod = repaymentPeriod.getLastInterestPeriod();
|
||||||
adjustedTargetDate = repaymentPeriod.getDueDate();
|
adjustedTargetDate = repaymentPeriod.getDueDate();
|
||||||
} else {
|
} else {
|
||||||
|
// TODO use findInterestPeriod
|
||||||
interestPeriod = repaymentPeriod.getInterestPeriods().stream()
|
interestPeriod = repaymentPeriod.getInterestPeriods().stream()
|
||||||
.filter(ip -> targetDate.isAfter(ip.getFromDate()) && !targetDate.isAfter(ip.getDueDate())).findFirst().orElseThrow();
|
.filter(ip -> targetDate.isAfter(ip.getFromDate()) && !targetDate.isAfter(ip.getDueDate())).findFirst().orElseThrow();
|
||||||
}
|
}
|
||||||
|
// TODO use findInterestPeriod
|
||||||
scheduleModelCopy.repaymentPeriods().stream()//
|
scheduleModelCopy.repaymentPeriods().stream()//
|
||||||
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()//
|
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()//
|
||||||
.flatMap(rp -> rp.getInterestPeriods().stream()//
|
.flatMap(rp -> rp.getInterestPeriods().stream()//
|
||||||
@@ -645,6 +675,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
|
BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
|
||||||
final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
|
final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
|
||||||
final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
|
final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
|
||||||
|
if (MathUtil.isZero(calculatedDaysInPeriod)) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays//
|
final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays//
|
||||||
.multiply(repaymentEvery, mc)//
|
.multiply(repaymentEvery, mc)//
|
||||||
.divide(daysInYear, mc);//
|
.divide(daysInYear, mc);//
|
||||||
@@ -661,6 +694,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
|
BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
|
||||||
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
||||||
final MathContext mc) {
|
final MathContext mc) {
|
||||||
|
if (MathUtil.isZero(calculatedDaysInPeriod)) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio);
|
final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio);
|
||||||
return interestRate//
|
return interestRate//
|
||||||
.multiply(interestFractionPerPeriod, mc)//
|
.multiply(interestFractionPerPeriod, mc)//
|
||||||
@@ -682,24 +718,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc);
|
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
|
|
||||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc) {
|
|
||||||
List<LoanRepaymentScheduleInstallment> repaymentModelsWithoutDownPayment = repaymentPeriods.stream()
|
|
||||||
.filter(period -> !period.isDownPayment() && !period.isAdditional()).toList();
|
|
||||||
|
|
||||||
List<RepaymentPeriod> repaymentModels = new ArrayList<>();
|
|
||||||
RepaymentPeriod previousPeriod = null;
|
|
||||||
for (LoanRepaymentScheduleInstallment repaymentModel : repaymentModelsWithoutDownPayment) {
|
|
||||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentModel.getFromDate(), repaymentModel.getDueDate(),
|
|
||||||
Money.zero(repaymentModel.getLoan().getCurrency(), mc), mc);
|
|
||||||
previousPeriod = currentPeriod;
|
|
||||||
repaymentModels.add(currentPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the sum of due interests on interest periods.
|
* Calculates the sum of due interests on interest periods.
|
||||||
*
|
*
|
||||||
|
|||||||
+5
-3
@@ -60,6 +60,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||||
@@ -95,6 +96,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||||
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
||||||
private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
|
private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
|
||||||
|
private static final LoanRepositoryWrapper loanRepositoryWrapper = Mockito.mock(LoanRepositoryWrapper.class);
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void init() {
|
public static void init() {
|
||||||
@@ -109,7 +111,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, null);
|
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null);
|
||||||
|
|
||||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
@@ -235,7 +237,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||||
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney);
|
||||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||||
when(loan.loanCurrency()).thenReturn(currency);
|
when(loan.getCurrency()).thenReturn(currency);
|
||||||
when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||||
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||||
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
when(loan.getDisbursementDate()).thenReturn(disbursementDate);
|
||||||
@@ -451,7 +453,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(loanProductRelatedDetail.isInterestRecalculationEnabled()).thenReturn(true);
|
when(loanProductRelatedDetail.isInterestRecalculationEnabled()).thenReturn(true);
|
||||||
|
|
||||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||||
when(loan.loanCurrency()).thenReturn(currency);
|
when(loan.getCurrency()).thenReturn(currency);
|
||||||
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||||
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||||
when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
||||||
|
|||||||
+48
-61
@@ -158,7 +158,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_generateInterestScheduleModel() {
|
public void test_generateInterestScheduleModel() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
final Integer installmentAmountInMultiplesOf = null;
|
final Integer installmentAmountInMultiplesOf = null;
|
||||||
|
|
||||||
@@ -169,8 +168,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator
|
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
.generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Assertions.assertTrue(interestScheduleModel != null);
|
Assertions.assertTrue(interestScheduleModel != null);
|
||||||
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
|
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
|
||||||
@@ -183,7 +182,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
@Test
|
@Test
|
||||||
@Timeout(1) // seconds
|
@Timeout(1) // seconds
|
||||||
public void test_emi_calculator_performance() {
|
public void test_emi_calculator_performance() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -209,8 +207,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -234,7 +232,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_emiAdjustment_newCalculatedEmiNotBetterThanOriginal() {
|
public void test_emiAdjustment_newCalculatedEmiNotBetterThanOriginal() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -254,8 +251,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -271,7 +268,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -291,8 +287,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -308,7 +304,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_multi_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_multi_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -328,8 +323,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -355,7 +350,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -375,8 +369,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -392,7 +386,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_reschedule_interest_on0201_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_reschedule_interest_on0201_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -414,8 +407,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -435,7 +428,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_reschedule_interest_on0201_2nd_EMI_not_changeable_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_reschedule_interest_on0201_2nd_EMI_not_changeable_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -457,8 +449,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -504,8 +496,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -529,7 +521,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_reschedule_interest_on0215_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_reschedule_interest_on0215_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -551,8 +542,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -576,7 +567,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void test_balance_correction_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_balance_correction_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -598,8 +588,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -684,7 +674,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_payoff_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_payoff_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -706,8 +695,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -723,9 +712,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getDuePrincipal()));
|
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getDuePrincipal()));
|
||||||
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getDueInterest()));
|
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getDueInterest()));
|
||||||
|
|
||||||
PeriodDueDetails details = null;
|
// check getDueAmounts forcast
|
||||||
// check getdueAmounts forcast
|
PeriodDueDetails details = emiCalculator.getDueAmounts(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
||||||
details = emiCalculator.getDueAmounts(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1));
|
|
||||||
Assertions.assertEquals(16.52, toDouble(details.getDuePrincipal()));
|
Assertions.assertEquals(16.52, toDouble(details.getDuePrincipal()));
|
||||||
Assertions.assertEquals(0.49, toDouble(details.getDueInterest()));
|
Assertions.assertEquals(0.49, toDouble(details.getDueInterest()));
|
||||||
|
|
||||||
@@ -757,7 +745,6 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_payoff_on0115_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
public void test_payoff_on0115_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
|
||||||
|
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
|
|
||||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||||
@@ -779,8 +766,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -856,8 +843,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = toMoney(100);
|
Money disbursedAmount = toMoney(100);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -905,8 +892,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = toMoney(100.0);
|
Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -954,8 +941,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
Money disbursedAmount = toMoney(100.0);
|
Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
|
||||||
@@ -998,8 +985,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
|
||||||
@@ -1032,8 +1019,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(1000.0);
|
final Money disbursedAmount = toMoney(1000.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -1065,8 +1052,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -1098,8 +1085,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -1131,8 +1118,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(100.0);
|
final Money disbursedAmount = toMoney(100.0);
|
||||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -1163,8 +1150,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount = toMoney(1000.0);
|
final Money disbursedAmount = toMoney(1000.0);
|
||||||
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||||
@@ -1225,8 +1212,8 @@ class ProgressiveEMICalculatorTest {
|
|||||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||||
|
|
||||||
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
|
||||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||||
|
|
||||||
final Money disbursedAmount1st = toMoney(1000.0);
|
final Money disbursedAmount1st = toMoney(1000.0);
|
||||||
final Money disbursedAmount2nd = toMoney(1000.0);
|
final Money disbursedAmount2nd = toMoney(1000.0);
|
||||||
|
|||||||
+27
-29
@@ -36,6 +36,7 @@ import org.apache.fineract.accounting.journalentry.data.LoanDTO;
|
|||||||
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
|
import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -51,69 +52,66 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
|||||||
for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
|
for (final LoanTransactionDTO loanTransactionDTO : loanDTO.getNewLoanTransactions()) {
|
||||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||||
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
|
this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
|
||||||
|
final LoanTransactionEnumData transactionType = loanTransactionDTO.getTransactionType();
|
||||||
|
|
||||||
/** Handle Disbursements **/
|
// Handle Disbursements
|
||||||
if (loanTransactionDTO.getTransactionType().isDisbursement()) {
|
if (transactionType.isDisbursement()) {
|
||||||
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForDisbursements(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Handle Accruals ***/
|
// Handle Accruals
|
||||||
if (loanTransactionDTO.getTransactionType().isAccrual()) {
|
if (transactionType.isAccrual() || transactionType.isAccrualAdjustment()) {
|
||||||
createJournalEntriesForAccruals(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForAccruals(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/*
|
||||||
* Handle repayments, loan refunds, repayments at disbursement and reversal of Repayments and Repayments at
|
* Handle repayments, loan refunds, repayments at disbursement and reversal of Repayments and Repayments at
|
||||||
* disbursement (except charge adjustment)
|
* disbursement (except charge adjustment)
|
||||||
***/
|
*/
|
||||||
else if ((loanTransactionDTO.getTransactionType().isRepaymentType()
|
else if ((transactionType.isRepaymentType() && !transactionType.isChargeAdjustment())
|
||||||
&& !loanTransactionDTO.getTransactionType().isChargeAdjustment())
|
|| transactionType.isRepaymentAtDisbursement() || transactionType.isChargePayment()) {
|
||||||
|| loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement()
|
|
||||||
|| loanTransactionDTO.getTransactionType().isChargePayment()) {
|
|
||||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false,
|
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, false,
|
||||||
loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement());
|
transactionType.isRepaymentAtDisbursement());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logic for handling recovery payments **/
|
// Logic for handling recovery payments
|
||||||
else if (loanTransactionDTO.getTransactionType().isRecoveryRepayment()) {
|
else if (transactionType.isRecoveryRepayment()) {
|
||||||
createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForRecoveryRepayments(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logic for Refunds of Overpayments **/
|
// Logic for Refunds of Overpayments
|
||||||
else if (loanTransactionDTO.getTransactionType().isRefund()) {
|
else if (transactionType.isRefund()) {
|
||||||
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForRefund(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logic for Credit Balance Refunds **/
|
// Logic for Credit Balance Refunds
|
||||||
else if (loanTransactionDTO.getTransactionType().isCreditBalanceRefund()) {
|
else if (transactionType.isCreditBalanceRefund()) {
|
||||||
createJournalEntriesForCreditBalanceRefund(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForCreditBalanceRefund(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle Write Offs, waivers and their reversals **/
|
// Handle Write Offs, waivers and their reversals
|
||||||
else if ((loanTransactionDTO.getTransactionType().isWriteOff() || loanTransactionDTO.getTransactionType().isWaiveInterest()
|
else if ((transactionType.isWriteOff() || transactionType.isWaiveInterest() || transactionType.isWaiveCharges())) {
|
||||||
|| loanTransactionDTO.getTransactionType().isWaiveCharges())) {
|
|
||||||
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
|
createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logic for Refunds of Active Loans **/
|
// Logic for Refunds of Active Loans
|
||||||
else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) {
|
else if (transactionType.isRefundForActiveLoans()) {
|
||||||
createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
// Logic for Chargebacks
|
// Logic for Chargebacks
|
||||||
else if (loanTransactionDTO.getTransactionType().isChargeback()) {
|
else if (transactionType.isChargeback()) {
|
||||||
createJournalEntriesForChargeback(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForChargeback(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
// Logic for Charge Adjustment
|
// Logic for Charge Adjustment
|
||||||
else if (loanTransactionDTO.getTransactionType().isChargeAdjustment()) {
|
else if (transactionType.isChargeAdjustment()) {
|
||||||
createJournalEntriesForChargeAdjustment(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForChargeAdjustment(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
// Logic for Charge-Off
|
// Logic for Charge-Off
|
||||||
else if (loanTransactionDTO.getTransactionType().isChargeoff()) {
|
else if (transactionType.isChargeoff()) {
|
||||||
createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForChargeOff(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
// Logic for Interest Payment Waiver
|
// Logic for Interest Payment Waiver
|
||||||
else if (loanTransactionDTO.getTransactionType().isInterestPaymentWaiver()
|
else if (transactionType.isInterestPaymentWaiver() || transactionType.isInterestRefund()) {
|
||||||
|| loanTransactionDTO.getTransactionType().isInterestRefund()) {
|
|
||||||
createJournalEntriesForInterestPaymentWaiverOrInterestRefund(loanDTO, loanTransactionDTO, office);
|
createJournalEntriesForInterestPaymentWaiverOrInterestRefund(loanDTO, loanTransactionDTO, office);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1142,6 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
|||||||
* @param office
|
* @param office
|
||||||
*/
|
*/
|
||||||
private void createJournalEntriesForAccruals(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
private void createJournalEntriesForAccruals(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||||
|
|
||||||
// loan properties
|
// loan properties
|
||||||
final Long loanProductId = loanDTO.getLoanProductId();
|
final Long loanProductId = loanDTO.getLoanProductId();
|
||||||
final Long loanId = loanDTO.getLoanId();
|
final Long loanId = loanDTO.getLoanId();
|
||||||
@@ -1153,10 +1150,11 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
|||||||
// transaction properties
|
// transaction properties
|
||||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||||
|
final LoanTransactionEnumData transactionType = loanTransactionDTO.getTransactionType();
|
||||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||||
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
final BigDecimal penaltiesAmount = loanTransactionDTO.getPenalties();
|
||||||
final boolean isReversed = loanTransactionDTO.isReversed();
|
final boolean isReversed = transactionType.isAccrualAdjustment() != loanTransactionDTO.isReversed();
|
||||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||||
|
|
||||||
// create journal entries for recognizing interest (or reversal)
|
// create journal entries for recognizing interest (or reversal)
|
||||||
|
|||||||
+1
-1
@@ -190,7 +190,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<ChargeData> retrieveLoanAccountApplicableCharges(final Long loanId, ChargeTimeType[] excludeChargeTimes) {
|
public List<ChargeData> retrieveLoanAccountApplicableCharges(final Long loanId, ChargeTimeType[] excludeChargeTimes) {
|
||||||
final ChargeMapper rm = new ChargeMapper();
|
final ChargeMapper rm = new ChargeMapper();
|
||||||
StringBuilder excludeClause = new StringBuilder("");
|
StringBuilder excludeClause = new StringBuilder("");
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
|||||||
+3
-2
@@ -42,6 +42,7 @@ import jakarta.ws.rs.core.UriInfo;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
@@ -442,7 +443,7 @@ public class LoanChargesApiResource {
|
|||||||
final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(resolvedLoanChargeId,
|
final LoanChargeData loanCharge = this.loanChargeReadPlatformService.retrieveLoanChargeDetails(resolvedLoanChargeId,
|
||||||
resolvedLoanId);
|
resolvedLoanId);
|
||||||
|
|
||||||
final Collection<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService
|
final List<LoanInstallmentChargeData> installmentChargeData = this.loanChargeReadPlatformService
|
||||||
.retrieveInstallmentLoanCharges(resolvedLoanChargeId, true);
|
.retrieveInstallmentLoanCharges(resolvedLoanChargeId, true);
|
||||||
|
|
||||||
final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
|
final LoanChargeData loanChargeData = new LoanChargeData(loanCharge, installmentChargeData);
|
||||||
@@ -536,7 +537,7 @@ public class LoanChargesApiResource {
|
|||||||
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
|
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
|
||||||
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
|
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
|
||||||
|
|
||||||
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(resolvedLoanId,
|
final List<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(resolvedLoanId,
|
||||||
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
|
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
|
||||||
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
|
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
|
||||||
|
|
||||||
|
|||||||
+2
-10
@@ -759,7 +759,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
|
||||||
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
||||||
|
|
||||||
loanAccrualsProcessingService.processAccrualsForLoanForeClosure(loan, foreClosureDate, newTransactions);
|
loanAccrualsProcessingService.processAccrualsOnLoanForeClosure(loan, foreClosureDate, newTransactions);
|
||||||
|
|
||||||
Money interestPayable = foreCloseDetail.getInterestCharged(currency);
|
Money interestPayable = foreCloseDetail.getInterestCharged(currency);
|
||||||
Money feePayable = foreCloseDetail.getFeeChargesCharged(currency);
|
Money feePayable = foreCloseDetail.getFeeChargesCharged(currency);
|
||||||
@@ -922,15 +922,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
if (interestRefundTransaction != null) {
|
if (interestRefundTransaction != null) {
|
||||||
loan.addLoanTransaction(interestRefundTransaction);
|
loan.addLoanTransaction(interestRefundTransaction);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
|
||||||
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
|
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(loan);
|
|
||||||
}
|
|
||||||
|
|
||||||
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
|
||||||
|
|
||||||
// Store and flush newly created transaction to generate PK
|
// Store and flush newly created transaction to generate PK
|
||||||
saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction);
|
saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction);
|
||||||
|
|||||||
+11
-29
@@ -18,15 +18,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.jobs.addaccrualentries;
|
package org.apache.fineract.portfolio.loanaccount.jobs.addaccrualentries;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
import org.springframework.batch.core.StepContribution;
|
import org.springframework.batch.core.StepContribution;
|
||||||
@@ -45,30 +42,15 @@ public class AddAccrualEntriesTasklet implements Tasklet {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = loanReadPlatformService.retriveScheduleAccrualData();
|
try {
|
||||||
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
|
addAccruals(DateUtils.getBusinessLocalDate());
|
||||||
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDataList) {
|
} catch (MultiException e) {
|
||||||
if (loanDataMap.containsKey(accrualData.getLoanId())) {
|
throw new JobExecutionException(e);
|
||||||
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
|
|
||||||
} else {
|
|
||||||
Collection<LoanScheduleAccrualData> accrualDataList = new ArrayList<>();
|
|
||||||
accrualDataList.add(accrualData);
|
|
||||||
loanDataMap.put(accrualData.getLoanId(), accrualDataList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
|
|
||||||
try {
|
|
||||||
loanAccrualsProcessingService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to add accrual transaction for loan {}", mapEntry.getKey(), e);
|
|
||||||
errors.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
}
|
||||||
return RepeatStatus.FINISHED;
|
return RepeatStatus.FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addAccruals(final LocalDate tilldate) throws MultiException {
|
||||||
|
loanAccrualsProcessingService.addAccruals(tilldate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -440,7 +440,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
}
|
}
|
||||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||||
loan.recalculateAllCharges();
|
loan.recalculateAllCharges();
|
||||||
ChangedTransactionDetail changedTransactionDetail = loan.processTransactions();
|
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
|
||||||
|
|
||||||
this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList);
|
this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList);
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -635,7 +635,8 @@ public final class LoanApplicationValidator {
|
|||||||
"error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
|
"error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
|
||||||
"loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
|
"loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
|
||||||
}
|
}
|
||||||
if (loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()) {
|
if (loanToClose.isMultiDisburmentLoan()
|
||||||
|
&& !loanToClose.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||||
throw new GeneralPlatformDomainRuleException(
|
throw new GeneralPlatformDomainRuleException(
|
||||||
"error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
|
"error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
|
||||||
"Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
|
"Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
|
||||||
@@ -1346,7 +1347,8 @@ public final class LoanApplicationValidator {
|
|||||||
"error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
|
"error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
|
||||||
"loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
|
"loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
|
||||||
}
|
}
|
||||||
if (loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()) {
|
if (loanToClose.isMultiDisburmentLoan()
|
||||||
|
&& !loanToClose.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||||
throw new GeneralPlatformDomainRuleException(
|
throw new GeneralPlatformDomainRuleException(
|
||||||
"error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
|
"error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
|
||||||
"Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
|
"Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
|
||||||
|
|||||||
+814
-1073
File diff suppressed because it is too large
Load Diff
+11
-1
@@ -104,7 +104,7 @@ import org.apache.fineract.portfolio.rate.service.RateAssembler;
|
|||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanAssembler {
|
public class LoanAssemblerImpl implements LoanAssembler {
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final LoanRepositoryWrapper loanRepository;
|
private final LoanRepositoryWrapper loanRepository;
|
||||||
@@ -136,6 +136,7 @@ public class LoanAssembler {
|
|||||||
private final LoanCollateralManagementMapper loanCollateralManagementMapper;
|
private final LoanCollateralManagementMapper loanCollateralManagementMapper;
|
||||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||||
|
|
||||||
|
@Override
|
||||||
public Loan assembleFrom(final Long accountId) {
|
public Loan assembleFrom(final Long accountId) {
|
||||||
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
|
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
|
||||||
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
|
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
|
||||||
@@ -144,11 +145,13 @@ public class LoanAssembler {
|
|||||||
return loanAccount;
|
return loanAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setHelpers(final Loan loanAccount) {
|
public void setHelpers(final Loan loanAccount) {
|
||||||
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
|
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
|
||||||
this.loanRepaymentScheduleTransactionProcessorFactory);
|
this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Loan assembleFrom(final JsonCommand command) {
|
public Loan assembleFrom(final JsonCommand command) {
|
||||||
final JsonElement element = command.parsedJson();
|
final JsonElement element = command.parsedJson();
|
||||||
|
|
||||||
@@ -281,6 +284,7 @@ public class LoanAssembler {
|
|||||||
|
|
||||||
// TODO: Review... it might be better somewhere else and rethink due to the account number generation logic is
|
// TODO: Review... it might be better somewhere else and rethink due to the account number generation logic is
|
||||||
// intertwined with GLIM logic
|
// intertwined with GLIM logic
|
||||||
|
@Override
|
||||||
public void accountNumberGeneration(JsonCommand command, Loan loan) {
|
public void accountNumberGeneration(JsonCommand command, Loan loan) {
|
||||||
if (loan.isAccountNumberRequiresAutoGeneration()) {
|
if (loan.isAccountNumberRequiresAutoGeneration()) {
|
||||||
JsonElement element = command.parsedJson();
|
JsonElement element = command.parsedJson();
|
||||||
@@ -371,6 +375,7 @@ public class LoanAssembler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
|
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
|
||||||
CodeValue codeValue = null;
|
CodeValue codeValue = null;
|
||||||
if (codeValueId != null) {
|
if (codeValueId != null) {
|
||||||
@@ -379,6 +384,7 @@ public class LoanAssembler {
|
|||||||
return codeValue;
|
return codeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Fund findFundByIdIfProvided(final Long fundId) {
|
public Fund findFundByIdIfProvided(final Long fundId) {
|
||||||
Fund fund = null;
|
Fund fund = null;
|
||||||
if (fundId != null) {
|
if (fundId != null) {
|
||||||
@@ -387,6 +393,7 @@ public class LoanAssembler {
|
|||||||
return fund;
|
return fund;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) {
|
public Staff findLoanOfficerByIdIfProvided(final Long loanOfficerId) {
|
||||||
Staff staff = null;
|
Staff staff = null;
|
||||||
if (loanOfficerId != null) {
|
if (loanOfficerId != null) {
|
||||||
@@ -419,6 +426,7 @@ public class LoanAssembler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<String, Object> updateFrom(JsonCommand command, Loan loan) {
|
public Map<String, Object> updateFrom(JsonCommand command, Loan loan) {
|
||||||
final Map<String, Object> changes = new HashMap<>();
|
final Map<String, Object> changes = new HashMap<>();
|
||||||
LoanProduct loanProduct;
|
LoanProduct loanProduct;
|
||||||
@@ -844,6 +852,7 @@ public class LoanAssembler {
|
|||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<String, Object> updateLoanApplicationAttributesForWithdrawal(Loan loan, JsonCommand command, AppUser currentUser) {
|
public Map<String, Object> updateLoanApplicationAttributesForWithdrawal(Loan loan, JsonCommand command, AppUser currentUser) {
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
||||||
|
|
||||||
@@ -868,6 +877,7 @@ public class LoanAssembler {
|
|||||||
return actualChanges;
|
return actualChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<String, Object> updateLoanApplicationAttributesForRejection(Loan loan, JsonCommand command, AppUser currentUser) {
|
public Map<String, Object> updateLoanApplicationAttributesForRejection(Loan loan, JsonCommand command, AppUser currentUser) {
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
||||||
|
|
||||||
+1
-241
@@ -24,9 +24,7 @@ import java.sql.SQLException;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
@@ -214,7 +212,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges) {
|
public List<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges) {
|
||||||
final LoanInstallmentChargeMapper rm = new LoanInstallmentChargeMapper();
|
final LoanInstallmentChargeMapper rm = new LoanInstallmentChargeMapper();
|
||||||
String sql = "select " + rm.schema() + "where lic.loan_charge_id= ? ";
|
String sql = "select " + rm.schema() + "where lic.loan_charge_id= ? ";
|
||||||
if (onlyPaymentPendingCharges) {
|
if (onlyPaymentPendingCharges) {
|
||||||
@@ -263,244 +261,6 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
|
|||||||
return frequencyNumbers;
|
return frequencyNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanChargeData> retrieveLoanChargesForAccrual(final Long loanId) {
|
|
||||||
|
|
||||||
final LoanChargeAccrualMapper rm = new LoanChargeAccrualMapper();
|
|
||||||
|
|
||||||
final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = true group by lc.id "
|
|
||||||
+ " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
|
|
||||||
|
|
||||||
Collection<LoanChargeData> charges = this.jdbcTemplate.query(sql, rm, // NOSONAR
|
|
||||||
LoanTransactionType.ACCRUAL.getValue(), loanId, loanId);
|
|
||||||
charges = updateLoanChargesWithUnrecognizedIncome(loanId, charges);
|
|
||||||
|
|
||||||
Collection<LoanChargeData> removeCharges = new ArrayList<>();
|
|
||||||
for (LoanChargeData loanChargeData : charges) {
|
|
||||||
if (loanChargeData.isInstallmentFee()) {
|
|
||||||
removeCharges.add(loanChargeData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
charges.removeAll(removeCharges);
|
|
||||||
for (LoanChargeData loanChargeData : removeCharges) {
|
|
||||||
if (loanChargeData.isInstallmentFee()) {
|
|
||||||
Collection<LoanInstallmentChargeData> installmentChargeDatas = retrieveInstallmentLoanChargesForAccrual(
|
|
||||||
loanChargeData.getId());
|
|
||||||
LoanChargeData modifiedChargeData = new LoanChargeData(loanChargeData, installmentChargeDatas);
|
|
||||||
charges.add(modifiedChargeData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return charges;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanChargeAccrualMapper implements RowMapper<LoanChargeData> {
|
|
||||||
|
|
||||||
private final String schemaSql;
|
|
||||||
|
|
||||||
LoanChargeAccrualMapper() {
|
|
||||||
StringBuilder sb = new StringBuilder(50);
|
|
||||||
sb.append(" lc.id as id, lc.charge_id as chargeId, lc.external_id as externalId, ");
|
|
||||||
sb.append(" lc.amount as amountDue, ");
|
|
||||||
sb.append(" lc.amount_waived_derived as amountWaived, ");
|
|
||||||
sb.append(" lc.charge_time_enum as chargeTime, ");
|
|
||||||
sb.append(" sum(cp.amount) as amountAccrued, ");
|
|
||||||
sb.append(" lc.is_penalty as penalty, ");
|
|
||||||
sb.append(" lc.due_for_collection_as_of_date as dueAsOfDate, ");
|
|
||||||
sb.append(" lc.submitted_on_date as submittedOnDate ");
|
|
||||||
sb.append(" from m_loan_charge lc ");
|
|
||||||
sb.append(" left join ( ");
|
|
||||||
sb.append(" select lcp.loan_charge_id, lcp.amount ");
|
|
||||||
sb.append(" from m_loan_charge_paid_by lcp ");
|
|
||||||
sb.append(
|
|
||||||
" inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ? and lt.loan_id = ? ");
|
|
||||||
sb.append(" ) cp on cp.loan_charge_id= lc.id ");
|
|
||||||
|
|
||||||
schemaSql = sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return this.schemaSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Long id = rs.getLong("id");
|
|
||||||
final Long chargeId = rs.getLong("chargeId");
|
|
||||||
final BigDecimal amount = rs.getBigDecimal("amountDue");
|
|
||||||
final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
|
|
||||||
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
|
|
||||||
|
|
||||||
final int chargeTime = rs.getInt("chargeTime");
|
|
||||||
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
|
|
||||||
|
|
||||||
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
|
|
||||||
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
|
||||||
final boolean penalty = rs.getBoolean("penalty");
|
|
||||||
|
|
||||||
final String externalIdStr = rs.getString("externalId");
|
|
||||||
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
|
|
||||||
|
|
||||||
return new LoanChargeData(id, chargeId, dueAsOfDate, submittedOnDate, chargeTimeType, amount, amountAccrued, amountWaived,
|
|
||||||
penalty, externalId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanChargeData> updateLoanChargesWithUnrecognizedIncome(final Long loanId,
|
|
||||||
Collection<LoanChargeData> loanChargeDatas) {
|
|
||||||
|
|
||||||
final LoanChargeUnRecognizedIncomeMapper rm = new LoanChargeUnRecognizedIncomeMapper(loanChargeDatas);
|
|
||||||
|
|
||||||
final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = true group by lc.id "
|
|
||||||
+ " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
|
|
||||||
|
|
||||||
return this.jdbcTemplate.query(sql, rm, LoanTransactionType.WAIVE_CHARGES.getValue(), loanId, loanId); // NOSONAR
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanChargeUnRecognizedIncomeMapper implements RowMapper<LoanChargeData> {
|
|
||||||
|
|
||||||
private final String schemaSql;
|
|
||||||
private final Map<Long, LoanChargeData> chargeDataMap;
|
|
||||||
|
|
||||||
LoanChargeUnRecognizedIncomeMapper(final Collection<LoanChargeData> datas) {
|
|
||||||
this.chargeDataMap = new HashMap<>();
|
|
||||||
for (LoanChargeData chargeData : datas) {
|
|
||||||
this.chargeDataMap.put(chargeData.getId(), chargeData);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder(50);
|
|
||||||
sb.append("lc.id as id, ");
|
|
||||||
sb.append(" sum(wt.unrecognized_income_portion) as amountUnrecognized ");
|
|
||||||
sb.append(" from m_loan_charge lc ");
|
|
||||||
sb.append("left join (");
|
|
||||||
sb.append("select cpb.loan_charge_id, lt.unrecognized_income_portion");
|
|
||||||
sb.append(" from m_loan_charge_paid_by cpb ");
|
|
||||||
sb.append(
|
|
||||||
"inner join m_loan_transaction lt on lt.id = cpb.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ? and lt.loan_id = ? ");
|
|
||||||
sb.append(") wt on wt.loan_charge_id= lc.id ");
|
|
||||||
|
|
||||||
schemaSql = sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return this.schemaSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Long id = rs.getLong("id");
|
|
||||||
final BigDecimal amountUnrecognized = rs.getBigDecimal("amountUnrecognized");
|
|
||||||
|
|
||||||
LoanChargeData chargeData = this.chargeDataMap.get(id);
|
|
||||||
return new LoanChargeData(amountUnrecognized, chargeData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanInstallmentChargeData> retrieveInstallmentLoanChargesForAccrual(Long loanChargeId) {
|
|
||||||
final LoanInstallmentChargeAccrualMapper rm = new LoanInstallmentChargeAccrualMapper();
|
|
||||||
String sql = "select " + rm.schema()
|
|
||||||
+ " where lic.loan_charge_id= ? group by lsi.installment, lsi.duedate, lic.amount_outstanding_derived, lic.amount, lic.is_paid_derived, lic.amount_waived_derived, lic.waived";
|
|
||||||
Collection<LoanInstallmentChargeData> chargeDatas = this.jdbcTemplate.query(sql, rm, // NOSONAR
|
|
||||||
LoanTransactionType.ACCRUAL.getValue(), loanChargeId);
|
|
||||||
final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas = new HashMap<>();
|
|
||||||
for (LoanInstallmentChargeData installmentChargeData : chargeDatas) {
|
|
||||||
installmentChargeDatas.put(installmentChargeData.getInstallmentNumber(), installmentChargeData);
|
|
||||||
}
|
|
||||||
chargeDatas = updateInstallmentLoanChargesWithUnrecognizedIncome(loanChargeId, installmentChargeDatas);
|
|
||||||
for (LoanInstallmentChargeData installmentChargeData : chargeDatas) {
|
|
||||||
installmentChargeDatas.put(installmentChargeData.getInstallmentNumber(), installmentChargeData);
|
|
||||||
}
|
|
||||||
return installmentChargeDatas.values();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanInstallmentChargeAccrualMapper implements RowMapper<LoanInstallmentChargeData> {
|
|
||||||
|
|
||||||
private final String schemaSql;
|
|
||||||
|
|
||||||
LoanInstallmentChargeAccrualMapper() {
|
|
||||||
StringBuilder sb = new StringBuilder(50);
|
|
||||||
sb.append(" lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, ");
|
|
||||||
sb.append("lic.amount_outstanding_derived as amountOutstanding,");
|
|
||||||
sb.append("lic.amount as amount, ");
|
|
||||||
sb.append("lic.is_paid_derived as paid, ");
|
|
||||||
sb.append("lic.amount_waived_derived as amountWaived, ");
|
|
||||||
sb.append(" sum(cp.amount) as amountAccrued, ");
|
|
||||||
sb.append("lic.waived as waived ");
|
|
||||||
sb.append("from m_loan_installment_charge lic ");
|
|
||||||
sb.append("join m_loan_repayment_schedule lsi on lsi.id = lic.loan_schedule_id ");
|
|
||||||
sb.append("left join (");
|
|
||||||
sb.append("select lcp.loan_charge_id, lcp.amount as amount, lcp.installment_number ");
|
|
||||||
sb.append(" from m_loan_charge_paid_by lcp ");
|
|
||||||
sb.append(
|
|
||||||
"inner join m_loan_transaction lt on lt.id = lcp.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ?");
|
|
||||||
sb.append(") cp on cp.loan_charge_id= lic.loan_charge_id and cp.installment_number = lsi.installment ");
|
|
||||||
schemaSql = sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return this.schemaSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
|
||||||
final Integer installmentNumber = rs.getInt("installmentNumber");
|
|
||||||
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
|
|
||||||
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
|
|
||||||
final BigDecimal amount = rs.getBigDecimal("amount");
|
|
||||||
final BigDecimal amountWaived = rs.getBigDecimal("amountWaived");
|
|
||||||
final boolean paid = rs.getBoolean("paid");
|
|
||||||
final boolean waived = rs.getBoolean("waived");
|
|
||||||
final BigDecimal amountAccrued = rs.getBigDecimal("amountAccrued");
|
|
||||||
|
|
||||||
return LoanInstallmentChargeData.builder().installmentNumber(installmentNumber).dueDate(dueAsOfDate).amount(amount)
|
|
||||||
.amountOutstanding(amountOutstanding).amountWaived(amountWaived).paid(paid).waived(waived).amountAccrued(amountAccrued)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanInstallmentChargeData> updateInstallmentLoanChargesWithUnrecognizedIncome(final Long loanChargeId,
|
|
||||||
final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas) {
|
|
||||||
final LoanInstallmentChargeUnRecognizedIncomeMapper rm = new LoanInstallmentChargeUnRecognizedIncomeMapper(installmentChargeDatas);
|
|
||||||
String sql = "select " + rm.schema() + " where cpb.loan_charge_id = ? group by cpb.installment_number ";
|
|
||||||
return this.jdbcTemplate.query(sql, rm, LoanTransactionType.WAIVE_CHARGES.getValue(), loanChargeId); // NOSONAR
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanInstallmentChargeUnRecognizedIncomeMapper implements RowMapper<LoanInstallmentChargeData> {
|
|
||||||
|
|
||||||
private final String schemaSql;
|
|
||||||
private final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas;
|
|
||||||
|
|
||||||
LoanInstallmentChargeUnRecognizedIncomeMapper(final Map<Integer, LoanInstallmentChargeData> installmentChargeDatas) {
|
|
||||||
this.installmentChargeDatas = installmentChargeDatas;
|
|
||||||
StringBuilder sb = new StringBuilder(50);
|
|
||||||
sb.append(" cpb.installment_number as installmentNumber, ");
|
|
||||||
sb.append(" sum(lt.unrecognized_income_portion) as amountUnrecognized ");
|
|
||||||
sb.append(" from m_loan_charge_paid_by cpb ");
|
|
||||||
sb.append(
|
|
||||||
"inner join m_loan_transaction lt on lt.id = cpb.loan_transaction_id and lt.is_reversed = false and lt.transaction_type_enum = ?");
|
|
||||||
schemaSql = sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return this.schemaSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanInstallmentChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
|
||||||
final Integer installmentNumber = rs.getInt("installmentNumber");
|
|
||||||
final BigDecimal amountUnrecognized = rs.getBigDecimal("amountUnrecognized");
|
|
||||||
LoanInstallmentChargeData installmentChargeData = this.installmentChargeDatas.get(installmentNumber);
|
|
||||||
return LoanInstallmentChargeData.builder().installmentNumber(installmentChargeData.getInstallmentNumber())
|
|
||||||
.dueDate(installmentChargeData.getDueDate()).amount(installmentChargeData.getAmount())
|
|
||||||
.amountOutstanding(installmentChargeData.getAmountOutstanding()).amountWaived(installmentChargeData.getAmountWaived())
|
|
||||||
.paid(installmentChargeData.isPaid()).waived(installmentChargeData.isWaived())
|
|
||||||
.amountAccrued(installmentChargeData.getAmountAccrued()).amountUnrecognized(amountUnrecognized).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, final LoanTransactionType transactionType,
|
public Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, final LoanTransactionType transactionType,
|
||||||
final Integer installmentNumber) {
|
final Integer installmentNumber) {
|
||||||
|
|||||||
+1
-4
@@ -838,14 +838,11 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT);
|
LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT);
|
||||||
loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
|
loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
|
||||||
|
|
||||||
defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
.determineProcessor(loan.transactionProcessingStrategy());
|
||||||
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
|
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
|
||||||
if (loan.isInterestBearing() && loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
if (loan.isInterestBearing() && loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(),
|
loan.reprocessTransactions();
|
||||||
loan.retrieveListOfTransactionsForReprocessing(), loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
|
||||||
loan.getActiveCharges());
|
|
||||||
} else {
|
} else {
|
||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction,
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
|||||||
-12
@@ -31,14 +31,12 @@ import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
|
|
||||||
@@ -98,16 +96,10 @@ public interface LoanReadPlatformService {
|
|||||||
|
|
||||||
DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId);
|
DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId);
|
||||||
|
|
||||||
Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
|
|
||||||
|
|
||||||
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
|
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
|
||||||
|
|
||||||
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
|
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
|
||||||
|
|
||||||
Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(LocalDate tillDate);
|
|
||||||
|
|
||||||
Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(LocalDate tillDate, Loan loan);
|
|
||||||
|
|
||||||
LoanTransactionData retrieveLoanChargeOffTemplate(Long loanId);
|
LoanTransactionData retrieveLoanChargeOffTemplate(Long loanId);
|
||||||
|
|
||||||
Collection<Long> fetchLoansForInterestRecalculation();
|
Collection<Long> fetchLoansForInterestRecalculation();
|
||||||
@@ -116,10 +108,6 @@ public interface LoanReadPlatformService {
|
|||||||
|
|
||||||
LoanTransactionData retrieveLoanPrePaymentTemplate(LoanTransactionType repaymentTransactionType, Long loanId, LocalDate onDate);
|
LoanTransactionData retrieveLoanPrePaymentTemplate(LoanTransactionType repaymentTransactionType, Long loanId, LocalDate onDate);
|
||||||
|
|
||||||
Collection<LoanTransactionData> retrieveWaiverLoanTransactions(Long loanId);
|
|
||||||
|
|
||||||
Collection<LoanSchedulePeriodData> fetchWaiverInterestRepaymentData(Long loanId);
|
|
||||||
|
|
||||||
boolean isGuaranteeRequired(Long loanId);
|
boolean isGuaranteeRequired(Long loanId);
|
||||||
|
|
||||||
LocalDate retrieveMinimumDateOfRepaymentTransaction(Long loanId);
|
LocalDate retrieveMinimumDateOfRepaymentTransaction(Long loanId);
|
||||||
|
|||||||
-400
@@ -29,18 +29,15 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.accounting.common.AccountingRuleType;
|
|
||||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||||
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
|
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
@@ -77,7 +74,6 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
|
|||||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
|
import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
|
||||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
|
||||||
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
|
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
|
||||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||||
@@ -96,7 +92,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||||
@@ -1742,258 +1737,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
return this.jdbcTemplate.queryForObject(sql, rm, loanId, disbursementId); // NOSONAR
|
return this.jdbcTemplate.queryForObject(sql, rm, loanId, disbursementId); // NOSONAR
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
|
|
||||||
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();
|
|
||||||
if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE)) {
|
|
||||||
return retrieveScheduleAccrualDataForChargeSubmittedDateProcessing();
|
|
||||||
}
|
|
||||||
return retrieveScheduleAccrualDataForDefaultProcessing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanScheduleAccrualData> retrieveScheduleAccrualDataForDefaultProcessing() {
|
|
||||||
LocalDate organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
|
||||||
LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper();
|
|
||||||
Map<String, Object> paramMap = new HashMap<>(3);
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("select ").append(mapper.schema()).append(
|
|
||||||
" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = false) ")
|
|
||||||
.append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))")
|
|
||||||
.append(" or ( ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))")
|
|
||||||
.append(" or ( ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))")
|
|
||||||
.append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false and loan.is_charged_off = false and ls.duedate <= :currentDate) ");
|
|
||||||
|
|
||||||
if (organisationStartDate != null) {
|
|
||||||
sqlBuilder.append(" and ls.duedate > :organisationStartDate ");
|
|
||||||
}
|
|
||||||
sqlBuilder.append(" order by loan.id,ls.duedate ");
|
|
||||||
|
|
||||||
paramMap.put("active", LoanStatus.ACTIVE.getValue());
|
|
||||||
paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue());
|
|
||||||
paramMap.put("organisationStartDate", (organisationStartDate == null) ? DateUtils.getBusinessLocalDate() : organisationStartDate);
|
|
||||||
paramMap.put("currentDate", DateUtils.getBusinessLocalDate());
|
|
||||||
return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanScheduleAccrualData> retrieveScheduleAccrualDataForChargeSubmittedDateProcessing() {
|
|
||||||
LocalDate organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
|
||||||
LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper();
|
|
||||||
Map<String, Object> paramMap = new HashMap<>(3);
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("select ").append(mapper.schema()).append(
|
|
||||||
" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = false) ")
|
|
||||||
.append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))")
|
|
||||||
.append(" or ( ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))")
|
|
||||||
.append(" or ( ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))")
|
|
||||||
.append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false and loan.is_charged_off = false) ");
|
|
||||||
|
|
||||||
if (organisationStartDate != null) {
|
|
||||||
sqlBuilder.append(" and ls.duedate > :organisationStartDate ");
|
|
||||||
}
|
|
||||||
sqlBuilder.append(" order by loan.id,ls.duedate ");
|
|
||||||
|
|
||||||
paramMap.put("active", LoanStatus.ACTIVE.getValue());
|
|
||||||
paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue());
|
|
||||||
paramMap.put("organisationStartDate", (organisationStartDate == null) ? DateUtils.getBusinessLocalDate() : organisationStartDate);
|
|
||||||
return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(final LocalDate tillDate) {
|
|
||||||
return retrievePeriodicAccrualData(tillDate, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(final LocalDate tillDate, final Loan loan) {
|
|
||||||
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();
|
|
||||||
if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE)) {
|
|
||||||
return retrievePeriodicAccrualDataForChargeSubmittedDateProcessing(tillDate, loan);
|
|
||||||
}
|
|
||||||
return retrievePeriodicAccrualDataForDefaultProcessing(tillDate, loan);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanScheduleAccrualData> retrievePeriodicAccrualDataForDefaultProcessing(final LocalDate tillDate, final Loan loan) {
|
|
||||||
LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper();
|
|
||||||
LocalDate organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("select ").append(mapper.schema()).append(
|
|
||||||
" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = false) ")
|
|
||||||
.append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))")
|
|
||||||
.append(" or (ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))")
|
|
||||||
.append(" or (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))")
|
|
||||||
.append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or loan.closedon_date is null)")
|
|
||||||
.append(" and loan.is_npa=false and loan.is_charged_off = false and (ls.duedate <= :tillDate or (ls.duedate > :tillDate and ls.fromdate < :tillDate)")
|
|
||||||
.append(" or (ls.installment = 1 and ls.fromdate = :tillDate))) ");
|
|
||||||
Map<String, Object> paramMap = new HashMap<>(5);
|
|
||||||
if (organisationStartDate != null) {
|
|
||||||
sqlBuilder.append(" and ls.duedate > :organisationStartDate ");
|
|
||||||
paramMap.put("organisationStartDate", organisationStartDate);
|
|
||||||
}
|
|
||||||
if (loan != null) {
|
|
||||||
sqlBuilder.append(" and loan.id= :loanId ");
|
|
||||||
paramMap.put("loanId", loan.getId());
|
|
||||||
}
|
|
||||||
sqlBuilder.append(" order by loan.id,ls.duedate ");
|
|
||||||
paramMap.put("active", LoanStatus.ACTIVE.getValue());
|
|
||||||
paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue());
|
|
||||||
paramMap.put("tillDate", tillDate);
|
|
||||||
return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<LoanScheduleAccrualData> retrievePeriodicAccrualDataForChargeSubmittedDateProcessing(final LocalDate tillDate,
|
|
||||||
final Loan loan) {
|
|
||||||
LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper();
|
|
||||||
LocalDate organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("select ").append(mapper.schema()).append(
|
|
||||||
" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = false) ")
|
|
||||||
.append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))")
|
|
||||||
.append(" or (ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))")
|
|
||||||
.append(" or (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))")
|
|
||||||
.append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or loan.closedon_date is null)")
|
|
||||||
.append(" and loan.is_npa=false and loan.is_charged_off = false)");
|
|
||||||
Map<String, Object> paramMap = new HashMap<>(5);
|
|
||||||
if (organisationStartDate != null) {
|
|
||||||
sqlBuilder.append(" and ls.duedate > :organisationStartDate ");
|
|
||||||
paramMap.put("organisationStartDate", organisationStartDate);
|
|
||||||
}
|
|
||||||
if (loan != null) {
|
|
||||||
sqlBuilder.append(" and loan.id= :loanId ");
|
|
||||||
paramMap.put("loanId", loan.getId());
|
|
||||||
}
|
|
||||||
sqlBuilder.append(" order by loan.id,ls.duedate ");
|
|
||||||
paramMap.put("active", LoanStatus.ACTIVE.getValue());
|
|
||||||
paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue());
|
|
||||||
paramMap.put("tillDate", tillDate);
|
|
||||||
return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanSchedulePeriodicAccrualMapper implements RowMapper<LoanScheduleAccrualData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("loan.id as loanId , (CASE WHEN loan.client_id is null THEN mg.office_id ELSE mc.office_id END) as officeId,")
|
|
||||||
.append("loan.accrued_till as accruedTill, loan.repayment_period_frequency_enum as frequencyEnum, ")
|
|
||||||
.append("loan.interest_calculated_from_date as interestCalculatedFrom, ").append("loan.repay_every as repayEvery,")
|
|
||||||
.append("ls.installment as installmentNumber, ")
|
|
||||||
.append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,")
|
|
||||||
.append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
|
|
||||||
.append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
|
|
||||||
.append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
|
|
||||||
.append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
|
|
||||||
.append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
|
|
||||||
.append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
|
|
||||||
.append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ")
|
|
||||||
.append(" left join m_product_loan mpl on mpl.id = loan.product_id")
|
|
||||||
.append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id")
|
|
||||||
.append(" left join m_currency curr on curr.code = loan.currency_code")
|
|
||||||
.append(" left join m_loan_recalculation_details as recaldet on loan.id = recaldet.loan_id ");
|
|
||||||
return sqlBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Long loanId = rs.getLong("loanId");
|
|
||||||
final Long officeId = rs.getLong("officeId");
|
|
||||||
final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill");
|
|
||||||
final LocalDate interestCalculatedFrom = JdbcSupport.getLocalDate(rs, "interestCalculatedFrom");
|
|
||||||
final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
|
|
||||||
|
|
||||||
final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum");
|
|
||||||
final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery");
|
|
||||||
final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum);
|
|
||||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
|
|
||||||
final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromdate");
|
|
||||||
final Long repaymentScheduleId = rs.getLong("scheduleId");
|
|
||||||
final Long loanProductId = rs.getLong("productId");
|
|
||||||
final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
|
|
||||||
final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
|
|
||||||
final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
|
|
||||||
final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
|
|
||||||
final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
|
|
||||||
final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
|
|
||||||
final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
|
|
||||||
final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
|
|
||||||
final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
|
|
||||||
|
|
||||||
final String currencyCode = rs.getString("currencyCode");
|
|
||||||
final String currencyName = rs.getString("currencyName");
|
|
||||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
|
||||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
|
||||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
|
||||||
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
|
||||||
final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
|
|
||||||
currencyDisplaySymbol, currencyNameCode);
|
|
||||||
|
|
||||||
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromDate,
|
|
||||||
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
|
|
||||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanScheduleAccrualMapper implements RowMapper<LoanScheduleAccrualData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
|
||||||
sqlBuilder.append("loan.id as loanId, (CASE WHEN loan.client_id is null THEN mg.office_id ELSE mc.office_id END) as officeId,")
|
|
||||||
.append("ls.duedate as duedate,ls.fromdate as fromdate,ls.id as scheduleId,loan.product_id as productId,")
|
|
||||||
.append("ls.installment as installmentNumber, ")
|
|
||||||
.append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,")
|
|
||||||
.append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ")
|
|
||||||
.append("ls.credited_penalty as credited_penalty, ").append("ls.credited_fee as credited_fee, ")
|
|
||||||
.append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,")
|
|
||||||
.append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,")
|
|
||||||
.append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode")
|
|
||||||
.append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ")
|
|
||||||
.append(" left join m_product_loan mpl on mpl.id = loan.product_id")
|
|
||||||
.append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id")
|
|
||||||
.append(" left join m_currency curr on curr.code = loan.currency_code")
|
|
||||||
.append(" left join m_loan_recalculation_details as recaldet on loan.id = recaldet.loan_id ");
|
|
||||||
return sqlBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanScheduleAccrualData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Long loanId = rs.getLong("loanId");
|
|
||||||
final Long officeId = rs.getLong("officeId");
|
|
||||||
final Integer installmentNumber = JdbcSupport.getInteger(rs, "installmentNumber");
|
|
||||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
|
|
||||||
final LocalDate fromdate = JdbcSupport.getLocalDate(rs, "fromdate");
|
|
||||||
final Long repaymentScheduleId = rs.getLong("scheduleId");
|
|
||||||
final Long loanProductId = rs.getLong("productId");
|
|
||||||
final BigDecimal interestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interest");
|
|
||||||
final BigDecimal feeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "charges");
|
|
||||||
final BigDecimal penaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "penalty");
|
|
||||||
final BigDecimal creditedFee = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_fee");
|
|
||||||
final BigDecimal creditedPenalty = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "credited_penalty");
|
|
||||||
|
|
||||||
final BigDecimal interestIncomeWaived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "interestWaived");
|
|
||||||
final BigDecimal accruedInterestIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accinterest");
|
|
||||||
final BigDecimal accruedFeeIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accfeecharege");
|
|
||||||
final BigDecimal accruedPenaltyIncome = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accpenalty");
|
|
||||||
|
|
||||||
final String currencyCode = rs.getString("currencyCode");
|
|
||||||
final String currencyName = rs.getString("currencyName");
|
|
||||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
|
||||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
|
||||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
|
||||||
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
|
||||||
final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
|
|
||||||
currencyDisplaySymbol, currencyNameCode);
|
|
||||||
final LocalDate accruedTill = null;
|
|
||||||
final PeriodFrequencyType frequency = null;
|
|
||||||
final Integer repayEvery = null;
|
|
||||||
final LocalDate interestCalculatedFrom = null;
|
|
||||||
return new LoanScheduleAccrualData(loanId, officeId, installmentNumber, accruedTill, frequency, repayEvery, dueDate, fromdate,
|
|
||||||
repaymentScheduleId, loanProductId, interestIncome, feeIncome, penaltyIncome, accruedInterestIncome, accruedFeeIncome,
|
|
||||||
accruedPenaltyIncome, currencyData, interestCalculatedFrom, interestIncomeWaived, creditedFee, creditedPenalty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId) {
|
public LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId) {
|
||||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||||
@@ -2146,155 +1889,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanTransactionData> retrieveWaiverLoanTransactions(final Long loanId) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
final LoanTransactionDerivedComponentMapper rm = new LoanTransactionDerivedComponentMapper(sqlGenerator);
|
|
||||||
|
|
||||||
final String sql = "select " + rm.schema()
|
|
||||||
+ " where tr.loan_id = ? and tr.transaction_type_enum = ? and tr.is_reversed=false order by tr.transaction_date, tr.created_on_utc, tr.id ";
|
|
||||||
return this.jdbcTemplate.query(sql, rm, loanId, LoanTransactionType.WAIVE_INTEREST.getValue()); // NOSONAR
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isGuaranteeRequired(final Long loanId) {
|
public boolean isGuaranteeRequired(final Long loanId) {
|
||||||
final String sql = "select pl.hold_guarantee_funds from m_loan ml inner join m_product_loan pl on pl.id = ml.product_id where ml.id=?";
|
final String sql = "select pl.hold_guarantee_funds from m_loan ml inner join m_product_loan pl on pl.id = ml.product_id where ml.id=?";
|
||||||
return TRUE.equals(this.jdbcTemplate.queryForObject(sql, Boolean.class, loanId));
|
return TRUE.equals(this.jdbcTemplate.queryForObject(sql, Boolean.class, loanId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class LoanTransactionDerivedComponentMapper implements RowMapper<LoanTransactionData> {
|
|
||||||
|
|
||||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
|
||||||
|
|
||||||
LoanTransactionDerivedComponentMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
|
||||||
this.sqlGenerator = sqlGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
|
|
||||||
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as " + sqlGenerator.escape("date")
|
|
||||||
+ ", tr.amount as total, tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, "
|
|
||||||
+ " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, "
|
|
||||||
+ " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, "
|
|
||||||
+ " tr.unrecognized_income_portion as unrecognizedIncome, tr.loan_id as loanId, l.external_id as externalLoanId, "
|
|
||||||
+ " tr.external_id as externalId from m_loan_transaction tr " + " left join m_loan l on tr.loan_id = l.id";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Long id = rs.getLong("id");
|
|
||||||
final Long loanId = rs.getLong("loanId");
|
|
||||||
final String externalLoanIdStr = rs.getString("externalLoanId");
|
|
||||||
final ExternalId externalLoanId = ExternalIdFactory.produce(externalLoanIdStr);
|
|
||||||
final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
|
|
||||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(transactionTypeInt);
|
|
||||||
|
|
||||||
final LocalDate date = JdbcSupport.getLocalDate(rs, "date");
|
|
||||||
final BigDecimal totalAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "total");
|
|
||||||
final BigDecimal principalPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principal");
|
|
||||||
final BigDecimal interestPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interest");
|
|
||||||
final BigDecimal feeChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fees");
|
|
||||||
final BigDecimal penaltyChargesPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalties");
|
|
||||||
final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment");
|
|
||||||
final BigDecimal unrecognizedIncomePortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome");
|
|
||||||
final BigDecimal outstandingLoanBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance");
|
|
||||||
final String externalIdStr = rs.getString("externalId");
|
|
||||||
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
|
|
||||||
|
|
||||||
return new LoanTransactionData(id, transactionType, date, totalAmount, null, principalPortion, interestPortion,
|
|
||||||
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, unrecognizedIncomePortion, outstandingLoanBalance, false,
|
|
||||||
externalId, loanId, externalLoanId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LoanSchedulePeriodData> fetchWaiverInterestRepaymentData(final Long loanId) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
final LoanRepaymentWaiverMapper rm = new LoanRepaymentWaiverMapper();
|
|
||||||
|
|
||||||
final String sql = "select " + rm.getSchema()
|
|
||||||
+ " where lrs.loan_id = ? and lrs.interest_waived_derived is not null order by lrs.installment ASC ";
|
|
||||||
return this.jdbcTemplate.query(sql, rm, loanId); // NOSONAR
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LoanRepaymentWaiverMapper implements RowMapper<LoanSchedulePeriodData> {
|
|
||||||
|
|
||||||
private final String sqlSchema;
|
|
||||||
|
|
||||||
public String getSchema() {
|
|
||||||
return this.sqlSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoanRepaymentWaiverMapper() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("lrs.duedate as dueDate,lrs.interest_waived_derived interestWaived, lrs.installment as installment");
|
|
||||||
sb.append(" from m_loan_repayment_schedule lrs ");
|
|
||||||
sqlSchema = sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
|
|
||||||
|
|
||||||
final Integer period = JdbcSupport.getInteger(rs, "installment");
|
|
||||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
|
|
||||||
final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
|
|
||||||
|
|
||||||
final LocalDate fromDate = null;
|
|
||||||
final LocalDate obligationsMetOnDate = null;
|
|
||||||
final Boolean complete = false;
|
|
||||||
final BigDecimal principalOriginalDue = null;
|
|
||||||
final BigDecimal principalPaid = null;
|
|
||||||
final BigDecimal principalWrittenOff = null;
|
|
||||||
final BigDecimal principalOutstanding = null;
|
|
||||||
final BigDecimal interestPaid = null;
|
|
||||||
final BigDecimal interestWrittenOff = null;
|
|
||||||
final BigDecimal interestOutstanding = null;
|
|
||||||
final BigDecimal feeChargesDue = null;
|
|
||||||
final BigDecimal feeChargesPaid = null;
|
|
||||||
final BigDecimal feeChargesWaived = null;
|
|
||||||
final BigDecimal feeChargesWrittenOff = null;
|
|
||||||
final BigDecimal feeChargesOutstanding = null;
|
|
||||||
final BigDecimal penaltyChargesDue = null;
|
|
||||||
final BigDecimal penaltyChargesPaid = null;
|
|
||||||
final BigDecimal penaltyChargesWaived = null;
|
|
||||||
final BigDecimal penaltyChargesWrittenOff = null;
|
|
||||||
final BigDecimal penaltyChargesOutstanding = null;
|
|
||||||
|
|
||||||
final BigDecimal totalDueForPeriod = null;
|
|
||||||
final BigDecimal totalPaidInAdvanceForPeriod = null;
|
|
||||||
final BigDecimal totalPaidLateForPeriod = null;
|
|
||||||
final BigDecimal totalActualCostOfLoanForPeriod = null;
|
|
||||||
final BigDecimal outstandingPrincipalBalanceOfLoan = null;
|
|
||||||
final BigDecimal interestDueOnPrincipalOutstanding = null;
|
|
||||||
Long loanId = null;
|
|
||||||
final BigDecimal totalWaived = null;
|
|
||||||
final BigDecimal totalWrittenOff = null;
|
|
||||||
final BigDecimal totalOutstanding = null;
|
|
||||||
final BigDecimal totalPaid = null;
|
|
||||||
final BigDecimal totalInstallmentAmount = null;
|
|
||||||
final BigDecimal totalCredits = null;
|
|
||||||
final BigDecimal totalAccruedInterest = null;
|
|
||||||
|
|
||||||
return LoanSchedulePeriodData.periodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete,
|
|
||||||
principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
|
|
||||||
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue,
|
|
||||||
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid,
|
|
||||||
penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid,
|
|
||||||
totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
|
|
||||||
totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false, totalAccruedInterest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalDate retrieveMinimumDateOfRepaymentTransaction(Long loanId) {
|
public LocalDate retrieveMinimumDateOfRepaymentTransaction(Long loanId) {
|
||||||
return this.jdbcTemplate.queryForObject(
|
return this.jdbcTemplate.queryForObject(
|
||||||
|
|||||||
+13
-8
@@ -25,6 +25,7 @@ import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
|||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -47,12 +48,15 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
|||||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||||
final Loan loan = event.get();
|
final Loan loan = event.get();
|
||||||
log.debug("Loan Status change for loan {}", loan.getId());
|
log.debug("Loan Status change for loan {}", loan.getId());
|
||||||
if (loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid()) {
|
LoanStatus oldStatus = event.getOldStatus();
|
||||||
log.debug("Loan Status {} for loan {}", loan.getStatus().getCode(), loan.getId());
|
LoanStatus newStatus = loan.getStatus();
|
||||||
loanAccrualsProcessingService.processAccrualsForLoanClosure(loan);
|
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||||
}
|
log.debug("Loan Status {} for loan {}", newStatus.getCode(), loan.getId());
|
||||||
if (loan.isOpen()) {
|
loan.updateLoanSummaryDerivedFields();
|
||||||
|
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||||
|
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||||
loan.handleMaturityDateActivate();
|
loan.handleMaturityDateActivate();
|
||||||
|
loanAccrualsProcessingService.processAccrualsOnLoanReopen(loan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,10 +68,11 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
|||||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||||
final Loan loan = event.get();
|
final Loan loan = event.get();
|
||||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||||
if (loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid()) {
|
LoanStatus oldStatus = event.getOldStatus();
|
||||||
|
LoanStatus newStatus = loan.getStatus();
|
||||||
|
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||||
}
|
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||||
if (loan.isOpen()) {
|
|
||||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan);
|
loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-25
@@ -597,15 +597,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||||
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
|
changedTransactionDetail = loan.reprocessTransactions();
|
||||||
.determineProcessor(loan.getTransactionProcessingStrategyCode());
|
|
||||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(),
|
|
||||||
allNonContraTransactionsPostDisbursement, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
|
||||||
loan.getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(loan);
|
|
||||||
}
|
|
||||||
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
|
||||||
}
|
}
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loan.updateLoanSummaryDerivedFields();
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1015,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
// FIXME - kw - update account balance to negative amount.
|
// FIXME - kw - update account balance to negative amount.
|
||||||
handleLoanOverpayment(loan, transactionDate, loanLifecycleStateMachine);
|
handleLoanOverpayment(loan, transactionDate, loanLifecycleStateMachine);
|
||||||
statusChanged = true;
|
statusChanged = true;
|
||||||
} else if (loan.getSummary().isRepaidInFull(loan.loanCurrency())) {
|
} else if (loan.getSummary().isRepaidInFull(loan.getCurrency())) {
|
||||||
handleLoanRepaymentInFull(loan, transactionDate, loanLifecycleStateMachine);
|
handleLoanRepaymentInFull(loan, transactionDate, loanLifecycleStateMachine);
|
||||||
statusChanged = true;
|
statusChanged = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -1077,7 +1069,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final boolean isTransactionChronologicallyLatest = loan
|
final boolean isTransactionChronologicallyLatest = loan
|
||||||
.isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
|
.isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
|
||||||
|
|
||||||
if (newInterestPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) {
|
if (newInterestPaymentWaiverTransaction.isNotZero()) {
|
||||||
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,19 +1125,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
return loan.reprocessTransactions();
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
|
||||||
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
|
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
mapEntry.getValue().updateLoan(loan);
|
|
||||||
}
|
|
||||||
/***
|
|
||||||
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
|
||||||
* the reversed transactions first and then new transactions.
|
|
||||||
*/
|
|
||||||
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
|
||||||
return changedTransactionDetail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -1503,7 +1483,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean thereIsNewTransaction = newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency());
|
boolean thereIsNewTransaction = newTransactionDetail.isGreaterThanZero();
|
||||||
if (thereIsNewTransaction) {
|
if (thereIsNewTransaction) {
|
||||||
if (paymentDetail != null) {
|
if (paymentDetail != null) {
|
||||||
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
|
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
|
||||||
|
|||||||
+3
-2
@@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
@@ -60,7 +61,7 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
|
|||||||
List<LoanTransaction> collect) {
|
List<LoanTransaction> collect) {
|
||||||
collect.add(new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf().getValue(), lt.getDateOf(), lt.getAmount(),
|
collect.add(new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf().getValue(), lt.getDateOf(), lt.getAmount(),
|
||||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
|
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
|
||||||
if (lt.getTypeOf().isDisbursement() && refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
|
if (lt.getTypeOf().isDisbursement() && MathUtil.isGreaterThanZero(refundFinal.get())) {
|
||||||
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
|
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
|
||||||
collect.add(
|
collect.add(
|
||||||
new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), REPAYMENT.getValue(), lt.getDateOf(), lt.getAmount(),
|
new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), REPAYMENT.getValue(), lt.getDateOf(), lt.getAmount(),
|
||||||
@@ -97,7 +98,7 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTransactionNeededForInterestRefundCalculations(LoanTransaction lt) {
|
private boolean isTransactionNeededForInterestRefundCalculations(LoanTransaction lt) {
|
||||||
return lt.isNotReversed() && !lt.isAccrual() && !lt.isAccrualActivity() && !lt.isInterestRefund();
|
return lt.isNotReversed() && !lt.isAccrualRelated() && !lt.isInterestRefund();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+1
-12
@@ -24,7 +24,6 @@ import java.math.BigDecimal;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
@@ -122,8 +121,7 @@ public class LoanReAgingServiceImpl {
|
|||||||
}
|
}
|
||||||
reverseReAgeTransaction(reAgeTransaction, command);
|
reverseReAgeTransaction(reAgeTransaction, command);
|
||||||
loanTransactionRepository.saveAndFlush(reAgeTransaction);
|
loanTransactionRepository.saveAndFlush(reAgeTransaction);
|
||||||
|
loan.reprocessTransactions();
|
||||||
reProcessLoanTransactions(reAgeTransaction.getLoan());
|
|
||||||
loan.updateLoanScheduleDependentDerivedFields();
|
loan.updateLoanScheduleDependentDerivedFields();
|
||||||
persistNote(loan, command, changes);
|
persistNote(loan, command, changes);
|
||||||
|
|
||||||
@@ -179,15 +177,6 @@ public class LoanReAgingServiceImpl {
|
|||||||
return new LoanReAgeParameter(reAgeTransaction, periodFrequencyType, periodFrequencyNumber, startDate, numberOfInstallments);
|
return new LoanReAgeParameter(reAgeTransaction, periodFrequencyType, periodFrequencyNumber, startDate, numberOfInstallments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reProcessLoanTransactions(Loan loan) {
|
|
||||||
final List<LoanTransaction> filteredTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(), filteredTransactions,
|
|
||||||
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistNote(Loan loan, JsonCommand command, Map<String, Object> changes) {
|
private void persistNote(Loan loan, JsonCommand command, Map<String, Object> changes) {
|
||||||
if (command.hasParameter("note")) {
|
if (command.hasParameter("note")) {
|
||||||
final String note = command.stringValueOfParameterNamed("note");
|
final String note = command.stringValueOfParameterNamed("note");
|
||||||
|
|||||||
+1
-11
@@ -24,7 +24,6 @@ import java.math.BigDecimal;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
@@ -129,16 +128,7 @@ public class LoanReAmortizationServiceImpl {
|
|||||||
LoanReAmortizationApiConstants.externalIdParameterName);
|
LoanReAmortizationApiConstants.externalIdParameterName);
|
||||||
reAmortizeTransaction.reverse(reversalExternalId);
|
reAmortizeTransaction.reverse(reversalExternalId);
|
||||||
reAmortizeTransaction.manuallyAdjustedOrReversed();
|
reAmortizeTransaction.manuallyAdjustedOrReversed();
|
||||||
reProcessLoanTransactions(reAmortizeTransaction.getLoan());
|
reAmortizeTransaction.getLoan().reprocessTransactions();
|
||||||
}
|
|
||||||
|
|
||||||
private void reProcessLoanTransactions(Loan loan) {
|
|
||||||
final List<LoanTransaction> filteredTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
|
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(), filteredTransactions,
|
|
||||||
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanTransaction findLatestNonReversedReAmortizeTransaction(Loan loan) {
|
private LoanTransaction findLatestNonReversedReAmortizeTransaction(Loan loan) {
|
||||||
|
|||||||
+3
-2
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.starter;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
|
||||||
@@ -107,8 +108,8 @@ public class LoanAccountAutoStarter {
|
|||||||
@Bean
|
@Bean
|
||||||
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
|
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
|
||||||
|
LoanRepositoryWrapper loanRepositoryWrapper,
|
||||||
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService) {
|
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService) {
|
||||||
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, progressiveLoanInterestRefundService);
|
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -102,6 +102,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePla
|
|||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingServiceImpl;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
|
||||||
@@ -217,7 +218,7 @@ public class LoanAccountConfiguration {
|
|||||||
LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService,
|
LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService,
|
||||||
LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler, LoanChargeMapper loanChargeMapper,
|
LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler, LoanChargeMapper loanChargeMapper,
|
||||||
LoanCollateralManagementMapper loanCollateralManagementMapper, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
LoanCollateralManagementMapper loanCollateralManagementMapper, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||||
return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository,
|
return new LoanAssemblerImpl(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository,
|
||||||
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
|
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
|
||||||
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
|
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
|
||||||
workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
|
workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
|
||||||
|
|||||||
+2
-2
@@ -105,7 +105,7 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
||||||
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
||||||
"LoanTransactionInterestRefundPreBusinessEvent");
|
"LoanTransactionInterestRefundPreBusinessEvent", "LoanAccrualAdjustmentTransactionBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
@@ -191,7 +191,7 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
||||||
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
||||||
"LoanTransactionInterestRefundPreBusinessEvent");
|
"LoanTransactionInterestRefundPreBusinessEvent", "LoanAccrualAdjustmentTransactionBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
|
|||||||
+10
-3
@@ -177,13 +177,20 @@ public class LoanTest {
|
|||||||
|
|
||||||
final LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
final LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
when(loanTransaction.isNotReversed()).thenReturn(Boolean.TRUE);
|
when(loanTransaction.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
when(loanTransaction.isAccrualTransaction()).thenReturn(Boolean.FALSE);
|
when(loanTransaction.isAccrual()).thenReturn(Boolean.FALSE);
|
||||||
|
when(loanTransaction.isAccrualAdjustment()).thenReturn(Boolean.FALSE);
|
||||||
final LoanTransaction loanTransaction2 = Mockito.mock(LoanTransaction.class);
|
final LoanTransaction loanTransaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
when(loanTransaction2.isNotReversed()).thenReturn(Boolean.TRUE);
|
when(loanTransaction2.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
when(loanTransaction2.isAccrualTransaction()).thenReturn(Boolean.FALSE);
|
when(loanTransaction2.isAccrual()).thenReturn(Boolean.FALSE);
|
||||||
|
when(loanTransaction2.isAccrualAdjustment()).thenReturn(Boolean.FALSE);
|
||||||
final LoanTransaction loanTransaction3 = Mockito.mock(LoanTransaction.class);
|
final LoanTransaction loanTransaction3 = Mockito.mock(LoanTransaction.class);
|
||||||
when(loanTransaction3.isNotReversed()).thenReturn(Boolean.TRUE);
|
when(loanTransaction3.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
when(loanTransaction3.isAccrualTransaction()).thenReturn(Boolean.TRUE);
|
when(loanTransaction3.isAccrual()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction3.isAccrualAdjustment()).thenReturn(Boolean.FALSE);
|
||||||
|
final LoanTransaction loanTransaction4 = Mockito.mock(LoanTransaction.class);
|
||||||
|
when(loanTransaction4.isNotReversed()).thenReturn(Boolean.TRUE);
|
||||||
|
when(loanTransaction4.isAccrual()).thenReturn(Boolean.FALSE);
|
||||||
|
when(loanTransaction4.isAccrualAdjustment()).thenReturn(Boolean.TRUE);
|
||||||
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction, loanTransaction2, loanTransaction3));
|
ReflectionTestUtils.setField(loan, "loanTransactions", List.of(loanTransaction, loanTransaction2, loanTransaction3));
|
||||||
final LoanTransaction userTransaction = loan.getLastUserTransaction();
|
final LoanTransaction userTransaction = loan.getLastUserTransaction();
|
||||||
assertNotNull(userTransaction);
|
assertNotNull(userTransaction);
|
||||||
|
|||||||
+2
-1
@@ -5145,7 +5145,8 @@ public class ClientLoanIntegrationTest extends BaseLoanIntegrationTest {
|
|||||||
for (int i = 1; i < loanSchedule.size(); i++) {
|
for (int i = 1; i < loanSchedule.size(); i++) {
|
||||||
|
|
||||||
retrieveDueDate = dateFormat.format(repaymentDate.getTime());
|
retrieveDueDate = dateFormat.format(repaymentDate.getTime());
|
||||||
amount = (Float) loanSchedule.get(i).get("principalOriginalDue") + (Float) loanSchedule.get(i).get("interestOriginalDue");
|
amount = ((Number) loanSchedule.get(i).get("principalOriginalDue")).floatValue()
|
||||||
|
+ ((Number) loanSchedule.get(i).get("interestOriginalDue")).floatValue();
|
||||||
if (currentDate.after(repaymentDate)) {
|
if (currentDate.after(repaymentDate)) {
|
||||||
LOAN_TRANSACTION_HELPER.makeRepayment(retrieveDueDate, amount, loanID);
|
LOAN_TRANSACTION_HELPER.makeRepayment(retrieveDueDate, amount, loanID);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
-4
@@ -300,7 +300,6 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
|||||||
transaction(312.0, "Repayment", "20 February 2023", 626.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
transaction(312.0, "Repayment", "20 February 2023", 626.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
transaction(312.0, "Repayment", "20 March 2023", 314.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
transaction(312.0, "Repayment", "20 March 2023", 314.0, 312.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
transaction(384.0, "Repayment", "20 April 2023", 0.0, 314.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
transaction(384.0, "Repayment", "20 April 2023", 0.0, 314.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||||
transaction(70.0, "Accrual", "20 April 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
|
||||||
transaction(100.0, "Chargeback", "02 May 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
transaction(100.0, "Chargeback", "02 May 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -585,7 +584,6 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
|||||||
verifyTransactions(loanId, //
|
verifyTransactions(loanId, //
|
||||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
||||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
|
||||||
transaction(100.0, "Chargeback", "02 May 2023", 50.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
|
transaction(100.0, "Chargeback", "02 May 2023", 50.0, 100.0, 0.0, 0.0, 0.0, 0.0, 50.0) //
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -651,7 +649,6 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
|||||||
verifyTransactions(loanId, //
|
verifyTransactions(loanId, //
|
||||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
||||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
|
||||||
transaction(100.0, "Chargeback", "20 January 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
transaction(100.0, "Chargeback", "20 January 2023", 30.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -718,7 +715,6 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
|||||||
verifyTransactions(loanId, //
|
verifyTransactions(loanId, //
|
||||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||||
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
transaction(1370.0, "Repayment", "20 January 2023", 0, 1250.0, 0.0, 50.0, 20.0, 0.0, 50.0), //
|
||||||
transaction(70.0, "Accrual", "20 January 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
|
||||||
transaction(100.0, "Chargeback", "20 January 2023", 0.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
transaction(100.0, "Chargeback", "20 January 2023", 0.0, 30.0, 0.0, 50.0, 20.0, 0.0, 50.0) //
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+16
-8
@@ -317,7 +317,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
logLoanTransactions(loanId);
|
logLoanTransactions(loanId);
|
||||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
transaction(87.89, "Repayment", "01 February 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"),
|
transaction(87.89, "Repayment", "01 February 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"),
|
||||||
transaction(10.49, "Interest Refund", "09 February 2021"));
|
transaction(10.49, "Interest Refund", "09 February 2021"), transaction(10.49, "Accrual", "09 February 2021"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +437,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
logLoanTransactions(loanId);
|
logLoanTransactions(loanId);
|
||||||
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"),
|
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"),
|
||||||
transaction(500.0, "Disbursement", "07 January 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"),
|
transaction(500.0, "Disbursement", "07 January 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"),
|
||||||
transaction(87.82, "Repayment", "01 February 2021"), transaction(9.67, "Interest Refund", "09 February 2021"));
|
transaction(87.82, "Repayment", "01 February 2021"), transaction(9.67, "Interest Refund", "09 February 2021"),
|
||||||
|
transaction(9.67, "Accrual", "09 February 2021"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +635,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
transaction(171.29, "Repayment", "01 April 2021"), //
|
transaction(171.29, "Repayment", "01 April 2021"), //
|
||||||
transaction(171.29, "Repayment", "01 May 2021"), //
|
transaction(171.29, "Repayment", "01 May 2021"), //
|
||||||
transaction(171.29, "Repayment", "01 June 2021"), //
|
transaction(171.29, "Repayment", "01 June 2021"), //
|
||||||
transaction(171.32, "Repayment", "01 July 2021") //
|
transaction(171.32, "Repayment", "01 July 2021"), //
|
||||||
|
transaction(27.77, "Accrual", "01 July 2021") //
|
||||||
); //
|
); //
|
||||||
});
|
});
|
||||||
runAt("11 July 2021", () -> {
|
runAt("11 July 2021", () -> {
|
||||||
@@ -654,7 +656,9 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
transaction(171.29, "Repayment", "01 June 2021"), //
|
transaction(171.29, "Repayment", "01 June 2021"), //
|
||||||
transaction(171.32, "Repayment", "01 July 2021"), //
|
transaction(171.32, "Repayment", "01 July 2021"), //
|
||||||
transaction(500.0, "Payout Refund", "11 July 2021"), //
|
transaction(500.0, "Payout Refund", "11 July 2021"), //
|
||||||
transaction(20.41, "Interest Refund", "11 July 2021")); //
|
transaction(20.41, "Interest Refund", "11 July 2021"), //
|
||||||
|
transaction(27.77, "Accrual", "01 July 2021") //
|
||||||
|
); //
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +696,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
transaction(500.0, "Merchant Issued Refund", "14 January 2021"), //
|
transaction(500.0, "Merchant Issued Refund", "14 January 2021"), //
|
||||||
transaction(1.78, "Interest Refund", "14 January 2021"), //
|
transaction(1.78, "Interest Refund", "14 January 2021"), //
|
||||||
transaction(500.0, "Payout Refund", "22 January 2021"), //
|
transaction(500.0, "Payout Refund", "22 January 2021"), //
|
||||||
transaction(2.88, "Interest Refund", "22 January 2021") //
|
transaction(2.88, "Interest Refund", "22 January 2021"), //
|
||||||
|
transaction(4.66, "Accrual", "22 January 2021") //
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -992,7 +997,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
transaction(250.0, "Payout Refund", "13 February 2021"), //
|
transaction(250.0, "Payout Refund", "13 February 2021"), //
|
||||||
transaction(2.96, "Interest Refund", "13 February 2021"), //
|
transaction(2.96, "Interest Refund", "13 February 2021"), //
|
||||||
transaction(400.0, "Merchant Issued Refund", "06 April 2021"), //
|
transaction(400.0, "Merchant Issued Refund", "06 April 2021"), //
|
||||||
transaction(10.11, "Interest Refund", "06 April 2021") //
|
transaction(10.11, "Interest Refund", "06 April 2021"), //
|
||||||
|
transaction(17.14, "Accrual", "06 April 2021") //
|
||||||
);
|
);
|
||||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
|
||||||
@@ -1109,6 +1115,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||||
transaction(85.63, "Repayment", "10 January 2021"), //
|
transaction(85.63, "Repayment", "10 January 2021"), //
|
||||||
|
transaction(5.7, "Accrual", "22 January 2021"), //
|
||||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
||||||
transaction(5.42, "Interest Refund", "22 January 2021") //
|
transaction(5.42, "Interest Refund", "22 January 2021") //
|
||||||
);
|
);
|
||||||
@@ -1183,7 +1190,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||||
transaction(85.63, "Repayment", "10 January 2021"), //
|
transaction(85.63, "Repayment", "10 January 2021"), //
|
||||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
||||||
transaction(5.42, "Interest Refund", "22 January 2021") //
|
transaction(5.42, "Interest Refund", "22 January 2021"), //
|
||||||
|
transaction(5.42, "Accrual", "22 January 2021") //
|
||||||
);
|
);
|
||||||
|
|
||||||
Long repaymentId = repaymentIdRef.get();
|
Long repaymentId = repaymentIdRef.get();
|
||||||
@@ -1194,7 +1202,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
reversedTransaction(85.63, "Repayment", "10 January 2021"), //
|
reversedTransaction(85.63, "Repayment", "10 January 2021"), //
|
||||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
||||||
transaction(5.70, "Interest Refund", "22 January 2021"), //
|
transaction(5.70, "Interest Refund", "22 January 2021"), //
|
||||||
transaction(5.70, "Accrual", "10 January 2021") //
|
transaction(5.42, "Accrual", "22 January 2021") //
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-7
@@ -569,7 +569,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
verifyTransactions(loanId.get(),
|
verifyTransactions(loanId.get(),
|
||||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
|
||||||
transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, true), //
|
transaction(1070.0, "Repayment", repaymentDate1, 0.0, 1000.0, 0.0, 40.0, 30, 0.0, 0.0, true), //
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
|
||||||
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); //
|
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false)); //
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -665,7 +664,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en"));
|
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en"));
|
||||||
|
|
||||||
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
|
||||||
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
||||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
||||||
|
|
||||||
@@ -675,8 +673,8 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||||
|
|
||||||
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
|
||||||
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
||||||
|
transaction(70.0, "Accrual", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
||||||
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
||||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
||||||
|
|
||||||
@@ -728,7 +726,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en"));
|
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(500.0)).locale("en"));
|
||||||
|
|
||||||
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), //
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
|
||||||
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), //
|
||||||
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), //
|
||||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
||||||
@@ -793,6 +790,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
final String disbursementDay2 = "02 February 2023";
|
final String disbursementDay2 = "02 February 2023";
|
||||||
final String repaymentDate1 = "15 January 2023";
|
final String repaymentDate1 = "15 January 2023";
|
||||||
final String repaymentPeriod1DueDate = "01 February 2023";
|
final String repaymentPeriod1DueDate = "01 February 2023";
|
||||||
|
final String repaymentPeriod2DueDate = "01 March 2023";
|
||||||
final String repaymentPeriod1CloseDate = "02 February 2023";
|
final String repaymentPeriod1CloseDate = "02 February 2023";
|
||||||
final String repaymentPeriod2CloseDate = "02 March 2023";
|
final String repaymentPeriod2CloseDate = "02 March 2023";
|
||||||
final String creationBusinessDay = "15 January 2023";
|
final String creationBusinessDay = "15 January 2023";
|
||||||
@@ -830,7 +828,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
|
|
||||||
verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false),
|
verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false),
|
||||||
transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false),
|
transaction(570, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false),
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
|
||||||
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
||||||
transaction(500.0, "Disbursement", disbursementDay2, 500, 0, 0, 0, 0, 0, 0, false));
|
transaction(500.0, "Disbursement", disbursementDay2, 500, 0, 0, 0, 0, 0, 0, false));
|
||||||
});
|
});
|
||||||
@@ -839,10 +836,10 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||||
|
|
||||||
verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false),
|
verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false),
|
||||||
transaction(70.0, "Accrual", repaymentDate1, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
|
||||||
transaction(570.0, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false),
|
transaction(570.0, "Repayment", repaymentDate1, 0.0, 500.0, 0, 40.0, 30.0, 0, 0, false),
|
||||||
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false),
|
||||||
transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false));
|
transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false),
|
||||||
|
transaction(70.0, "Accrual", repaymentPeriod2DueDate, 0, 0, 0.0, 40.0, 30.0, 0, 0, false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -465,6 +465,11 @@ public class ExternalEventConfigurationHelper {
|
|||||||
loanAccrualTransactionCreatedBusinessEvent.put("enabled", false);
|
loanAccrualTransactionCreatedBusinessEvent.put("enabled", false);
|
||||||
defaults.add(loanAccrualTransactionCreatedBusinessEvent);
|
defaults.add(loanAccrualTransactionCreatedBusinessEvent);
|
||||||
|
|
||||||
|
Map<String, Object> loanAccrualAdjustmentTransactionBusinessEvent = new HashMap<>();
|
||||||
|
loanAccrualAdjustmentTransactionBusinessEvent.put("type", "LoanAccrualAdjustmentTransactionBusinessEvent");
|
||||||
|
loanAccrualAdjustmentTransactionBusinessEvent.put("enabled", false);
|
||||||
|
defaults.add(loanAccrualAdjustmentTransactionBusinessEvent);
|
||||||
|
|
||||||
Map<String, Object> loanRescheduledDueAdjustScheduleBusinessEvent = new HashMap<>();
|
Map<String, Object> loanRescheduledDueAdjustScheduleBusinessEvent = new HashMap<>();
|
||||||
loanRescheduledDueAdjustScheduleBusinessEvent.put("type", "LoanRescheduledDueAdjustScheduleBusinessEvent");
|
loanRescheduledDueAdjustScheduleBusinessEvent.put("type", "LoanRescheduledDueAdjustScheduleBusinessEvent");
|
||||||
loanRescheduledDueAdjustScheduleBusinessEvent.put("enabled", false);
|
loanRescheduledDueAdjustScheduleBusinessEvent.put("enabled", false);
|
||||||
@@ -571,7 +576,6 @@ public class ExternalEventConfigurationHelper {
|
|||||||
defaults.add(loanTransactionInterestRefundPreBusinessEvent);
|
defaults.add(loanTransactionInterestRefundPreBusinessEvent);
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getExternalEventConfigurationsForUpdateJSON() {
|
public static String getExternalEventConfigurationsForUpdateJSON() {
|
||||||
|
|||||||
Reference in New Issue
Block a user