FINERACT-2060: Progressive loan - final accrual calculation on closed loan
This commit is contained in:
committed by
Arnold Gálovics
parent
c4cdd6f51a
commit
136bb0009a
+36
-88
@@ -348,9 +348,12 @@ public final class ErrorMessageHelper {
|
||||
}
|
||||
|
||||
public static String wrongLoanStatus(Integer actual, Integer expected) {
|
||||
String actualToStr = actual.toString();
|
||||
String expectedToStr = expected.toString();
|
||||
return String.format("Wrong Loan status ID. Actual ID is: %s - But expected ID is: %s", actualToStr, expectedToStr);
|
||||
return wrongLoanStatus(null, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongLoanStatus(String resourceId, Integer actual, Integer expected) {
|
||||
return String.format("Wrong Loan status ID of resource %s. Actual ID is: %s - But expected ID is: %s", resourceId, actual,
|
||||
expected);
|
||||
}
|
||||
|
||||
public static String wrongFraudFlag(Boolean actualFraudStatus, Boolean expectedFraudStatus) {
|
||||
@@ -446,109 +449,54 @@ public final class ErrorMessageHelper {
|
||||
return String.format("Idempotency key is not matching: Actual value is: %s - But expected value is: %s", actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongNumberOfLinesInRepaymentSchedule(int actual, int expected) {
|
||||
return wrongNumberOfLinesInRepaymentSchedule(null, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongNumberOfLinesInRepaymentSchedule(String resourceId, int actual, int expected) {
|
||||
String actualStr = String.valueOf(actual);
|
||||
String expectedStr = String.valueOf(expected);
|
||||
String prefx = "Number of lines in Repayment schedule";
|
||||
String postfx = " is not correct. Actual value is: %s - Expected value is: %s";
|
||||
if (resourceId != null) {
|
||||
return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr);
|
||||
}
|
||||
return String.format(prefx + postfx, actualStr, expectedStr);
|
||||
return String.format("Number of lines in Repayment schedule of resource %s is not correct. " //
|
||||
+ "Actual value is: %s - But expected value is: %s", resourceId, actual, 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,
|
||||
public static String wrongValueInLineInRepaymentSchedule(String resourceId, int line, List<List<String>> actualList,
|
||||
List<String> expected) {
|
||||
String lineStr = String.valueOf(line);
|
||||
String expectedStr = expected.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (List<String> innerList : actual) {
|
||||
sb.append(innerList.toString());
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
String prefx = "%nWrong value in Repayment schedule";
|
||||
String postfx = " tab line %s. %nActual values in line (with the same due date) are: %n%s %nExpected values in line: %n%s";
|
||||
if (resourceId != null) {
|
||||
return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr);
|
||||
}
|
||||
return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr);
|
||||
String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator()));
|
||||
return String.format("%nWrong value in Repayment schedule of resource %s tab line %s." //
|
||||
+ "%nActual values in line (with the same due date) are: %n%s - But expected values in line: %n%s", resourceId, line,
|
||||
actual, 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 expectedStr = expected.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (List<String> innerList : actual) {
|
||||
sb.append(innerList.toString());
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
String prefx = "%nWrong value in Transactions tab";
|
||||
String postfx = " line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s";
|
||||
if (resourceId != null) {
|
||||
return String.format(prefx + " of resource %s" + postfx, resourceId, lineStr, sb.toString(), expectedStr);
|
||||
}
|
||||
return String.format(prefx + postfx, lineStr, sb.toString(), expectedStr);
|
||||
}
|
||||
|
||||
public static String nrOfLinesWrongInTransactionsTab(int actual, int expected) {
|
||||
return nrOfLinesWrongInTransactionsTab(null, actual, expected);
|
||||
public static String wrongValueInLineInTransactionsTab(String resourceId, int line, List<List<String>> actualList,
|
||||
List<String> expected) {
|
||||
String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator()));
|
||||
return String.format("%nWrong value in Transactions tab of resource %s line %s." //
|
||||
+ "%nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s", resourceId, line, actual,
|
||||
expected);
|
||||
}
|
||||
|
||||
public static String nrOfLinesWrongInTransactionsTab(String resourceId, int actual, int expected) {
|
||||
String actualStr = String.valueOf(actual);
|
||||
String expectedStr = String.valueOf(expected);
|
||||
|
||||
String prefx = "%nNumber of lines does not match in Transactions tab and expected datatable";
|
||||
String postfx = ". %nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n";
|
||||
if (resourceId != null) {
|
||||
return String.format(prefx + " of resource %s" + postfx, resourceId, actualStr, expectedStr);
|
||||
}
|
||||
return String.format(prefx + postfx, actualStr, expectedStr);
|
||||
return String.format("%nNumber of lines does not match in Transactions tab and expected datatable of resource %s." //
|
||||
+ "%nNumber of transaction tab lines: %s %nNumber of expected datatable lines: %s%n", resourceId, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongValueInLineInChargesTab(int line, List<List<String>> actual, List<String> expected) {
|
||||
String lineStr = String.valueOf(line);
|
||||
String expectedStr = expected.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (List<String> innerList : actual) {
|
||||
sb.append(innerList.toString());
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"%nWrong value in Charges tab line %s. %nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s",
|
||||
lineStr, sb.toString(), expectedStr);
|
||||
public static String wrongValueInLineInChargesTab(String resourceId, int line, List<List<String>> actualList, List<String> expected) {
|
||||
String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator()));
|
||||
return String.format("%nWrong value in Charges tab of resource %s line %s." //
|
||||
+ "%nActual values in line (with the same date) are: %n%s %nExpected values in line: %n%s", resourceId, line, actual,
|
||||
expected);
|
||||
}
|
||||
|
||||
public static String wrongValueInLineInJournalEntries(int line, List<List<List<String>>> actual, List<String> expected) {
|
||||
String lineStr = String.valueOf(line);
|
||||
String expectedStr = expected.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (List<List<String>> innerList : actual) {
|
||||
sb.append(innerList.toString());
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
return wrongValueInLineInJournalEntries(null, line, actual, expected);
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"%nWrong value in Journal entries line %s. %nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s",
|
||||
lineStr, sb.toString(), expectedStr);
|
||||
public static String wrongValueInLineInJournalEntries(String resourceId, int line, List<List<List<String>>> actualList,
|
||||
List<String> expected) {
|
||||
String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator()));
|
||||
return String.format("%nWrong value in Journal entries of resource %s line %s." //
|
||||
+ "%nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s",
|
||||
resourceId, line, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongDataInJournalEntriesGlAccountType(int line, String actual, String expected) {
|
||||
return String.format("Wrong data in Journal entries, line %s / GL account type. Actual value is: %s - But expected value is: %s",
|
||||
line, actual, expected);
|
||||
return String.format("Wrong data in Journal entries, line %s / GL account type. " //
|
||||
+ "Actual value is: %s - But expected value is: %s", line, actual, expected);
|
||||
}
|
||||
|
||||
public static String wrongDataInJournalEntriesGlAccountCode(int line, String actual, String expected) {
|
||||
|
||||
+3
-1
@@ -865,7 +865,9 @@ public class BatchApiStepDef extends AbstractStepDef {
|
||||
Integer statusIdActual = status.getId();
|
||||
Integer statusIdExpected = LoanStatus.APPROVED.value;
|
||||
|
||||
assertThat(statusIdActual).as(ErrorMessageHelper.wrongLoanStatus(statusIdActual, statusIdExpected)).isEqualTo(statusIdExpected);
|
||||
String resourceId = String.valueOf(response.body().getId());
|
||||
assertThat(statusIdActual).as(ErrorMessageHelper.wrongLoanStatus(resourceId, statusIdActual, statusIdExpected))
|
||||
.isEqualTo(statusIdExpected);
|
||||
}
|
||||
|
||||
@Then("Nr. {int} Client creation was rolled back")
|
||||
|
||||
+6
-2
@@ -59,6 +59,7 @@ public class JournalEntriesStepDef extends AbstractStepDef {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
@@ -132,7 +133,8 @@ public class JournalEntriesStepDef extends AbstractStepDef {
|
||||
}
|
||||
}
|
||||
assertThat(containsAnyExpected)
|
||||
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue();
|
||||
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +143,7 @@ public class JournalEntriesStepDef extends AbstractStepDef {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
@@ -218,7 +221,8 @@ public class JournalEntriesStepDef extends AbstractStepDef {
|
||||
}
|
||||
}
|
||||
assertThat(containsAnyExpected)
|
||||
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(i, possibleActualValuesList, expectedValues)).isTrue();
|
||||
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-8
@@ -1298,6 +1298,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
public void disburseLoan(String actualDisbursementDate, String transactionAmount) throws IOException {
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
|
||||
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
|
||||
|
||||
@@ -1310,7 +1312,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId());
|
||||
|
||||
assertThat(statusActual)//
|
||||
.as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))//
|
||||
.as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))//
|
||||
.isEqualTo(statusExpected);//
|
||||
eventCheckHelper.disburseLoanEventCheck(loanId);
|
||||
eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse);
|
||||
@@ -1320,6 +1322,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
public void disburseLoanWithoutAutoDownpayment(String actualDisbursementDate, String transactionAmount) throws IOException {
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
|
||||
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
|
||||
|
||||
@@ -1333,7 +1337,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId());
|
||||
|
||||
assertThat(statusActual)//
|
||||
.as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))//
|
||||
.as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))//
|
||||
.isEqualTo(statusExpected);//
|
||||
eventCheckHelper.disburseLoanEventCheck(loanId);
|
||||
eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse);
|
||||
@@ -1773,6 +1777,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
public void loanChargesGivenChargeDataCheck(DataTable table) throws IOException {
|
||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanCreateResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
@@ -1786,14 +1791,15 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
|
||||
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
||||
|
||||
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInChargesTab(1, actualValuesList, expectedValues))
|
||||
.isTrue();
|
||||
assertThat(containsExpectedValues)
|
||||
.as(ErrorMessageHelper.wrongValueInLineInChargesTab(resourceId, 1, actualValuesList, expectedValues)).isTrue();
|
||||
}
|
||||
|
||||
@Then("Loan Charges tab has the following data:")
|
||||
public void loanChargesTabCheck(DataTable table) throws IOException {
|
||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanCreateResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "charges", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
@@ -1808,8 +1814,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
|
||||
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
||||
|
||||
assertThat(containsExpectedValues).as(ErrorMessageHelper.wrongValueInLineInChargesTab(i, actualValuesList, expectedValues))
|
||||
.isTrue();
|
||||
assertThat(containsExpectedValues)
|
||||
.as(ErrorMessageHelper.wrongValueInLineInChargesTab(resourceId, i, actualValuesList, expectedValues)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1846,6 +1852,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
public void loanStatus(String statusExpected) throws IOException {
|
||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
long loanId = loanCreateResponse.body().getLoanId();
|
||||
String resourceId = String.valueOf(loanId);
|
||||
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
@@ -1855,7 +1862,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
LoanStatus loanStatusExpected = LoanStatus.valueOf(statusExpected);
|
||||
Integer loanStatusExpectedValue = loanStatusExpected.getValue();
|
||||
|
||||
assertThat(loanStatusActualValue).as(ErrorMessageHelper.wrongLoanStatus(loanStatusActualValue, loanStatusExpectedValue))
|
||||
assertThat(loanStatusActualValue).as(ErrorMessageHelper.wrongLoanStatus(resourceId, loanStatusActualValue, loanStatusExpectedValue))
|
||||
.isEqualTo(loanStatusExpectedValue);
|
||||
}
|
||||
|
||||
@@ -2776,7 +2783,9 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
assertNotNull(loanDetails.body().getStatus());
|
||||
final Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId());
|
||||
|
||||
assertThat(statusActual).as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))
|
||||
String resourceId = String.valueOf(loanId);
|
||||
assertThat(statusActual)
|
||||
.as(ErrorMessageHelper.wrongLoanStatus(resourceId, Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))
|
||||
.isEqualTo(statusExpected);
|
||||
eventCheckHelper.disburseLoanEventCheck(loanId);
|
||||
eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse);
|
||||
|
||||
@@ -5231,6 +5231,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
||||
| 22 January 2021 | Accrual | 5.7 | 0.0 | 5.7 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 22 January 2021 | Merchant Issued Refund | 1000.0 | 914.37 | 5.42 | 0.0 | 0.0 | 0.0 | false | true |
|
||||
| 22 January 2021 | Interest Refund | 5.42 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
|
||||
| 22 January 2021 | Accrual Adjustment | 0.28 | 0.0 | 0.28 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
|
||||
@TestRailId:C3302
|
||||
Scenario: UC18-2 - In case of repayment reversal the Interest Refund transaction needs to be recalculated
|
||||
@@ -5288,6 +5289,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan
|
||||
| 22 January 2021 | Merchant Issued Refund | 1000.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
|
||||
| 22 January 2021 | Interest Refund | 5.7 | 0.0 | 5.7 | 0.0 | 0.0 | 0.0 | false | true |
|
||||
| 23 January 2021 | Credit Balance Refund | 85.63 | 85.63 | 0.0 | 0.0 | 0.0 | 85.63 | false | true |
|
||||
| 22 January 2021 | Accrual | 5.42 | 0.0 | 5.42 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
And In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted
|
||||
|
||||
@TestRailId:C3303
|
||||
|
||||
@@ -1331,10 +1331,8 @@ Feature: LoanAccrualActivity
|
||||
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 |
|
||||
| 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 |
|
||||
| 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 | 4.1 | 0.0 | 4.1 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
# --- Accrual activity ---
|
||||
When Admin sets the business date to "07 January 2024"
|
||||
When Admin runs inline COB job for Loan
|
||||
@@ -1351,11 +1349,9 @@ Feature: LoanAccrualActivity
|
||||
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 |
|
||||
| 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 |
|
||||
| 04 January 2024 | Accrual | 4.1 | 0.0 | 4.1 | 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 | 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 Adjustment | 2.79 | 0.0 | 2.79 | 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 |
|
||||
|
||||
@@ -1481,9 +1477,8 @@ Feature: LoanAccrualActivity
|
||||
| 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 Activity | 1.64 | 0.0 | 1.64 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 07 January 2024 | Accrual | 0.25 | 0.0 | 0.25 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 08 January 2024 | Repayment | 753.07 | 749.4 | 3.67 | 0.0 | 0.0 | 250.6 | false | true |
|
||||
| 08 January 2024 | Accrual | 0.24 | 0.0 | 0.24 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 08 January 2024 | Accrual | 2.46 | 0.0 | 2.46 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
|
||||
@TestRailId:C3182
|
||||
Scenario: Verify accrual activity posting job
|
||||
@@ -2045,9 +2040,9 @@ Feature: LoanAccrualActivity
|
||||
Then Loan Transactions tab has the following data:
|
||||
| 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 | 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 | 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 |
|
||||
|
||||
@TestRailId:C3274
|
||||
|
||||
@@ -2959,6 +2959,7 @@ Feature: LoanChargeback
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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:
|
||||
@@ -3032,6 +3033,7 @@ Feature: LoanChargeback
|
||||
| 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 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 |
|
||||
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 |
|
||||
@@ -3065,6 +3067,7 @@ Feature: LoanChargeback
|
||||
| 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 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 |
|
||||
| 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:
|
||||
@@ -3154,8 +3157,8 @@ Feature: LoanChargeback
|
||||
| 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 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 | 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:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||
@@ -3165,7 +3168,7 @@ Feature: LoanChargeback
|
||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries:
|
||||
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 |
|
||||
@@ -3193,8 +3196,8 @@ Feature: LoanChargeback
|
||||
| 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 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 | 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 |
|
||||
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 |
|
||||
@@ -3205,7 +3208,7 @@ Feature: LoanChargeback
|
||||
| ASSET | 112601 | Loans Receivable | | 100.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 5.0 |
|
||||
| LIABILITY | 145023 | Suspense/Clearing account | 105.0 | |
|
||||
Then Loan Transactions tab has a "ACCRUAL" transaction with date "15 April 2024" which has the following Journal entries:
|
||||
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 |
|
||||
@@ -3354,6 +3357,7 @@ Feature: LoanChargeback
|
||||
| 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 |
|
||||
| 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 | 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 |
|
||||
Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2024" which has the following Journal entries:
|
||||
|
||||
@@ -2473,8 +2473,8 @@ Feature: LoanRepayment
|
||||
Then Loan Transactions tab has the following data:
|
||||
| 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 |
|
||||
| 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 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||
Then Loan Charges tab has the following data:
|
||||
| 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 |
|
||||
@@ -2486,8 +2486,8 @@ Feature: LoanRepayment
|
||||
Then Loan Transactions tab has the following data:
|
||||
| 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 |
|
||||
| 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 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||
| 28 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 0.0 | 20.0 | 500.0 |
|
||||
Then Loan Charges tab has the following data:
|
||||
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
|
||||
@@ -2507,8 +2507,8 @@ Feature: LoanRepayment
|
||||
Then Loan Transactions tab has the following data:
|
||||
| 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 |
|
||||
| 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 | Repayment | 1020.0 | 1000.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||
| 28 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 0.0 | 20.0 | 500.0 |
|
||||
| 30 January 2023 | Repayment | 520.0 | 500.0 | 0.0 | 20.0 | 0.0 | 0.0 |
|
||||
| 30 January 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 |
|
||||
@@ -3656,9 +3656,9 @@ Feature: LoanRepayment
|
||||
| 13 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
|
||||
| 22 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
|
||||
| 22 August 2024 | Repayment | 38.24 | 35.44 | 0.0 | 0.0 | 2.8 | 0.0 | false |
|
||||
| 22 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
| 23 August 2024 | Repayment | 10.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
| 24 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false |
|
||||
| 24 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
Then Loan Repayment schedule has 2 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 |
|
||||
| | | 23 July 2024 | | 111.92 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
@@ -3676,9 +3676,9 @@ Feature: LoanRepayment
|
||||
| 13 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
|
||||
| 22 August 2024 | Repayment | 35.44 | 35.44 | 0.0 | 0.0 | 0.0 | 0.0 | true |
|
||||
| 22 August 2024 | Repayment | 38.24 | 35.44 | 0.0 | 0.0 | 2.8 | 0.0 | false |
|
||||
| 22 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
| 23 August 2024 | Repayment | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | false |
|
||||
| 24 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false |
|
||||
| 24 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
| 25 August 2024 | Accrual | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 0.0 | false |
|
||||
Then Loan Repayment schedule has 2 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 |
|
||||
|
||||
+1
@@ -34,4 +34,5 @@ public class AccrualChargeData {
|
||||
private Money chargeAmount;
|
||||
private Money chargeAccruable;
|
||||
private Money chargeAccrued;
|
||||
private Money transactionAccrued;
|
||||
}
|
||||
|
||||
+10
@@ -68,10 +68,20 @@ public class AccrualPeriodData {
|
||||
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getFeeTransactionAccrued() {
|
||||
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getTransactionAccrued).reduce(null,
|
||||
MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getPenaltyAccrued() {
|
||||
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getPenaltyTransactionAccrued() {
|
||||
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getTransactionAccrued).reduce(null,
|
||||
MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getChargeAccruable() {
|
||||
return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
/**
|
||||
* 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.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
public class LoanScheduleAccrualData {
|
||||
|
||||
private final Long loanId;
|
||||
private final Long officeId;
|
||||
private final LocalDate accruedTill;
|
||||
private final PeriodFrequencyType repaymentFrequency;
|
||||
private final Integer repayEvery;
|
||||
private final Integer installmentNumber;
|
||||
private final LocalDate dueDate;
|
||||
private final LocalDate fromDate;
|
||||
private final Long repaymentScheduleId;
|
||||
private final Long loanProductId;
|
||||
private final BigDecimal interestIncome;
|
||||
private final BigDecimal feeIncome;
|
||||
private final BigDecimal penaltyIncome;
|
||||
private final BigDecimal waivedInterestIncome;
|
||||
private final BigDecimal accruedInterestIncome;
|
||||
private final BigDecimal accruedFeeIncome;
|
||||
private final BigDecimal accruedPenaltyIncome;
|
||||
private final CurrencyData currencyData;
|
||||
private final LocalDate interestCalculatedFrom;
|
||||
|
||||
private Map<LoanChargeData, BigDecimal> applicableCharges;
|
||||
private BigDecimal dueDateFeeIncome;
|
||||
private BigDecimal dueDatePenaltyIncome;
|
||||
private BigDecimal accruableIncome;
|
||||
|
||||
private BigDecimal creditedFee;
|
||||
private BigDecimal creditedPenalty;
|
||||
|
||||
public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
|
||||
final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate,
|
||||
final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome,
|
||||
final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome,
|
||||
final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom,
|
||||
final BigDecimal waivedInterestIncome, BigDecimal creditedFee, BigDecimal creditedPenalty) {
|
||||
this.loanId = loanId;
|
||||
this.installmentNumber = installmentNumber;
|
||||
this.officeId = officeId;
|
||||
this.accruedTill = accruedTill;
|
||||
this.dueDate = dueDate;
|
||||
this.fromDate = fromDate;
|
||||
this.repaymentScheduleId = repaymentScheduleId;
|
||||
this.loanProductId = loanProductId;
|
||||
this.interestIncome = interestIncome;
|
||||
this.feeIncome = feeIncome;
|
||||
this.penaltyIncome = penaltyIncome;
|
||||
this.accruedFeeIncome = accruedFeeIncome;
|
||||
this.accruedInterestIncome = accruedInterestIncome;
|
||||
this.accruedPenaltyIncome = accruedPenaltyIncome;
|
||||
this.currencyData = currencyData;
|
||||
this.repaymentFrequency = repaymentFrequency;
|
||||
this.repayEvery = repayEvery;
|
||||
this.interestCalculatedFrom = interestCalculatedFrom;
|
||||
this.waivedInterestIncome = waivedInterestIncome;
|
||||
this.creditedFee = creditedFee;
|
||||
this.creditedPenalty = creditedPenalty;
|
||||
}
|
||||
|
||||
public Long getLoanId() {
|
||||
return this.loanId;
|
||||
}
|
||||
|
||||
public Long getOfficeId() {
|
||||
return this.officeId;
|
||||
}
|
||||
|
||||
public LocalDate getDueDate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
public LocalDate getDueDateAsLocaldate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
public Long getRepaymentScheduleId() {
|
||||
return this.repaymentScheduleId;
|
||||
}
|
||||
|
||||
public Long getLoanProductId() {
|
||||
return this.loanProductId;
|
||||
}
|
||||
|
||||
public BigDecimal getInterestIncome() {
|
||||
return this.interestIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getFeeIncome() {
|
||||
return this.feeIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getPenaltyIncome() {
|
||||
return this.penaltyIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getAccruedInterestIncome() {
|
||||
return this.accruedInterestIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getAccruedFeeIncome() {
|
||||
return this.accruedFeeIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getAccruedPenaltyIncome() {
|
||||
return this.accruedPenaltyIncome;
|
||||
}
|
||||
|
||||
public CurrencyData getCurrencyData() {
|
||||
return this.currencyData;
|
||||
}
|
||||
|
||||
public LocalDate getAccruedTill() {
|
||||
return this.accruedTill;
|
||||
}
|
||||
|
||||
public LocalDate getFromDateAsLocaldate() {
|
||||
return this.fromDate;
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getRepaymentFrequency() {
|
||||
return this.repaymentFrequency;
|
||||
}
|
||||
|
||||
public Integer getRepayEvery() {
|
||||
return this.repayEvery;
|
||||
}
|
||||
|
||||
public LocalDate getInterestCalculatedFrom() {
|
||||
return this.interestCalculatedFrom;
|
||||
}
|
||||
|
||||
public Integer getInstallmentNumber() {
|
||||
return this.installmentNumber;
|
||||
}
|
||||
|
||||
public Map<LoanChargeData, BigDecimal> getApplicableCharges() {
|
||||
return this.applicableCharges;
|
||||
}
|
||||
|
||||
public BigDecimal getDueDateFeeIncome() {
|
||||
return this.dueDateFeeIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getDueDatePenaltyIncome() {
|
||||
return this.dueDatePenaltyIncome;
|
||||
}
|
||||
|
||||
public void updateChargeDetails(final Map<LoanChargeData, BigDecimal> applicableCharges, final BigDecimal dueDateFeeIncome,
|
||||
final BigDecimal dueDatePenaltyIncome) {
|
||||
this.applicableCharges = applicableCharges;
|
||||
this.dueDateFeeIncome = dueDateFeeIncome;
|
||||
this.dueDatePenaltyIncome = dueDatePenaltyIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getWaivedInterestIncome() {
|
||||
return this.waivedInterestIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getAccruableIncome() {
|
||||
return this.accruableIncome;
|
||||
}
|
||||
|
||||
public void updateAccruableIncome(BigDecimal accruableIncome) {
|
||||
this.accruableIncome = accruableIncome;
|
||||
}
|
||||
|
||||
public BigDecimal getCreditedFee() {
|
||||
return this.creditedFee;
|
||||
}
|
||||
|
||||
public BigDecimal getCreditedPenalty() {
|
||||
return this.creditedPenalty;
|
||||
}
|
||||
|
||||
}
|
||||
+10
-1
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
@@ -740,6 +742,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* is lesser than todays date. If not, the transaction date is set to todays date
|
||||
*/
|
||||
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
|
||||
if (isProgressiveSchedule()) {
|
||||
return null;
|
||||
}
|
||||
final Money chargeAmount = loanCharge.getAmount(getCurrency());
|
||||
Money feeCharges = chargeAmount;
|
||||
Money penaltyCharges = Money.zero(getCurrency());
|
||||
@@ -1053,7 +1058,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
LoanCharge charge = loanCharge;
|
||||
// add new charges
|
||||
if (loanCharge.getId() == null) {
|
||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
|
||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
|
||||
loanCharge.update(this);
|
||||
if (this.loanProduct.isMultiDisburseLoan() && loanCharge.isTrancheDisbursementCharge()) {
|
||||
loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this);
|
||||
@@ -3563,4 +3568,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() {
|
||||
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
|
||||
}
|
||||
|
||||
public boolean isProgressiveSchedule() {
|
||||
return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -25,13 +26,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public interface LoanAccrualActivityProcessingService {
|
||||
|
||||
@Transactional
|
||||
void makeAccrualActivityTransaction(Long loanId, LocalDate currentDate);
|
||||
void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate);
|
||||
|
||||
Loan makeAccrualActivityTransaction(Loan loan, LocalDate currentDate);
|
||||
void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate);
|
||||
|
||||
@Transactional
|
||||
void processAccrualActivityForLoanClosure(Loan loan);
|
||||
void processAccrualActivityForLoanClosure(@NotNull Loan loan);
|
||||
|
||||
@Transactional
|
||||
void processAccrualActivityForLoanReopen(Loan loan);
|
||||
void processAccrualActivityForLoanReopen(@NotNull Loan loan);
|
||||
}
|
||||
|
||||
+8
-2
@@ -20,7 +20,9 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
@@ -33,8 +35,12 @@ public class LoanAccrualTransactionBusinessEventServiceImpl implements LoanAccru
|
||||
@Override
|
||||
public void raiseBusinessEventForAccrualTransactions(Loan loan, List<Long> existingTransactionIds) {
|
||||
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
|
||||
if (transaction.isNotReversed() && transaction.isAccrual() && !existingTransactionIds.contains(transaction.getId())) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(transaction));
|
||||
if (transaction.isNotReversed() && (transaction.isAccrual() || transaction.isAccrualAdjustment())
|
||||
&& !existingTransactionIds.contains(transaction.getId())) {
|
||||
LoanTransactionBusinessEvent businessEvent = transaction.isAccrual()
|
||||
? new LoanAccrualTransactionCreatedBusinessEvent(transaction)
|
||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -35,16 +35,14 @@ public interface LoanAccrualsProcessingService {
|
||||
|
||||
void reprocessExistingAccruals(@NotNull Loan loan);
|
||||
|
||||
void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled);
|
||||
void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal);
|
||||
|
||||
void addIncomePostingAndAccruals(Long loanId) throws Exception;
|
||||
|
||||
void processIncomePostingAndAccruals(@NotNull Loan loan);
|
||||
|
||||
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
||||
|
||||
void processAccrualsOnLoanClosure(@NotNull Loan loan);
|
||||
|
||||
void processAccrualsOnLoanReopen(@NotNull Loan loan);
|
||||
|
||||
void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate,
|
||||
@NotNull List<LoanTransaction> newAccrualTransactions);
|
||||
}
|
||||
|
||||
-11
@@ -30,7 +30,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
|
||||
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -53,16 +52,6 @@ public interface LoanWritePlatformService {
|
||||
CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId,
|
||||
JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType);
|
||||
|
||||
@Transactional
|
||||
Loan reverseReplayAccrualActivityTransaction(Loan loan, LoanTransaction loanTransaction, LoanRepaymentScheduleInstallment installment,
|
||||
LocalDate transactionDate);
|
||||
|
||||
@Transactional
|
||||
Loan makeAccrualActivityTransaction(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate transactionDate);
|
||||
|
||||
@Transactional
|
||||
Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction accrualActivityTransaction);
|
||||
|
||||
@Transactional
|
||||
CommandProcessingResult makeInterestPaymentWaiver(JsonCommand command);
|
||||
|
||||
|
||||
+21
-16
@@ -293,10 +293,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
setLoanDelinquencyTag(loan, transactionDate);
|
||||
|
||||
@@ -306,6 +304,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
// disable all active standing orders linked to this loan if status
|
||||
// changes to closed
|
||||
disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
@@ -492,13 +494,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newPaymentTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
return newPaymentTransaction;
|
||||
}
|
||||
|
||||
@@ -726,13 +728,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return newCreditBalanceRefundTransaction;
|
||||
}
|
||||
|
||||
@@ -778,13 +782,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
builderResult.withEntityId(newRefundTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
|
||||
.withGroupId(loan.getGroupId());
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class AddPeriodicAccrualEntriesForLoansTasklet implements Tasklet {
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Long loanId : loanIds) {
|
||||
try {
|
||||
loanAccrualsProcessingService.addIncomeAndAccrualTransactions(loanId);
|
||||
loanAccrualsProcessingService.addIncomePostingAndAccruals(loanId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add income and accrual transaction for loan {}", loanId, e);
|
||||
errors.add(e);
|
||||
|
||||
+4
-4
@@ -466,13 +466,13 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
// update the loan object
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, true);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
|
||||
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).withClientId(loan.getClientId())
|
||||
.withOfficeId(loan.getOfficeId()).withGroupId(loan.getGroupId()).build();
|
||||
|
||||
+137
-84
@@ -18,24 +18,29 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
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.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -46,131 +51,179 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActivityProcessingService {
|
||||
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanWritePlatformService loanWritePlatformService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanChargeValidator loanChargeValidator;
|
||||
private final LoanTransactionAssembler loanTransactionAssembler;
|
||||
private final LoanAccountDomainService loanAccountDomainService;
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void makeAccrualActivityTransaction(Long loanId, final LocalDate currentDate) {
|
||||
public void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate) {
|
||||
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
makeAccrualActivityTransaction(loan, currentDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, final LocalDate currentDate) {
|
||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
// check if loan has installment due on business day
|
||||
Optional<LoanRepaymentScheduleInstallment> first = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate))
|
||||
.findFirst();
|
||||
if (first.isPresent()) {
|
||||
final LoanRepaymentScheduleInstallment installment = first.get();
|
||||
// check if there is no not-replayed-accrual-activity related to business date
|
||||
List<LoanTransaction> loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed()
|
||||
&& loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate));
|
||||
if (loanTransactions.isEmpty()) {
|
||||
loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate);
|
||||
}
|
||||
public void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
// check if loan has installment in the past or due on current date
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan
|
||||
.getRepaymentScheduleInstallments(i -> !i.isDownPayment() && !DateUtils.isBefore(currentDate, i.getDueDate()));
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
// check if there is any not-replayed-accrual-activity related to business date
|
||||
ArrayList<LoanTransaction> existingActivities = new ArrayList<>(
|
||||
loan.getLoanTransactions(t -> t.isNotReversed() && t.isAccrualActivity() && t.getTransactionDate().isEqual(dueDate)));
|
||||
boolean hasExisting = !existingActivities.isEmpty();
|
||||
LoanTransaction existingActivity = hasExisting ? existingActivities.get(0) : null;
|
||||
makeOrReplayActivity(loan, installment, existingActivity);
|
||||
if (hasExisting) {
|
||||
existingActivities.remove(existingActivity);
|
||||
existingActivities.forEach(this::reverseAccrualActivityTransaction);
|
||||
}
|
||||
}
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualActivityForLoanClosure(Loan loan) {
|
||||
public void processAccrualActivityForLoanClosure(@NotNull Loan loan) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
LocalDate date = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
|
||||
List<LoanTransaction> accrualActivityTransaction = loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed)
|
||||
.filter(LoanTransaction::isAccrualActivity).filter(loanTransaction -> loanTransaction.getDateOf().isAfter(date)).toList();
|
||||
if (!accrualActivityTransaction.isEmpty()) {
|
||||
accrualActivityTransaction.forEach(this::reverseAccrualActivityTransaction);
|
||||
}
|
||||
LoanTransaction loanTransaction = assembleClosingAccrualActivityTransaction(loan, date);
|
||||
if (loanTransaction.getAmount().compareTo(BigDecimal.ZERO) != 0) {
|
||||
loanWritePlatformService.makeAccrualActivityTransaction(loan, loanTransaction);
|
||||
}
|
||||
}
|
||||
// reverse after closure activities
|
||||
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(date))
|
||||
.forEach(this::reverseAccrualActivityTransaction);
|
||||
|
||||
private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, "reversed");
|
||||
loanTransaction.reverse();
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
|
||||
private LoanTransaction assembleClosingAccrualActivityTransaction(Loan loan, LocalDate date) {
|
||||
// collect fees
|
||||
// calculate activity amounts
|
||||
BigDecimal feeChargesPortion = BigDecimal.ZERO;
|
||||
// collect penalties
|
||||
BigDecimal penaltyChargesPortion = BigDecimal.ZERO;
|
||||
// collect interests
|
||||
BigDecimal interestPortion = BigDecimal.ZERO;
|
||||
var currency = loan.getCurrency();
|
||||
// sum up all accruals
|
||||
// collect installment amounts
|
||||
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||
feeChargesPortion = installment.getFeeAccrued(currency).getAmount().add(feeChargesPortion);
|
||||
penaltyChargesPortion = installment.getPenaltyAccrued(currency).getAmount().add(penaltyChargesPortion);
|
||||
interestPortion = installment.getInterestAccrued(currency).getAmount().add(interestPortion);
|
||||
feeChargesPortion = MathUtil.add(feeChargesPortion, installment.getFeeChargesCharged());
|
||||
penaltyChargesPortion = MathUtil.add(penaltyChargesPortion, installment.getPenaltyCharges());
|
||||
interestPortion = MathUtil.add(interestPortion, installment.getInterestCharged());
|
||||
}
|
||||
List<LoanTransaction> accrualActivities = loan.getLoanTransactions().stream().filter(LoanTransaction::isAccrualActivity)
|
||||
.filter(LoanTransaction::isNotReversed).toList();
|
||||
// subtract already Posted accruals
|
||||
// subtract already posted activities
|
||||
for (LoanTransaction accrualActivity : accrualActivities) {
|
||||
if (accrualActivity.getFeeChargesPortion() != null) {
|
||||
feeChargesPortion = feeChargesPortion.subtract(accrualActivity.getFeeChargesPortion());
|
||||
}
|
||||
if (accrualActivity.getPenaltyChargesPortion() != null) {
|
||||
penaltyChargesPortion = penaltyChargesPortion.subtract(accrualActivity.getPenaltyChargesPortion());
|
||||
}
|
||||
if (accrualActivity.getInterestPortion() != null) {
|
||||
interestPortion = interestPortion.subtract(accrualActivity.getInterestPortion());
|
||||
}
|
||||
feeChargesPortion = MathUtil.subtract(feeChargesPortion, accrualActivity.getFeeChargesPortion());
|
||||
penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, accrualActivity.getPenaltyChargesPortion());
|
||||
interestPortion = MathUtil.subtract(interestPortion, accrualActivity.getInterestPortion());
|
||||
}
|
||||
|
||||
BigDecimal transactionAmount = feeChargesPortion.add(penaltyChargesPortion).add(interestPortion);
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date, transactionAmount, null,
|
||||
interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||
BigDecimal transactionAmount = MathUtil.add(feeChargesPortion, penaltyChargesPortion, interestPortion);
|
||||
if (!MathUtil.isGreaterThanZero(transactionAmount)) {
|
||||
return;
|
||||
}
|
||||
if (MathUtil.isLessThanZero(feeChargesPortion) || MathUtil.isLessThanZero(penaltyChargesPortion)
|
||||
|| MathUtil.isLessThanZero(interestPortion)) {
|
||||
// TODO reverse latest accrual activity if any amount is negative
|
||||
return;
|
||||
}
|
||||
LoanTransaction newActivity = new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null,
|
||||
externalIdFactory.create());
|
||||
makeAccrualActivityTransaction(loan, newActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualActivityForLoanReopen(Loan loan) {
|
||||
LoanTransaction lastAccrualActivityMarkedToReverse = null;
|
||||
List<LoanTransaction> accrualActivityTransaction = loan.getLoanTransactions().stream()
|
||||
.filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity())
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList();
|
||||
public void processAccrualActivityForLoanReopen(@NotNull Loan loan) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
// grab the latest AccrualActivityTransaction
|
||||
// it does not matter if it is on an installment due date or not because it was posted due to loan close
|
||||
if (!accrualActivityTransaction.isEmpty()) {
|
||||
lastAccrualActivityMarkedToReverse = accrualActivityTransaction.get(accrualActivityTransaction.size() - 1);
|
||||
}
|
||||
LoanTransaction lastAccrualActivityMarkedToReverse = loan.getLoanTransactions().stream()
|
||||
.filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity())
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).reduce((first, second) -> second).orElse(null);
|
||||
final LocalDate lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse == null ? null
|
||||
: lastAccrualActivityMarkedToReverse.getDateOf();
|
||||
LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final List<LoanRepaymentScheduleInstallment> installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate = loan
|
||||
.getRepaymentScheduleInstallments().stream()
|
||||
.filter(installment -> installment.getDueDate().isBefore(today) && (lastAccrualActivityTransactionDate == null
|
||||
|| installment.getDueDate().isAfter(lastAccrualActivityTransactionDate)
|
||||
// if close event happened on installment due date
|
||||
// we should reverse replay it to calculate installment related accrual parts only
|
||||
|| installment.getDueDate().isEqual(lastAccrualActivityTransactionDate)))
|
||||
.filter(installment -> installment.getDueDate().isBefore(today)
|
||||
&& (DateUtils.isAfter(installment.getDueDate(), lastAccrualActivityTransactionDate)
|
||||
// if close event happened on installment due date
|
||||
// we should reverse replay it to calculate installment related accrual parts only
|
||||
|| installment.getDueDate().isEqual(lastAccrualActivityTransactionDate)))
|
||||
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList();
|
||||
for (LoanRepaymentScheduleInstallment installment : installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate) {
|
||||
if (lastAccrualActivityMarkedToReverse != null) {
|
||||
loanWritePlatformService.reverseReplayAccrualActivityTransaction(loan, lastAccrualActivityMarkedToReverse, installment,
|
||||
installment.getDueDate());
|
||||
lastAccrualActivityMarkedToReverse = null;
|
||||
} else {
|
||||
loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, installment.getDueDate());
|
||||
}
|
||||
makeOrReplayActivity(loan, installment, lastAccrualActivityMarkedToReverse);
|
||||
lastAccrualActivityMarkedToReverse = null;
|
||||
}
|
||||
if (lastAccrualActivityMarkedToReverse != null) {
|
||||
reverseAccrualActivityTransaction(lastAccrualActivityMarkedToReverse);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeOrReplayActivity(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
LoanTransaction existingActivity) {
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
if (existingActivity == null) {
|
||||
makeAccrualActivityTransaction(loan, installment, dueDate);
|
||||
} else {
|
||||
reverseReplayAccrualActivityTransaction(loan, existingActivity, installment, dueDate);
|
||||
}
|
||||
}
|
||||
|
||||
private LoanTransaction reverseReplayAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction loanTransaction,
|
||||
@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate transactionDate) {
|
||||
if (validateActivityTransaction(installment, loanTransaction)) {
|
||||
return loanTransaction;
|
||||
}
|
||||
|
||||
LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
if (newLoanTransaction != null) {
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||
loan.addLoanTransaction(newLoanTransaction);
|
||||
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
data.setNewTransactionDetail(newLoanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
reverseAccrualActivityTransaction(loanTransaction);
|
||||
return newLoanTransaction;
|
||||
}
|
||||
|
||||
private boolean validateActivityTransaction(@NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LoanTransaction transaction) {
|
||||
return DateUtils.isEqual(installment.getDueDate(), transaction.getDateOf())
|
||||
&& MathUtil.isEqualTo(transaction.getInterestPortion(), installment.getInterestCharged())
|
||||
&& MathUtil.isEqualTo(transaction.getFeeChargesPortion(), installment.getFeeChargesCharged())
|
||||
&& MathUtil.isEqualTo(transaction.getPenaltyChargesPortion(), installment.getPenaltyCharges());
|
||||
}
|
||||
|
||||
private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) {
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
|
||||
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LocalDate transactionDate) {
|
||||
LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
return newAccrualActivityTransaction == null ? null : makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
|
||||
}
|
||||
|
||||
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction newAccrualActivityTransaction) {
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan));
|
||||
newAccrualActivityTransaction = loanAccountDomainService
|
||||
.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction);
|
||||
|
||||
loan.addLoanTransaction(newAccrualActivityTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction));
|
||||
return newAccrualActivityTransaction;
|
||||
}
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualEventService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanCloseBusinessEvent.class, new LoanCloseListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class, new LoanBalanceChangedListener());
|
||||
}
|
||||
|
||||
private final class LoanCloseListener implements BusinessEventListener<LoanCloseBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanCloseBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
LoanStatus status = loan.getStatus();
|
||||
if (status.isClosedObligationsMet() || status.isOverpaid()) {
|
||||
log.debug("Loan closure on accrual for loan {}", loan.getId());
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanBalanceChangedListener implements BusinessEventListener<LoanBalanceChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanBalanceChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
LoanStatus status = loan.getStatus();
|
||||
if (status.isClosedObligationsMet() || status.isOverpaid()) {
|
||||
log.debug("Loan balance change on accrual for loan {}", loan.getId());
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
-118
@@ -56,14 +56,12 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
|
||||
@@ -77,8 +75,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -102,9 +98,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final OfficeRepository officeRepository;
|
||||
private final LoanChargeRepository loanChargeRepository;
|
||||
private final LoanChargeValidator loanChargeValidator;
|
||||
|
||||
/**
|
||||
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
|
||||
@@ -167,9 +160,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
*/
|
||||
@Override
|
||||
public void reprocessExistingAccruals(@NotNull Loan loan) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
List<LoanTransaction> accrualTransactions = retrieveListOfAccrualTransactions(loan);
|
||||
if (!accrualTransactions.isEmpty()) {
|
||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
@@ -185,7 +175,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled) {
|
||||
public void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
@@ -194,13 +184,31 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
try {
|
||||
addPeriodicAccruals(accruedTill, loan);
|
||||
addAccruals(loan, accruedTill, true, false, addJournal);
|
||||
} catch (Exception e) {
|
||||
String globalisationMessageCode = "error.msg.accrual.exception";
|
||||
throw new GeneralPlatformDomainRuleException(globalisationMessageCode, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addIncomePostingAndAccruals(Long loanId) throws LoanNotFoundException {
|
||||
if (loanId == null) {
|
||||
return;
|
||||
}
|
||||
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
processIncomePostingAndAccruals(loan);
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* method calculates accruals for loan with interest recalculation and compounding to be posted as income
|
||||
*/
|
||||
@@ -228,26 +236,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment.getLastNonDownPaymentInstallment(installments);
|
||||
reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException {
|
||||
if (loanId == null) {
|
||||
return;
|
||||
}
|
||||
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
processIncomePostingAndAccruals(loan);
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate(), false);
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,32 +254,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
processIncomeAndAccrualTransactionOnLoanClosure(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAccrualsOnLoanReopen(@NotNull Loan loan) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
List<LoanTransaction> accruals = retrieveListOfAccrualTransactions(loan);
|
||||
LocalDate accruedTill = loan.getAccruedTill();
|
||||
// if accruedTill is null, the all the accrual transactions will be reversed here
|
||||
if (reverseTransactionsAfter(accruals, accruedTill)) {
|
||||
return;
|
||||
}
|
||||
LoanTransaction closureAccrual = accruals.stream()
|
||||
.filter(t -> !t.isReversed() && t.isAccrual() && DateUtils.isEqual(t.getTransactionDate(), accruedTill))
|
||||
.sorted(LoanTransactionComparator.INSTANCE).reduce((first, second) -> second).orElse(null);
|
||||
if (closureAccrual == null) {
|
||||
return;
|
||||
}
|
||||
int firstInstallmentNumber = fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
|
||||
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(
|
||||
i -> isInPeriod(closureAccrual.getDateOf(), i, i.getInstallmentNumber().equals(firstInstallmentNumber)));
|
||||
if (MathUtil.isGreaterThan(closureAccrual.getInterestPortion(), installment.getInterestCharged())
|
||||
|| MathUtil.isGreaterThan(closureAccrual.getFeeChargesPortion(), installment.getFeeChargesCharged())
|
||||
|| MathUtil.isGreaterThan(closureAccrual.getPenaltyChargesPortion(), installment.getPenaltyCharges())) {
|
||||
reverseAccrual(closureAccrual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method calculates accruals for loan on loan fore closure
|
||||
*/
|
||||
@@ -301,7 +265,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
|
||||
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate);
|
||||
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
|
||||
|
||||
HashMap<String, Object> incomeDetails = new HashMap<>();
|
||||
|
||||
@@ -331,20 +295,29 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
List<LoanTransaction> existingAccruals = retrieveListOfAccrualTransactions(loan);
|
||||
reverseTransactionsAfter(existingAccruals, loan.getLastLoanRepaymentScheduleInstallment().getDueDate());
|
||||
LocalDate lastDueDate = loan.getLastLoanRepaymentScheduleInstallment().getDueDate();
|
||||
reverseTransactionsAfter(existingAccruals, lastDueDate, addJournal);
|
||||
ensureAccrualTransactionMappings(loan);
|
||||
if (DateUtils.isAfter(tillDate, lastDueDate)) {
|
||||
tillDate = lastDueDate;
|
||||
}
|
||||
|
||||
boolean progressiveAccrual = isProgressiveAccrual(loan);
|
||||
LocalDate accruedTill = loan.getAccruedTill();
|
||||
if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)
|
||||
&& existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), tillDate))) {
|
||||
return;
|
||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
LocalDate accrualDate = isFinal
|
||||
? (progressiveAccrual ? (DateUtils.isBefore(lastDueDate, businessDate) ? lastDueDate : businessDate)
|
||||
: getFinalAccrualTransactionDate(loan))
|
||||
: tillDate;
|
||||
if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)) {
|
||||
if (isFinal) {
|
||||
reverseTransactionsAfter(existingAccruals, accrualDate, addJournal);
|
||||
} else if (existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), accrualDate))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic);
|
||||
|
||||
LocalDate accrualDate = isFinal ? (progressiveAccrual ? DateUtils.getBusinessLocalDate() : getFinalAccrualTransactionDate(loan))
|
||||
: tillDate;
|
||||
boolean mergeTransactions = isFinal || progressiveAccrual;
|
||||
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
||||
List<LoanTransaction> accrualTransactions = new ArrayList<>();
|
||||
@@ -399,17 +372,17 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFinal) {
|
||||
loan.setAccruedTill(tillDate);
|
||||
if (!isFinal || progressiveAccrual) {
|
||||
loan.setAccruedTill(isFinal ? accrualDate : tillDate);
|
||||
}
|
||||
ArrayList<Map<String, Object>> newTransactionMapping = new ArrayList<>();
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction);
|
||||
LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
|
||||
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
|
||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||
if (addJournal) {
|
||||
LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
|
||||
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
|
||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||
newTransactionMapping.add(accrualTransaction.toMapData(currency.getCode()));
|
||||
}
|
||||
}
|
||||
@@ -584,7 +557,9 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
|
||||
Money unrecognizedWaived = MathUtil.toMoney(calcChargeUnrecognizedWaivedAmount(paidBys, tillDate), currency);
|
||||
Money transactionWaived = MathUtil.minusToZero(waived, unrecognizedWaived);
|
||||
chargeData.setChargeAccrued(MathUtil.minusToZero(MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency), transactionWaived));
|
||||
Money transactionAccrued = MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency);
|
||||
chargeData.setTransactionAccrued(transactionAccrued);
|
||||
chargeData.setChargeAccrued(MathUtil.minusToZero(transactionAccrued, transactionWaived));
|
||||
|
||||
duePeriod.addCharge(chargeData);
|
||||
}
|
||||
@@ -720,17 +695,29 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
ensureAccrualTransactionMappings(loan);
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate();
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment);
|
||||
LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||
LocalDate lastDueDate = lastInstallment.getDueDate();
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, lastDueDate, true);
|
||||
for (AccrualPeriodData period : accrualPeriods.getPeriods()) {
|
||||
Money interestAccrued = period.getTransactionAccrued();
|
||||
Money feeAccrued = period.getFeeTransactionAccrued();
|
||||
Money penaltyAccrued = period.getPenaltyTransactionAccrued();
|
||||
LoanRepaymentScheduleInstallment installment = loan.fetchRepaymentScheduleInstallment(period.getInstallmentNumber());
|
||||
installment.updateAccrualPortion(interestAccrued, feeAccrued, penaltyAccrued);
|
||||
}
|
||||
} else {
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate();
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment);
|
||||
}
|
||||
}
|
||||
// reverse accruals after last installment
|
||||
LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsAfter(accrualTransactions, lastDueDate, false);
|
||||
}
|
||||
|
||||
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accruals) {
|
||||
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
@@ -738,29 +725,25 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
boolean isExternalIdAutoGenerationEnabled = configurationDomainService.isExternalIdAutoGenerationEnabled();
|
||||
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
if (loanTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) {
|
||||
if (loanTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
loanTransaction.reverse();
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) {
|
||||
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
accrualTransaction.reverse();
|
||||
if (isExternalIdAutoGenerationEnabled) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan,
|
||||
final LoanTransaction interestAccrualTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan,
|
||||
interestApplied, loan.getDisbursementDate(), externalId);
|
||||
loan.addLoanTransaction(interestAppliedTransaction);
|
||||
loan.addLoanTransaction(interestAccrualTransaction);
|
||||
}
|
||||
} else {
|
||||
Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
|
||||
Set<LoanChargePaidBy> chargePaidBies = accrualTransaction.getLoanChargesPaid();
|
||||
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
|
||||
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
|
||||
Money chargeAmount = loanCharge.getAmount(loan.getCurrency());
|
||||
if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(loan.getCurrency()))) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(),
|
||||
loanTransaction, "reversed");
|
||||
loanTransaction.reverse();
|
||||
loan.handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate());
|
||||
if (chargeAmount.isNotEqualTo(accrualTransaction.getAmount(loan.getCurrency()))) {
|
||||
accrualTransaction.reverse();
|
||||
loan.handleChargeAppliedTransaction(loanCharge, accrualTransaction.getTransactionDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,8 +769,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
interest = interest.minus(accrualTransaction.getInterestPortion(currency));
|
||||
fee = fee.minus(accrualTransaction.getFeeChargesPortion(currency));
|
||||
penalty = penalty.minus(accrualTransaction.getPenaltyChargesPortion(currency));
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(accrualTransaction.getLoan(),
|
||||
accrualTransaction, "reversed");
|
||||
accrualTransaction.reverse();
|
||||
}
|
||||
}
|
||||
@@ -869,8 +850,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
loan.addLoanTransaction(transaction);
|
||||
} else if (existingIncomeTransaction.getAmount(loan.getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(existingIncomeTransaction.getLoan(),
|
||||
existingIncomeTransaction, "reversed");
|
||||
existingIncomeTransaction.reverse();
|
||||
LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
@@ -889,8 +868,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
if (existingAccrualTransaction == null
|
||||
|| !MathUtil.isEqualTo(existingAccrualTransaction.getAmount(), compoundingDetail.getAmount())) {
|
||||
if (existingAccrualTransaction != null) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(existingAccrualTransaction.getLoan(),
|
||||
existingAccrualTransaction, "reversed");
|
||||
existingAccrualTransaction.reverse();
|
||||
}
|
||||
LoanTransaction accrual = LoanTransaction.accrueTransaction(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
@@ -910,8 +887,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
&& loan.getStatus().isClosedObligationsMet() && !loan.isNpa() && !loan.isChargedOff()) {
|
||||
|
||||
LocalDate closedDate = loan.getClosedOnDate();
|
||||
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(loan), closedDate);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(loan), closedDate);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(loan), closedDate, false);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(loan), closedDate, false);
|
||||
|
||||
HashMap<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>();
|
||||
determineCumulativeIncomeFromInstallments(loan, cumulativeIncomeFromInstallments);
|
||||
@@ -1188,36 +1165,34 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
}
|
||||
|
||||
private boolean reverseTransactionsAfter(List<LoanTransaction> transactions, LocalDate effectiveDate) {
|
||||
private boolean reverseTransactionsAfter(List<LoanTransaction> accrualTransactions, LocalDate effectiveDate, boolean addEvent) {
|
||||
boolean reversed = false;
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (DateUtils.isAfter(loanTransaction.getTransactionDate(), effectiveDate)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
reverseAccrual(loanTransaction);
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (!accrualTransaction.isReversed() && DateUtils.isAfter(accrualTransaction.getTransactionDate(), effectiveDate)) {
|
||||
reverseAccrual(accrualTransaction, addEvent);
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
private boolean reverseTransactionsOnOrAfter(List<LoanTransaction> transactions, LocalDate date) {
|
||||
private boolean reverseTransactionsOnOrAfter(List<LoanTransaction> accrualTransactions, LocalDate date, boolean addEvent) {
|
||||
boolean reversed = false;
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (!DateUtils.isBefore(loanTransaction.getTransactionDate(), date)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
reverseAccrual(loanTransaction);
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (!accrualTransaction.isReversed() && !DateUtils.isBefore(accrualTransaction.getTransactionDate(), date)) {
|
||||
reverseAccrual(accrualTransaction, addEvent);
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
private void reverseAccrual(LoanTransaction transaction) {
|
||||
private void reverseAccrual(LoanTransaction transaction, boolean addEvent) {
|
||||
transaction.reverse();
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
if (addEvent) {
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getFinalAccrualTransactionDate(Loan loan) {
|
||||
@@ -1229,7 +1204,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
|
||||
public boolean isProgressiveAccrual(@NotNull Loan loan) {
|
||||
return loan.getLoanProductRelatedDetail().getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
|
||||
return loan.isProgressiveSchedule();
|
||||
}
|
||||
|
||||
private void setSetHelpers(Loan loan) {
|
||||
|
||||
+14
-11
@@ -301,17 +301,18 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withEntityId(loanCharge.getId()) //
|
||||
.withEntityExternalId(loanCharge.getExternalId()) //
|
||||
@@ -825,14 +826,14 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1050,9 +1051,11 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
|| (configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled()
|
||||
&& DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate()))) {
|
||||
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
|
||||
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
|
||||
if (applyLoanChargeTransaction != null) {
|
||||
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
|
||||
}
|
||||
}
|
||||
return DateUtils.isBeforeBusinessDate(loanCharge.getDueLocalDate());
|
||||
}
|
||||
|
||||
+3
-13
@@ -32,7 +32,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlatformService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
@@ -42,21 +41,14 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
||||
new LoanAccrualActivityPostingLoanStatusChangedListener());
|
||||
}
|
||||
|
||||
private final class LoanStatusChangedListener implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
private static final class LoanStatusChangedListener implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
log.debug("Loan Status change for loan {}", loan.getId());
|
||||
LoanStatus oldStatus = event.getOldStatus();
|
||||
LoanStatus newStatus = loan.getStatus();
|
||||
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||
log.debug("Loan Status {} for loan {}", newStatus.getCode(), loan.getId());
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
if (loan.isOpen()) {
|
||||
loan.handleMaturityDateActivate();
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanReopen(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,9 +62,7 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
LoanStatus oldStatus = event.getOldStatus();
|
||||
LoanStatus newStatus = loan.getStatus();
|
||||
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
@@ -26,7 +27,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
@@ -68,16 +69,17 @@ public class LoanTransactionAssembler {
|
||||
txnExternalId, null);
|
||||
}
|
||||
|
||||
public LoanTransaction assembleAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||
final LocalDate transactionDate) {
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
public LoanTransaction assembleAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LocalDate transactionDate) {
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
BigDecimal interestPortion = installment.getInterestCharged(currency).getAmount();
|
||||
BigDecimal feeChargesPortion = installment.getFeeChargesCharged(currency).getAmount();
|
||||
BigDecimal penaltyChargesPortion = installment.getPenaltyChargesCharged(currency).getAmount();
|
||||
BigDecimal transactionAmount = interestPortion.add(feeChargesPortion).add(penaltyChargesPortion);
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||
BigDecimal interestPortion = installment.getInterestCharged();
|
||||
BigDecimal feeChargesPortion = installment.getFeeChargesCharged();
|
||||
BigDecimal penaltyChargesPortion = installment.getPenaltyCharges();
|
||||
BigDecimal transactionAmount = MathUtil.add(interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
return MathUtil.isGreaterThanZero(transactionAmount)
|
||||
? new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
+93
-145
@@ -95,8 +95,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdra
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
||||
@@ -230,7 +228,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
|
||||
@@ -499,9 +496,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
createNote(loan, command, changes);
|
||||
// auto create standing instruction
|
||||
createStandingInstruction(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
|
||||
final Set<LoanCharge> loanCharges = loan.getActiveCharges();
|
||||
@@ -526,8 +520,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
// Post Dated Checks
|
||||
@@ -551,6 +545,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loan.getId()) //
|
||||
@@ -869,8 +866,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), true);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan));
|
||||
}
|
||||
@@ -1085,8 +1082,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loan loan,
|
||||
LoanTransaction newInterestPaymentWaiverTransaction) {
|
||||
LocalDate recalculateFrom = null;
|
||||
LocalDate transactionDate = newInterestPaymentWaiverTransaction.getTransactionDate();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
recalculateFrom = newInterestPaymentWaiverTransaction.getTransactionDate();
|
||||
recalculateFrom = transactionDate;
|
||||
}
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
|
||||
|
||||
@@ -1102,14 +1100,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan
|
||||
.fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
|
||||
|
||||
boolean reprocess = true;
|
||||
|
||||
if (!loan.isForeclosure() && isTransactionChronologicallyLatest
|
||||
&& DateUtils.isEqualBusinessDate(newInterestPaymentWaiverTransaction.getTransactionDate()) && currentInstallment != null
|
||||
&& currentInstallment.getTotalOutstanding(loan.getCurrency())
|
||||
if (!loan.isForeclosure() && isTransactionChronologicallyLatest && DateUtils.isEqualBusinessDate(transactionDate)
|
||||
&& currentInstallment != null && currentInstallment.getTotalOutstanding(loan.getCurrency())
|
||||
.isEqualTo(newInterestPaymentWaiverTransaction.getAmount(loan.getCurrency()))) {
|
||||
reprocess = false;
|
||||
}
|
||||
@@ -1155,66 +1150,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
return loan.reprocessTransactions();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan reverseReplayAccrualActivityTransaction(Loan loan, final LoanTransaction loanTransaction,
|
||||
final LoanRepaymentScheduleInstallment installment, final LocalDate transactionDate) {
|
||||
|
||||
LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
if (!newLoanTransaction.getDateOf().isEqual(loanTransaction.getDateOf())
|
||||
|| !LoanTransaction.transactionAmountsMatch(loan.getCurrency(), loanTransaction, newLoanTransaction)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||
loan.addLoanTransaction(newLoanTransaction);
|
||||
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
data.setNewTransactionDetail(newLoanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||
final LocalDate transactionDate) {
|
||||
|
||||
LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
|
||||
if (newAccrualActivityTransaction.getAmount().compareTo(BigDecimal.ZERO) == 0) {
|
||||
return loan;
|
||||
}
|
||||
|
||||
return makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction newAccrualActivityTransaction) {
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan));
|
||||
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction);
|
||||
|
||||
loan.addLoanTransaction(newAccrualActivityTransaction);
|
||||
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction));
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand command) {
|
||||
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiver(command);
|
||||
|
||||
final Long loanId = command.getLoanId();
|
||||
@@ -1245,10 +1183,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
saveNote(noteText, loan, newInterestPaymentWaiverTransaction);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
// disable all active standing orders linked to this loan if status changes to closed
|
||||
@@ -1264,6 +1200,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
.withEntityId(newInterestPaymentWaiverTransaction.getId()) //
|
||||
@@ -1564,11 +1503,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan.updateLoanSummaryAndStatus();
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -1586,6 +1522,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(entityId) //
|
||||
@@ -1647,9 +1586,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
writeOffTransaction.reverse();
|
||||
}
|
||||
|
||||
if (loan.isClosedObligationsMet() || loan.isClosedWrittenOff() || loan.isClosedWithOutstandingAmountMarkedForReschedule()) {
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan);
|
||||
}
|
||||
loan.updateLoanSummaryAndStatus();
|
||||
|
||||
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
|
||||
changedTransactionDetail = loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan,
|
||||
@@ -1850,14 +1787,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(waiveInterestTransaction.getId()) //
|
||||
@@ -1918,44 +1856,48 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING);
|
||||
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.WRITE_OFF_OUTSTANDING,
|
||||
writtenOffOnLocalDate);
|
||||
final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
this.loanTransactionRepository.saveAndFlush(writeOff);
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
final Note note = Note.loanTransactionNote(loan, writeOff, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(writeOff.getId()) //
|
||||
.withEntityExternalId(writeOff.getExternalId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
.withLoanId(loanId) //
|
||||
.with(changes).build();
|
||||
.with(changes);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
if (changedTransactionDetail != null) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
this.loanTransactionRepository.saveAndFlush(writeOff);
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
final Note note = Note.loanTransactionNote(loan, writeOff, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
builder.withEntityId(writeOff.getId()).withEntityExternalId(writeOff.getExternalId());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -2020,12 +1962,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
if (possibleClosingTransaction != null) {
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -2037,6 +1976,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// disable all active standing instructions linked to the loan
|
||||
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
CommandProcessingResult result;
|
||||
if (possibleClosingTransaction != null) {
|
||||
|
||||
@@ -2654,15 +2596,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
|
||||
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
if (writeOffTransaction != null) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
@@ -2811,11 +2755,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
@@ -2879,11 +2825,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
|
||||
return loan;
|
||||
}
|
||||
|
||||
@@ -3396,8 +3343,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
// Create journal entries for the new transaction(s)
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
// Calculate and apply delinquency tag (if applicable)
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
|
||||
// disable all active standing orders linked to this loan if status
|
||||
@@ -3414,11 +3359,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
|
||||
}
|
||||
// Raise business events
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
|
||||
// Create journal entries for the new transaction(s)
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
Long entityId = refundTransaction.getId();
|
||||
ExternalId entityExternalId = refundTransaction.getExternalId();
|
||||
Long subEntityId = interestRefundTransaction != null ? interestRefundTransaction.getId() : null;
|
||||
|
||||
+22
-15
@@ -100,6 +100,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
@@ -332,9 +333,16 @@ public class LoanAccountConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanStatusChangePlatformService.class)
|
||||
public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualActivityProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanAccrualEventService.class)
|
||||
public LoanAccrualEventService loanAccrualEventService(BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanAccrualsProcessingService loanAccrualsProcessingService,
|
||||
LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService,
|
||||
return new LoanAccrualEventService(businessEventNotifierService, loanAccrualsProcessingService,
|
||||
loanAccrualActivityProcessingService);
|
||||
}
|
||||
|
||||
@@ -351,8 +359,8 @@ public class LoanAccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanWritePlatformService.class)
|
||||
public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory,
|
||||
PlatformSecurityContext context, LoanTransactionValidator loanTransactionValidator,
|
||||
public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context,
|
||||
LoanTransactionValidator loanTransactionValidator,
|
||||
LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository,
|
||||
LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository,
|
||||
@@ -381,18 +389,17 @@ public class LoanAccountConfiguration {
|
||||
LoanOfficerValidator loanOfficerValidator, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator,
|
||||
LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService,
|
||||
LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator,
|
||||
loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository,
|
||||
loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService,
|
||||
calendarInstanceRepository, paymentDetailWritePlatformService, holidayRepository, configurationDomainService,
|
||||
workingDaysRepository, accountTransfersWritePlatformService, accountTransfersReadPlatformService,
|
||||
accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository,
|
||||
loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository,
|
||||
accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper,
|
||||
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository,
|
||||
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
|
||||
postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine,
|
||||
loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
|
||||
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
|
||||
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
|
||||
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
|
||||
guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService,
|
||||
transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
|
||||
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
|
||||
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository,
|
||||
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
|
||||
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService);
|
||||
|
||||
+3
@@ -128,6 +128,9 @@ class LoanChargeWritePlatformServiceImplTest {
|
||||
@Mock
|
||||
private LoanChargeValidator loanChargeValidator;
|
||||
|
||||
@Mock
|
||||
private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);
|
||||
|
||||
-4
@@ -240,8 +240,6 @@ class LoanWritePlatformServiceJpaRepositoryImplTest {
|
||||
when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList());
|
||||
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
|
||||
when(loan.isClosedWrittenOff()).thenReturn(false);
|
||||
when(loan.isClosedObligationsMet()).thenReturn(false);
|
||||
when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false);
|
||||
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
|
||||
|
||||
// Act
|
||||
@@ -287,8 +285,6 @@ class LoanWritePlatformServiceJpaRepositoryImplTest {
|
||||
when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList());
|
||||
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
|
||||
when(loan.isClosedWrittenOff()).thenReturn(false);
|
||||
when(loan.isClosedObligationsMet()).thenReturn(false);
|
||||
when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false);
|
||||
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
|
||||
|
||||
// Act
|
||||
|
||||
+7
-6
@@ -553,7 +553,7 @@ public abstract class BaseLoanIntegrationTest {
|
||||
protected void verifyTransactions(Long loanId, Transaction... transactions) {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
if (transactions == null || transactions.length == 0) {
|
||||
Assertions.assertTrue(loanDetails.getTransactions().isEmpty(), "No transaction is expected");
|
||||
Assertions.assertTrue(loanDetails.getTransactions().isEmpty(), "No transaction is expected on loan " + loanId);
|
||||
} else {
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size());
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
@@ -562,12 +562,13 @@ public abstract class BaseLoanIntegrationTest {
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)))
|
||||
.findFirst();
|
||||
Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr);
|
||||
Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr + " on loan " + loanId);
|
||||
|
||||
GetLoansLoanIdTransactions tx = optTx.get();
|
||||
|
||||
if (tr.reversed != null) {
|
||||
Assertions.assertEquals(tr.reversed, tx.getManuallyReversed(), "Transaction is not reversed: " + tr);
|
||||
Assertions.assertEquals(tr.reversed, tx.getManuallyReversed(),
|
||||
"Transaction is not reversed: " + tr + " on loan " + loanId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -576,9 +577,9 @@ public abstract class BaseLoanIntegrationTest {
|
||||
protected void verifyTransactions(Long loanId, TransactionExt... transactions) {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
if (transactions == null || transactions.length == 0) {
|
||||
assertNull(loanDetails.getTransactions(), "No transaction is expected");
|
||||
assertNull(loanDetails.getTransactions(), "No transaction is expected on loan " + loanId);
|
||||
} else {
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size());
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size(), "Number of transactions on loan " + loanId);
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
boolean found = loanDetails.getTransactions().stream().anyMatch(item -> Objects.equals(item.getAmount(), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
@@ -591,7 +592,7 @@ public abstract class BaseLoanIntegrationTest {
|
||||
&& Objects.equals(item.getOverpaymentPortion(), tr.overpaymentPortion) //
|
||||
&& Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) //
|
||||
);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -78,15 +79,20 @@ public class LoanChargeProgressiveTest extends BaseLoanIntegrationTest {
|
||||
addLoanCharge(loanId, chargeResponse.getResourceId(), "03 October 2024", 20.0d);
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertTrue(
|
||||
loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d)));
|
||||
loanDetails.getTransactions().stream().noneMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d)));
|
||||
});
|
||||
runAt("04 October 2024", () -> {
|
||||
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY,
|
||||
false);
|
||||
executeInlineCOB(loanId);
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertTrue(
|
||||
loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d)));
|
||||
Assertions.assertTrue(loanDetails.getTransactions().stream()
|
||||
.anyMatch(t -> t.getType().getAccrual() && t.getFeeChargesPortion().equals(20.0d)));
|
||||
});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() {
|
||||
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY, false);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -300,6 +300,7 @@ 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 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(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) //
|
||||
);
|
||||
|
||||
@@ -584,6 +585,7 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
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(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) //
|
||||
);
|
||||
|
||||
@@ -649,6 +651,7 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
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(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) //
|
||||
);
|
||||
|
||||
@@ -715,6 +718,7 @@ public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoa
|
||||
verifyTransactions(loanId, //
|
||||
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(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) //
|
||||
);
|
||||
|
||||
|
||||
+44
-25
@@ -210,9 +210,11 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"),
|
||||
transaction(5.74, "Interest Refund", "22 January 2021"));
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
||||
transaction(5.74, "Accrual", "22 January 2021"), //
|
||||
transaction(5.74, "Interest Refund", "22 January 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -241,9 +243,11 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"),
|
||||
transaction(5.74, "Interest Refund", "22 January 2021"));
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Payout Refund", "22 January 2021"), //
|
||||
transaction(5.74, "Accrual", "22 January 2021"), //
|
||||
transaction(5.74, "Interest Refund", "22 January 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -272,9 +276,10 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||
transaction(1000.0, "Payout Refund", "01 February 2021"), transaction(8.48, "Accrual", "01 February 2021"),
|
||||
transaction(8.48, "Interest Refund", "01 February 2021"));
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Payout Refund", "01 February 2021"), //
|
||||
transaction(8.48, "Accrual", "01 February 2021"), //
|
||||
transaction(8.48, "Interest Refund", "01 February 2021")); //
|
||||
});
|
||||
}
|
||||
|
||||
@@ -315,9 +320,12 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
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(10.49, "Interest Refund", "09 February 2021"), transaction(10.49, "Accrual", "09 February 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(10.49, "Interest Refund", "09 February 2021"), //
|
||||
transaction(10.49, "Accrual", "09 February 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -350,9 +358,12 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"),
|
||||
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
|
||||
transaction(5.74, "Accrual", "22 January 2021"), transaction(5.74, "Interest Refund", "22 January 2021"));
|
||||
verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(250.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Payout Refund", "22 January 2021"), //
|
||||
transaction(5.74, "Accrual", "22 January 2021"), //
|
||||
transaction(5.74, "Interest Refund", "22 January 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -386,9 +397,12 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"),
|
||||
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
|
||||
transaction(5.13, "Accrual", "22 January 2021"), transaction(5.13, "Interest Refund", "22 January 2021"));
|
||||
verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"), //
|
||||
transaction(250.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Payout Refund", "22 January 2021"), //
|
||||
transaction(5.13, "Accrual", "22 January 2021"), //
|
||||
transaction(5.13, "Interest Refund", "22 January 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -435,10 +449,13 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
logLoanTransactions(loanId);
|
||||
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(87.82, "Repayment", "01 February 2021"), transaction(9.67, "Interest Refund", "09 February 2021"),
|
||||
transaction(9.67, "Accrual", "09 February 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(87.82, "Repayment", "01 February 2021"), //
|
||||
transaction(9.67, "Interest Refund", "09 February 2021"), //
|
||||
transaction(9.67, "Accrual", "09 February 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1115,9 +1132,10 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(85.63, "Repayment", "10 January 2021"), //
|
||||
transaction(5.7, "Accrual", "22 January 2021"), //
|
||||
transaction(5.70, "Accrual", "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(0.28, "Accrual Adjustment", "22 January 2021") //
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1202,7 +1220,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
reversedTransaction(85.63, "Repayment", "10 January 2021"), //
|
||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), //
|
||||
transaction(5.70, "Interest Refund", "22 January 2021"), //
|
||||
transaction(5.42, "Accrual", "22 January 2021") //
|
||||
transaction(5.42, "Accrual", "22 January 2021"), //
|
||||
transaction(0.28, "Accrual", "22 January 2021") //
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
+24
-40
@@ -569,8 +569,8 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
verifyTransactions(loanId.get(),
|
||||
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(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)); //
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -652,32 +652,36 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
|
||||
addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1);
|
||||
|
||||
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(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
|
||||
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(70.0, "Accrual Activity", repaymentDate1, 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(70.0, "Accrual Activity", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false) //
|
||||
);
|
||||
});
|
||||
runAt(disbursementDay2, () -> {
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay2)
|
||||
.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), //
|
||||
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)); //
|
||||
|
||||
verifyTransactions(loanId.get(),
|
||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
|
||||
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) //
|
||||
);
|
||||
});
|
||||
runAt(repaymentPeriod1CloseDate, () -> {
|
||||
|
||||
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(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false), //
|
||||
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(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(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.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) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -727,6 +731,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
|
||||
verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.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", 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(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); //
|
||||
|
||||
@@ -828,6 +833,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
|
||||
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(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(500.0, "Disbursement", disbursementDay2, 500, 0, 0, 0, 0, 0, 0, false));
|
||||
});
|
||||
@@ -837,9 +843,9 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
|
||||
verifyTransactions(loanId.get(), transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 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", 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(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));
|
||||
transaction(500.0, "Disbursement", disbursementDay2, 500.0, 0, 0, 0, 0, 0, 0, false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -867,18 +873,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate);
|
||||
addRepaymentForLoan(loanId.get(), 650.0, repaymentDate1);
|
||||
verifyTransactions(loanId.get(),
|
||||
transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45,
|
||||
// "Accrual",
|
||||
// repaymentDate1,
|
||||
// 0.0,
|
||||
// 0,
|
||||
// 39.45,
|
||||
// 40.0,
|
||||
// 30.0,
|
||||
// 0,
|
||||
// 0,
|
||||
// false),
|
||||
// //
|
||||
transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), //
|
||||
transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
|
||||
transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
|
||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) //
|
||||
@@ -887,18 +882,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
runAt(repaymentPeriod1CloseDate, () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||
verifyTransactions(loanId.get(),
|
||||
transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), // transaction(109.45,
|
||||
// "Accrual",
|
||||
// repaymentDate1,
|
||||
// 0.0,
|
||||
// 0,
|
||||
// 39.45,
|
||||
// 40.0,
|
||||
// 30.0,
|
||||
// 0,
|
||||
// 0,
|
||||
// false),
|
||||
// //
|
||||
transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 39.45, 40.0, 30.0, 0.0, 40.55, false), //
|
||||
transaction(109.45, "Accrual Activity", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
|
||||
transaction(109.45, "Accrual", repaymentDate1, 0.0, 0.0, 39.45, 40.0, 30.0, 0.0, 0.0, false), //
|
||||
transaction(500.0, "Disbursement", disbursementDay, 500.0, 0, 0, 0, 0, 0, 0, false) //
|
||||
|
||||
Reference in New Issue
Block a user