Merge branch 'FINERACT-167' into develop
This commit is contained in:
@@ -1303,6 +1303,14 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>loans/{loanId}/transactions?command=foreclosure</td>
|
||||||
|
<td><a href="#loans_transaction_foreclosure">Foreclose an Active Loan</a></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="#loans_charges">Loan Charges</a></td>
|
<td><a href="#loans_charges">Loan Charges</a></td>
|
||||||
@@ -10141,6 +10149,13 @@ No Request Body:
|
|||||||
"total" "amount" is set
|
"total" "amount" is set
|
||||||
to the total amount paid in advance.
|
to the total amount paid in advance.
|
||||||
</dd>
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<b>'foreclosure'</b><br> "transaction date" is set to the current date by default<br>
|
||||||
|
"transaction amount" is set
|
||||||
|
to the sum of total loan outstanding principal
|
||||||
|
and total Interest/ Fee/ Charges / Penalties
|
||||||
|
till foreclosure date.
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>Example Request:</p>
|
<p>Example Request:</p>
|
||||||
<div class=apiClick>loans/1/transactions/template?command=repayment</div>
|
<div class=apiClick>loans/1/transactions/template?command=repayment</div>
|
||||||
@@ -10266,6 +10281,42 @@ Request Body:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a id="loans_transaction_foreclosure" name="loans_transaction_foreclosure" class="old-syle-anchor"> </a>
|
||||||
|
<div class="method-section">
|
||||||
|
<div class="method-description">
|
||||||
|
<h4>Foreclosure of an Active Loan</h4>
|
||||||
|
</div>
|
||||||
|
<div class="method-example">
|
||||||
|
<code class="method-declaration">
|
||||||
|
POST https://DomainName/api/v1/loans/{loanId}/transactions?command=foreclosure
|
||||||
|
</code>
|
||||||
|
<code class="method-request">
|
||||||
|
POST loans/5/transactions?command=foreclosure
|
||||||
|
Content-Type: application/json
|
||||||
|
Request Body:
|
||||||
|
{
|
||||||
|
"dateFormat": "dd MMMM yyyy",
|
||||||
|
"locale": "en",
|
||||||
|
"transactionDate": "10 December 2012",
|
||||||
|
"note": "Customer Death"
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
<code class="method-response">
|
||||||
|
{
|
||||||
|
"changes": {
|
||||||
|
"note": "Foreclosure Made!!!",
|
||||||
|
"eventAmount": 7573.76,
|
||||||
|
"transactionDate": [2012,
|
||||||
|
12,
|
||||||
|
10],
|
||||||
|
"transactions": [15818]
|
||||||
|
},
|
||||||
|
"loanId": 5
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<a id="loans_transaction_waiveinterest" name="loans_transaction_waiveinterest" class="old-syle-anchor"> </a>
|
<a id="loans_transaction_waiveinterest" name="loans_transaction_waiveinterest" class="old-syle-anchor"> </a>
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
|
|||||||
+53
@@ -4956,6 +4956,59 @@ public class ClientLoanIntegrationTest {
|
|||||||
this.validateIfValuesAreNotOverridden(loanID, loanProductID);
|
this.validateIfValuesAreNotOverridden(loanID, loanProductID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test case to verify Loan Foreclosure.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testLoanForeclosure() {
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||||
|
final Integer loanProductID = createLoanProduct(false, NONE);
|
||||||
|
|
||||||
|
List<HashMap> charges = new ArrayList<>();
|
||||||
|
|
||||||
|
Integer flatAmountChargeOne = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false));
|
||||||
|
addCharges(charges, flatAmountChargeOne, "50", "01 October 2011");
|
||||||
|
Integer flatAmountChargeTwo = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||||
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "100", true));
|
||||||
|
addCharges(charges, flatAmountChargeTwo, "100", "15 December 2011");
|
||||||
|
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, charges, null, "10,000.00");
|
||||||
|
Assert.assertNotNull(loanID);
|
||||||
|
|
||||||
|
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||||
|
|
||||||
|
System.out.println("----------------------------------- APPROVE LOAN -----------------------------------------");
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
|
||||||
|
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
|
||||||
|
|
||||||
|
System.out.println("----------------------------------- DISBURSE LOAN ----------------------------------------");
|
||||||
|
loanStatusHashMap = this.loanTransactionHelper.disburseLoan("20 September 2011", loanID, "10,000.00");
|
||||||
|
System.out.println("DISBURSE " + loanStatusHashMap);
|
||||||
|
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||||
|
|
||||||
|
System.out.println("---------------------------------- Make repayment 1 --------------------------------------");
|
||||||
|
this.loanTransactionHelper.makeRepayment("20 October 2011", Float.valueOf("2676.24"), loanID);
|
||||||
|
|
||||||
|
System.out.println("---------------------------------- FORECLOSE LOAN ----------------------------------------");
|
||||||
|
this.loanTransactionHelper.forecloseLoan("08 November 2011", loanID);
|
||||||
|
|
||||||
|
// retrieving the loan status
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
// verifying the loan status is closed
|
||||||
|
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
|
||||||
|
// retrieving the loan sub-status
|
||||||
|
loanStatusHashMap = LoanStatusChecker.getSubStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
// verifying the loan sub-status is foreclosed
|
||||||
|
LoanStatusChecker.verifyLoanAccountForeclosed(loanStatusHashMap);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
|
private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
|
||||||
String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
|
String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
|
||||||
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
|
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
|
||||||
|
|||||||
+14
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests.common.loans;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@@ -59,14 +60,27 @@ public class LoanStatusChecker {
|
|||||||
assertTrue(getStatus(loanStatusHashMap, "overpaid"));
|
assertTrue(getStatus(loanStatusHashMap, "overpaid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void verifyLoanAccountForeclosed(final HashMap loanSubStatusHashMap) {
|
||||||
|
assertEquals("Foreclosed", getSubStatus(loanSubStatusHashMap, "value"));
|
||||||
|
}
|
||||||
|
|
||||||
public static HashMap<String, Object> getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
public static HashMap<String, Object> getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
final Integer loanID) {
|
final Integer loanID) {
|
||||||
final String url = "/fineract-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
|
final String url = "/fineract-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
|
||||||
return Utils.performServerGet(requestSpec, responseSpec, url, "status");
|
return Utils.performServerGet(requestSpec, responseSpec, url, "status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HashMap<String, Object> getSubStatusOfLoan(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec, final Integer loanID) {
|
||||||
|
final String url = "/fineract-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
return Utils.performServerGet(requestSpec, responseSpec, url, "subStatus");
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean getStatus(final HashMap loanStatusMap, final String nameOfLoanStatusString) {
|
private static boolean getStatus(final HashMap loanStatusMap, final String nameOfLoanStatusString) {
|
||||||
return (Boolean) loanStatusMap.get(nameOfLoanStatusString);
|
return (Boolean) loanStatusMap.get(nameOfLoanStatusString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getSubStatus(final HashMap loanStatusMap, final String nameOfLoanStatusString) {
|
||||||
|
return (String) loanStatusMap.get(nameOfLoanStatusString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -52,6 +52,7 @@ public class LoanTransactionHelper {
|
|||||||
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
|
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
|
||||||
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
|
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
|
||||||
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
|
private static final String MAKE_REFUND_BY_CASH_COMMAND = "refundByCash";
|
||||||
|
private static final String FORECLOSURE_COMMAND = "foreclosure";
|
||||||
|
|
||||||
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
||||||
this.requestSpec = requestSpec;
|
this.requestSpec = requestSpec;
|
||||||
@@ -217,6 +218,11 @@ public class LoanTransactionHelper {
|
|||||||
getRepaymentBodyAsJSON(date, amountToBePaid), "");
|
getRepaymentBodyAsJSON(date, amountToBePaid), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap forecloseLoan(final String transactionDate, final Integer loanID) {
|
||||||
|
return (HashMap) performLoanTransaction(createLoanTransactionURL(FORECLOSURE_COMMAND, loanID),
|
||||||
|
getForeclosureBodyAsJSON(transactionDate, loanID), "");
|
||||||
|
}
|
||||||
|
|
||||||
public HashMap withdrawLoanApplicationByClient(final String date, final Integer loanID) {
|
public HashMap withdrawLoanApplicationByClient(final String date, final Integer loanID) {
|
||||||
return performLoanTransaction(createLoanOperationURL(WITHDRAW_LOAN_APPLICATION_COMMAND, loanID),
|
return performLoanTransaction(createLoanOperationURL(WITHDRAW_LOAN_APPLICATION_COMMAND, loanID),
|
||||||
getWithdrawLoanApplicationBodyAsJSON(date));
|
getWithdrawLoanApplicationBodyAsJSON(date));
|
||||||
@@ -326,6 +332,17 @@ public class LoanTransactionHelper {
|
|||||||
return new Gson().toJson(map);
|
return new Gson().toJson(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getForeclosureBodyAsJSON(final String transactionDate, final Integer loanId) {
|
||||||
|
final HashMap<String, Object> map = new HashMap<>();
|
||||||
|
map.put("locale", "en");
|
||||||
|
map.put("dateFormat", "dd MMMM yyyy");
|
||||||
|
map.put("transactionDate", transactionDate);
|
||||||
|
map.put("note", "Foreclosure Made!!!");
|
||||||
|
String json = new Gson().toJson(map);
|
||||||
|
System.out.println(json);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
private String getWriteOffBodyAsJSON(final String transactionDate) {
|
private String getWriteOffBodyAsJSON(final String transactionDate) {
|
||||||
final HashMap<String, String> map = new HashMap<>();
|
final HashMap<String, String> map = new HashMap<>();
|
||||||
map.put("dateFormat", "dd MMMM yyyy");
|
map.put("dateFormat", "dd MMMM yyyy");
|
||||||
|
|||||||
+9
@@ -723,6 +723,15 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder loanForeclosure(final Long loanId) {
|
||||||
|
this.actionName = "FORECLOSURE";
|
||||||
|
this.entityName = "LOAN";
|
||||||
|
this.entityId = null;
|
||||||
|
this.loanId = loanId;
|
||||||
|
this.href = "/loans/" + loanId + "/transactions?command=foreclosure";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createLoanApplication() {
|
public CommandWrapperBuilder createLoanApplication() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "LOAN";
|
this.entityName = "LOAN";
|
||||||
|
|||||||
+4
@@ -113,4 +113,8 @@ public interface LoanApiConstants {
|
|||||||
//loan write off
|
//loan write off
|
||||||
public static final String WRITEOFFREASONS = "WriteOffReasons";
|
public static final String WRITEOFFREASONS = "WriteOffReasons";
|
||||||
|
|
||||||
|
// fore closure constants
|
||||||
|
public static final String transactionDateParamName = "transactionDate";
|
||||||
|
public static final String noteParamName = "note";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -129,6 +129,14 @@ public class LoanTransactionsApiResource {
|
|||||||
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
|
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
|
||||||
} else if (is(commandParam, "refundbytransfer")) {
|
} else if (is(commandParam, "refundbytransfer")) {
|
||||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
|
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
|
||||||
|
} else if (is(commandParam, "foreclosure")) {
|
||||||
|
LocalDate transactionDate = null;
|
||||||
|
if (transactionDateParam == null) {
|
||||||
|
transactionDate = DateUtils.getLocalDateOfTenant();
|
||||||
|
} else {
|
||||||
|
transactionDate = LocalDate.fromDateFields(transactionDateParam.getDate("transactionDate", dateFormat, locale));
|
||||||
|
}
|
||||||
|
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, transactionDate);
|
||||||
} else {
|
} else {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||||
}
|
}
|
||||||
@@ -189,6 +197,9 @@ public class LoanTransactionsApiResource {
|
|||||||
} else if (is(commandParam, "refundByCash")) {
|
} else if (is(commandParam, "refundByCash")) {
|
||||||
final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
|
final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
} else if (is(commandParam, "foreclosure")) {
|
||||||
|
final CommandWrapper commandRequest = builder.loanForeclosure(loanId).build();
|
||||||
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
|
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam); }
|
||||||
|
|||||||
+24
-16
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
|
|||||||
import org.apache.fineract.portfolio.fund.data.FundData;
|
import org.apache.fineract.portfolio.fund.data.FundData;
|
||||||
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
|
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
|
import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||||
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
|
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
|
||||||
@@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrat
|
|||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
|
||||||
import org.apache.fineract.portfolio.note.data.NoteData;
|
import org.apache.fineract.portfolio.note.data.NoteData;
|
||||||
|
import org.hibernate.dialect.Sybase11Dialect;
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@@ -65,6 +67,7 @@ public class LoanAccountData {
|
|||||||
|
|
||||||
// status
|
// status
|
||||||
private final LoanStatusEnumData status;
|
private final LoanStatusEnumData status;
|
||||||
|
private final EnumOptionData subStatus;
|
||||||
|
|
||||||
// related to
|
// related to
|
||||||
private final Long clientId;
|
private final Long clientId;
|
||||||
@@ -214,6 +217,7 @@ public class LoanAccountData {
|
|||||||
final Long id = null;
|
final Long id = null;
|
||||||
final String accountNo = null;
|
final String accountNo = null;
|
||||||
final LoanStatusEnumData status = null;
|
final LoanStatusEnumData status = null;
|
||||||
|
final EnumOptionData subStatus = null;
|
||||||
final String externalId = null;
|
final String externalId = null;
|
||||||
final Long clientId = null;
|
final Long clientId = null;
|
||||||
final String clientName = null;
|
final String clientName = null;
|
||||||
@@ -335,7 +339,7 @@ public class LoanAccountData {
|
|||||||
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
||||||
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||||
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
||||||
maximumGap);
|
maximumGap, subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,6 +352,7 @@ public class LoanAccountData {
|
|||||||
final Long id = null;
|
final Long id = null;
|
||||||
final String accountNo = null;
|
final String accountNo = null;
|
||||||
final LoanStatusEnumData status = null;
|
final LoanStatusEnumData status = null;
|
||||||
|
final EnumOptionData subStatus = null;
|
||||||
final String externalId = null;
|
final String externalId = null;
|
||||||
final GroupGeneralData group = null;
|
final GroupGeneralData group = null;
|
||||||
final EnumOptionData loanType = null;
|
final EnumOptionData loanType = null;
|
||||||
@@ -468,7 +473,7 @@ public class LoanAccountData {
|
|||||||
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
||||||
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||||
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
||||||
maximumGap);
|
maximumGap, subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +502,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -509,6 +514,7 @@ public class LoanAccountData {
|
|||||||
final Long id = null;
|
final Long id = null;
|
||||||
final String accountNo = null;
|
final String accountNo = null;
|
||||||
final LoanStatusEnumData status = null;
|
final LoanStatusEnumData status = null;
|
||||||
|
final EnumOptionData subStatus = null;
|
||||||
final String externalId = null;
|
final String externalId = null;
|
||||||
final Long clientId = null;
|
final Long clientId = null;
|
||||||
final String clientAccountNo = null;
|
final String clientAccountNo = null;
|
||||||
@@ -631,7 +637,7 @@ public class LoanAccountData {
|
|||||||
maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
||||||
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||||
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
||||||
maximumGap);
|
maximumGap, subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +666,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,6 +684,7 @@ public class LoanAccountData {
|
|||||||
final Long id = null;
|
final Long id = null;
|
||||||
final String accountNo = null;
|
final String accountNo = null;
|
||||||
final LoanStatusEnumData status = null;
|
final LoanStatusEnumData status = null;
|
||||||
|
final EnumOptionData subStatus = null;
|
||||||
final String externalId = null;
|
final String externalId = null;
|
||||||
final Long clientId = null;
|
final Long clientId = null;
|
||||||
final String clientAccountNo = null;
|
final String clientAccountNo = null;
|
||||||
@@ -808,7 +815,7 @@ public class LoanAccountData {
|
|||||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||||
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
|
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
|
||||||
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
|
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
|
||||||
product.getMaximumGapBetweenInstallments());
|
product.getMaximumGapBetweenInstallments(), subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||||
@@ -867,7 +874,7 @@ public class LoanAccountData {
|
|||||||
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
|
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
|
||||||
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
|
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
|
||||||
paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(),
|
paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(),
|
||||||
product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments());
|
product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), acc.subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -897,7 +904,7 @@ public class LoanAccountData {
|
|||||||
final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||||
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
|
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
|
||||||
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap) {
|
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus) {
|
||||||
|
|
||||||
final LoanScheduleData repaymentSchedule = null;
|
final LoanScheduleData repaymentSchedule = null;
|
||||||
final Collection<LoanTransactionData> transactions = null;
|
final Collection<LoanTransactionData> transactions = null;
|
||||||
@@ -952,7 +959,7 @@ public class LoanAccountData {
|
|||||||
outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
||||||
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||||
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
|
||||||
maximumGap);
|
maximumGap, subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1003,7 +1010,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
|
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
|
||||||
acc.minimumGap, acc.maximumGap);
|
acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||||
@@ -1044,7 +1051,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
||||||
@@ -1108,7 +1115,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,7 +1148,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
|
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
||||||
@@ -1168,7 +1175,7 @@ public class LoanAccountData {
|
|||||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
|
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
|
||||||
@@ -1196,7 +1203,7 @@ public class LoanAccountData {
|
|||||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
|
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
|
||||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
|
||||||
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
|
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanAccountData(
|
private LoanAccountData(
|
||||||
@@ -1263,11 +1270,12 @@ public class LoanAccountData {
|
|||||||
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
|
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
|
||||||
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance,
|
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance,
|
||||||
final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
|
final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
|
||||||
final Integer minimumGap, final Integer maximumGap) {
|
final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus) {
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.subStatus = subStatus;
|
||||||
this.externalId = externalId;
|
this.externalId = externalId;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.clientAccountNo = clientAccountNo;
|
this.clientAccountNo = clientAccountNo;
|
||||||
|
|||||||
+253
-6
@@ -107,6 +107,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactio
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidRefundDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.InvalidRefundDateException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||||
@@ -132,6 +133,7 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
|||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.hibernate.annotations.LazyCollection;
|
import org.hibernate.annotations.LazyCollection;
|
||||||
import org.hibernate.annotations.LazyCollectionOption;
|
import org.hibernate.annotations.LazyCollectionOption;
|
||||||
|
import org.joda.time.Days;
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.joda.time.LocalDateTime;
|
import org.joda.time.LocalDateTime;
|
||||||
import org.joda.time.format.DateTimeFormat;
|
import org.joda.time.format.DateTimeFormat;
|
||||||
@@ -395,6 +397,9 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
@JoinColumn(name = "writeoff_reason_cv_id", nullable = true)
|
@JoinColumn(name = "writeoff_reason_cv_id", nullable = true)
|
||||||
private CodeValue writeOffReason;
|
private CodeValue writeOffReason;
|
||||||
|
|
||||||
|
@Column(name = "loan_sub_status_id", nullable = true)
|
||||||
|
private Integer loanSubStatus;
|
||||||
|
|
||||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
||||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||||
@@ -4814,6 +4819,14 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
dataValidationErrors.add(error);
|
dataValidationErrors.add(error);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case LOAN_FORECLOSURE:
|
||||||
|
if (!isOpen()) {
|
||||||
|
final String defaultUserMessage = "Loan foreclosure is not allowed. Loan Account is not active.";
|
||||||
|
final ApiParameterError error = ApiParameterError.generalError(
|
||||||
|
"error.msg.loan.foreclosure.account.is.not.active", defaultUserMessage);
|
||||||
|
dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -5982,12 +5995,246 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
this.writeOffReason = writeOffReason;
|
this.writeOffReason = writeOffReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Group getGroup() {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanProduct getLoanProduct() {
|
public Group getGroup() {
|
||||||
return loanProduct;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanProduct getLoanProduct() {
|
||||||
|
return loanProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoanRepaymentScheduleInstallment fetchLoanForeclosureDetail(final LocalDate closureDate) {
|
||||||
|
Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding());
|
||||||
|
Money[] receivables = retriveIncomeOutstandingTillDate(closureDate);
|
||||||
|
final List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||||
|
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
|
||||||
|
return new LoanRepaymentScheduleInstallment(null, 0, currentDate, currentDate, totalPrincipal.getAmount(),
|
||||||
|
receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, compoundingDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money[] retriveIncomeOutstandingTillDate(final LocalDate paymentDate) {
|
||||||
|
Money[] balances = new Money[3];
|
||||||
|
final MonetaryCurrency currency = getCurrency();
|
||||||
|
Money interest = Money.zero(currency);
|
||||||
|
Money fee = Money.zero(currency);
|
||||||
|
Money penalty = Money.zero(currency);
|
||||||
|
boolean isArrearsPresent = false;
|
||||||
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
if (installment.isNotFullyPaidOff()) {
|
||||||
|
if (!isArrearsPresent || !installment.getDueDate().isAfter(paymentDate)) {
|
||||||
|
interest = interest.plus(installment.getInterestOutstanding(currency));
|
||||||
|
fee = fee.plus(installment.getFeeChargesOutstanding(currency));
|
||||||
|
penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
|
||||||
|
isArrearsPresent = true;
|
||||||
|
} else if (installment.getFromDate().isBefore(paymentDate)) {
|
||||||
|
Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenalty(paymentDate, currency, installment);
|
||||||
|
interest = interest.plus(balancesForCurrentPeroid[0]);
|
||||||
|
fee = fee.plus(balancesForCurrentPeroid[1]);
|
||||||
|
penalty = penalty.plus(balancesForCurrentPeroid[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balances[0] = interest;
|
||||||
|
balances[1] = fee;
|
||||||
|
balances[2] = penalty;
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Money[] fetchInterestFeeAndPenalty(final LocalDate paymentDate, final MonetaryCurrency currency, final LoanRepaymentScheduleInstallment installment) {
|
||||||
|
Money penaltyForCurrentPeriod = Money.zero(getCurrency());
|
||||||
|
Money feeForCurrentPeriod = Money.zero(getCurrency());
|
||||||
|
int totalPeriodDays = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays();
|
||||||
|
int tillDays = Days.daysBetween(installment.getFromDate(), paymentDate).getDays();
|
||||||
|
Money interestForCurrentPeriod = Money.of(getCurrency(),BigDecimal.valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestOutstanding(getCurrency())
|
||||||
|
.getAmount(), tillDays)));
|
||||||
|
for (LoanCharge loanCharge : this.charges) {
|
||||||
|
if (loanCharge.isActive()
|
||||||
|
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate)) {
|
||||||
|
if (loanCharge.isPenaltyCharge()) {
|
||||||
|
penaltyForCurrentPeriod = loanCharge.getAmountOutstanding(getCurrency());
|
||||||
|
} else {
|
||||||
|
feeForCurrentPeriod = loanCharge.getAmountOutstanding(currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Money[] balances = new Money[3];
|
||||||
|
balances[0] = interestForCurrentPeriod;
|
||||||
|
balances[1] = feeForCurrentPeriod;
|
||||||
|
balances[2] = penaltyForCurrentPeriod;
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) {
|
||||||
|
Money[] balances = new Money[3];
|
||||||
|
final MonetaryCurrency currency = getCurrency();
|
||||||
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
if (installment.getDueDate().isEqual(paymentDate)){
|
||||||
|
Money interest = installment.getInterestOutstanding(currency);
|
||||||
|
Money fee = installment.getFeeChargesOutstanding(currency);
|
||||||
|
Money penalty = installment.getPenaltyChargesOutstanding(currency);
|
||||||
|
balances[0] = interest;
|
||||||
|
balances[1] = fee;
|
||||||
|
balances[2] = penalty;
|
||||||
|
break;
|
||||||
|
}else if(installment.getDueDate().isAfter(paymentDate) && installment.getFromDate().isBefore(paymentDate)){
|
||||||
|
balances = fetchInterestFeeAndPenalty(paymentDate, currency, installment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
private double calculateInterestForDays(int daysInPeriod, BigDecimal interest, int days) {
|
||||||
|
if (interest.doubleValue() == 0) { return 0; }
|
||||||
|
return ((interest.doubleValue()) / daysInPeriod) * days;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money[] getReceivableIncome(final LocalDate tillDate) {
|
||||||
|
MonetaryCurrency currency = getCurrency();
|
||||||
|
Money receivableInterest = Money.zero(currency);
|
||||||
|
Money receivableFee = Money.zero(currency);
|
||||||
|
Money receivablePenalty = Money.zero(currency);
|
||||||
|
Money[] receivables = new Money[3];
|
||||||
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
|
if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement()
|
||||||
|
&& !transaction.getTransactionDate().isAfter(tillDate)) {
|
||||||
|
if (transaction.isAccrual()) {
|
||||||
|
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(currency));
|
||||||
|
receivableFee = receivableFee.plus(transaction.getFeeChargesPortion(currency));
|
||||||
|
receivablePenalty = receivablePenalty.plus(transaction.getPenaltyChargesPortion(currency));
|
||||||
|
} else if (transaction.isRepayment() || transaction.isChargePayment()) {
|
||||||
|
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(currency));
|
||||||
|
receivableFee = receivableFee.minus(transaction.getFeeChargesPortion(currency));
|
||||||
|
receivablePenalty = receivablePenalty.minus(transaction.getPenaltyChargesPortion(currency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (receivableInterest.isLessThanZero()) {
|
||||||
|
receivableInterest = receivableInterest.zero();
|
||||||
|
}
|
||||||
|
if (receivableFee.isLessThanZero()) {
|
||||||
|
receivableFee = receivableFee.zero();
|
||||||
|
}
|
||||||
|
if (receivablePenalty.isLessThanZero()) {
|
||||||
|
receivablePenalty = receivablePenalty.zero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
receivables[0] = receivableInterest;
|
||||||
|
receivables[1] = receivableFee;
|
||||||
|
receivables[2] = receivablePenalty;
|
||||||
|
return receivables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleForeClosureTransactions(final List<LoanTransaction> repaymentTransaction,
|
||||||
|
final LocalDate foreClosureDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
|
|
||||||
|
LoanEvent event = LoanEvent.LOAN_FORECLOSURE;
|
||||||
|
|
||||||
|
validateAccountStatus(event);
|
||||||
|
|
||||||
|
MonetaryCurrency currency = getCurrency();
|
||||||
|
|
||||||
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||||
|
.determineProcessor(this.transactionProcessingStrategy);
|
||||||
|
|
||||||
|
loanRepaymentScheduleTransactionProcessor.processTransactionsFromDerivedFields(repaymentTransaction, currency,
|
||||||
|
this.repaymentScheduleInstallments, charges());
|
||||||
|
this.loanTransactions.addAll(repaymentTransaction);
|
||||||
|
this.loanSubStatus = LoanSubStatus.FORECLOSED.getValue();
|
||||||
|
updateLoanSummaryDerivedFields();
|
||||||
|
doPostLoanTransactionChecks(foreClosureDate, loanLifecycleStateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money retrieveAccruedAmountAfterDate(final LocalDate tillDate) {
|
||||||
|
Money totalAmountAccrued = Money.zero(getCurrency());
|
||||||
|
Money actualAmountTobeAccrued = Money.zero(getCurrency());
|
||||||
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
totalAmountAccrued = totalAmountAccrued.plus(installment.getInterestAccrued(getCurrency()));
|
||||||
|
|
||||||
|
if (tillDate.isAfter(installment.getFromDate()) && tillDate.isBefore(installment.getDueDate())) {
|
||||||
|
int daysInPeriod = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays();
|
||||||
|
int tillDays = Days.daysBetween(installment.getFromDate(), tillDate).getDays();
|
||||||
|
double interest = calculateInterestForDays(daysInPeriod, installment.getInterestCharged(getCurrency()).getAmount(),
|
||||||
|
tillDays);
|
||||||
|
actualAmountTobeAccrued = actualAmountTobeAccrued.plus(interest);
|
||||||
|
} else if ((tillDate.isAfter(installment.getFromDate()) && tillDate.isEqual(installment.getDueDate()))
|
||||||
|
|| (tillDate.isEqual(installment.getFromDate()) && tillDate.isEqual(installment.getDueDate()))
|
||||||
|
|| (tillDate.isAfter(installment.getFromDate()) && tillDate.isAfter(installment.getDueDate()))) {
|
||||||
|
actualAmountTobeAccrued = actualAmountTobeAccrued.plus(installment.getInterestAccrued(getCurrency()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Money accredAmountAfterDate = totalAmountAccrued.minus(actualAmountTobeAccrued);
|
||||||
|
if (accredAmountAfterDate.isLessThanZero()) {
|
||||||
|
accredAmountAfterDate = Money.zero(getCurrency());
|
||||||
|
}
|
||||||
|
return accredAmountAfterDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForForeclosure(final LocalDate transactionDate) {
|
||||||
|
|
||||||
|
if (isInterestRecalculationEnabledForProduct()) {
|
||||||
|
final String defaultUserMessage = "The loan with interest recalculation enabled cannot be foreclosed.";
|
||||||
|
throw new LoanForeclosureException("loan.with.interest.recalculation.enabled.cannot.be.foreclosured", defaultUserMessage,
|
||||||
|
getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate lastUserTransactionDate = getLastUserTransactionDate();
|
||||||
|
|
||||||
|
if (DateUtils.isDateInTheFuture(transactionDate)) {
|
||||||
|
final String defaultUserMessage = "The transactionDate cannot be in the future.";
|
||||||
|
throw new LoanForeclosureException("loan.foreclosure.transaction.date.is.in.future", defaultUserMessage, transactionDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastUserTransactionDate.isAfter(transactionDate)) {
|
||||||
|
final String defaultUserMessage = "The transactionDate cannot be in the future.";
|
||||||
|
throw new LoanForeclosureException("loan.foreclosure.transaction.date.cannot.before.the.last.transaction.date",
|
||||||
|
defaultUserMessage, transactionDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateInstallmentsPostDate(LocalDate transactionDate) {
|
||||||
|
List<LoanRepaymentScheduleInstallment> newInstallments = new ArrayList<>(this.repaymentScheduleInstallments);
|
||||||
|
final MonetaryCurrency currency = getCurrency();
|
||||||
|
Money totalPrincipal = Money.zero(currency);
|
||||||
|
Money [] balances = retriveIncomeForOverlappingPeriod(transactionDate);
|
||||||
|
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
|
||||||
|
if (!installment.getDueDate().isBefore(transactionDate)) {
|
||||||
|
totalPrincipal = totalPrincipal.plus(installment.getPrincipalOutstanding(currency));
|
||||||
|
newInstallments.remove(installment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoanRepaymentScheduleInstallment newInstallment = new LoanRepaymentScheduleInstallment(null, newInstallments.size() + 1,
|
||||||
|
newInstallments.get((newInstallments.size() - 1)).getDueDate(), transactionDate, totalPrincipal.getAmount(),
|
||||||
|
balances[0].getAmount(), balances[1].getAmount(), balances[2].getAmount(), true, null);
|
||||||
|
newInstallment.updateInstallmentNumber(newInstallments.size() + 1);
|
||||||
|
newInstallments.add(newInstallment);
|
||||||
|
updateLoanScheduleOnForeclosure(newInstallments);
|
||||||
|
|
||||||
|
Set<LoanCharge> charges = this.charges();
|
||||||
|
int penaltyWaitPeriod = 0;
|
||||||
|
for (LoanCharge loanCharge : charges) {
|
||||||
|
if (loanCharge.getDueLocalDate() != null
|
||||||
|
&& (loanCharge.getDueLocalDate().isAfter(transactionDate))) {
|
||||||
|
loanCharge.setActive(false);
|
||||||
|
} else if (loanCharge.getDueLocalDate() == null) {
|
||||||
|
recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLoanScheduleOnForeclosure(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
this.repaymentScheduleInstallments.clear();
|
||||||
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
|
addRepaymentScheduleInstallment(installment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLoanSubStatus() {
|
||||||
|
return this.loanSubStatus;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
@@ -56,4 +57,5 @@ public interface LoanAccountDomainService {
|
|||||||
|
|
||||||
void saveLoanWithDataIntegrityViolationChecks(Loan loan);
|
void saveLoanWithDataIntegrityViolationChecks(Loan loan);
|
||||||
|
|
||||||
|
Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClourseDate, String noteText);
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-1
@@ -19,11 +19,12 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -574,9 +575,118 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
return newRefundTransaction;
|
return newRefundTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText) {
|
||||||
|
MonetaryCurrency currency = loan.getCurrency();
|
||||||
|
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||||
|
List<LoanTransaction> newTransactions = new ArrayList<>();
|
||||||
|
|
||||||
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
|
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
||||||
|
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
||||||
|
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
||||||
|
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
|
||||||
|
&& (loan.getAccruedTill() == null || !foreClosureDate.isEqual(loan.getAccruedTill()))) {
|
||||||
|
Money[] accruedReceivables = loan.getReceivableIncome(foreClosureDate);
|
||||||
|
Money interestPortion = foreCloseDetail.getInterestCharged(currency).minus(accruedReceivables[0]);
|
||||||
|
Money feePortion = foreCloseDetail.getFeeChargesCharged(currency).minus(accruedReceivables[1]);
|
||||||
|
Money penaltyPortion = foreCloseDetail.getPenaltyChargesCharged(currency).minus(accruedReceivables[2]);
|
||||||
|
Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
|
||||||
|
if (total.isGreaterThanZero()) {
|
||||||
|
LoanTransaction accrualTransaction = LoanTransaction.accrual(loan, loan.getOffice(), total, interestPortion, feePortion,
|
||||||
|
penaltyPortion, foreClosureDate);
|
||||||
|
LocalDate fromDate = loan.getDisbursementDate();
|
||||||
|
if (loan.getAccruedTill() != null) {
|
||||||
|
fromDate = loan.getAccruedTill();
|
||||||
|
}
|
||||||
|
LoanRepaymentScheduleInstallment installment = fetchLoanRepaymentScheduleInstallment(fromDate, loan);
|
||||||
|
installment.updateAccrualPortion(interestPortion, feePortion, penaltyPortion);
|
||||||
|
accrualTransaction.updateCreatedDate(createdDate.toDate());
|
||||||
|
createdDate = createdDate.plusSeconds(1);
|
||||||
|
newTransactions.add(accrualTransaction);
|
||||||
|
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
|
||||||
|
for (LoanCharge loanCharge : loan.charges()) {
|
||||||
|
if (loanCharge.isActive()
|
||||||
|
&& !loanCharge.isPaid()
|
||||||
|
&& (loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate) || loanCharge
|
||||||
|
.isInstalmentFee())) {
|
||||||
|
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge, loanCharge
|
||||||
|
.getAmountOutstanding(currency).getAmount(), null);
|
||||||
|
accrualCharges.add(loanChargePaidBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Money interestPayable = foreCloseDetail.getInterestCharged(currency);
|
||||||
|
|
||||||
|
Money feePayable = foreCloseDetail.getFeeChargesCharged(currency);
|
||||||
|
Money penaltyPayable = foreCloseDetail.getPenaltyChargesCharged(currency);
|
||||||
|
|
||||||
|
Money payPrincipal = foreCloseDetail.getPrincipal(currency);
|
||||||
|
|
||||||
|
LoanTransaction payment = null;
|
||||||
|
if (payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable).isGreaterThanZero()) {
|
||||||
|
AppUser appUser = null;
|
||||||
|
final PaymentDetail paymentDetail = null;
|
||||||
|
String externalId = null;
|
||||||
|
final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
payment = LoanTransaction.repayment(loan.getOffice(), payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable),
|
||||||
|
paymentDetail, foreClosureDate, externalId, currentDateTime, appUser);
|
||||||
|
createdDate = createdDate.plusSeconds(1);
|
||||||
|
payment.updateCreatedDate(createdDate.toDate());
|
||||||
|
payment.updateComponents(payPrincipal, interestPayable, feePayable, penaltyPayable);
|
||||||
|
payment.updateLoan(loan);
|
||||||
|
newTransactions.add(payment);
|
||||||
|
}
|
||||||
|
List<Long> transactionIds = new ArrayList<>();
|
||||||
|
loan.handleForeClosureTransactions(newTransactions, foreClosureDate, defaultLoanLifecycleStateMachine());
|
||||||
|
for (LoanTransaction newTransaction : newTransactions) {
|
||||||
|
saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
|
||||||
|
transactionIds.add(newTransaction.getId());
|
||||||
|
}
|
||||||
|
changes.put("transactions", transactionIds);
|
||||||
|
changes.put("eventAmount", payPrincipal.getAmount().negate());
|
||||||
|
|
||||||
|
/***
|
||||||
|
* TODO Vishwas Batch save is giving me a
|
||||||
|
* HibernateOptimisticLockingFailureException, looping and saving for
|
||||||
|
* the time being, not a major issue for now as this loop is entered
|
||||||
|
* only in edge cases (when a payment is made before the latest payment
|
||||||
|
* recorded against the loan)
|
||||||
|
***/
|
||||||
|
|
||||||
|
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(noteText)) {
|
||||||
|
changes.put("note", noteText);
|
||||||
|
final Note note = Note.loanNote(loan, noteText);
|
||||||
|
this.noteRepository.save(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate fromDate, final Loan loan) {
|
||||||
|
LoanRepaymentScheduleInstallment installment = null;
|
||||||
|
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
|
||||||
|
if (fromDate.equals(loanRepaymentScheduleInstallment.getFromDate())) {
|
||||||
|
installment = loanRepaymentScheduleInstallment;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return installment;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
|
private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
|
||||||
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
|
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
|
||||||
map.put(entityEvent, entity);
|
map.put(entityEvent, entity);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-1
@@ -42,5 +42,6 @@ public enum LoanEvent {
|
|||||||
LOAN_CHARGE_PAYMENT, //
|
LOAN_CHARGE_PAYMENT, //
|
||||||
LOAN_CLOSED, //
|
LOAN_CLOSED, //
|
||||||
LOAN_EDIT_MULTI_DISBURSE_DATE, //
|
LOAN_EDIT_MULTI_DISBURSE_DATE, //
|
||||||
LOAN_REFUND;
|
LOAN_REFUND, //
|
||||||
|
LOAN_FORECLOSURE;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -785,4 +785,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
|||||||
public List<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
public List<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||||
return this.loanCompoundingDetails;
|
return this.loanCompoundingDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money getAccruedInterestOutstanding(final MonetaryCurrency currency) {
|
||||||
|
final Money interestAccountedFor = getInterestPaid(currency).plus(getInterestWaived(currency))
|
||||||
|
.plus(getInterestWrittenOff(currency));
|
||||||
|
return getInterestAccrued(currency).minus(interestAccountedFor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 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.domain;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
|
|
||||||
|
public enum LoanSubStatus {
|
||||||
|
INVALID(0, "loanSubStatusType.invalid"), //
|
||||||
|
FORECLOSED(100, "loanSubStatusType.foreclosed");
|
||||||
|
|
||||||
|
private final Integer value;
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
public static LoanSubStatus fromInt(final Integer statusValue) {
|
||||||
|
|
||||||
|
LoanSubStatus enumeration = LoanSubStatus.INVALID;
|
||||||
|
switch (statusValue) {
|
||||||
|
case 100:
|
||||||
|
enumeration = LoanSubStatus.FORECLOSED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return enumeration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoanSubStatus(final Integer value, final String code) {
|
||||||
|
this.value = value;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasStateOf(final LoanSubStatus state) {
|
||||||
|
return this.value.equals(state.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForeclosed() {
|
||||||
|
return this.value.equals(LoanSubStatus.FORECLOSED.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumOptionData loanSubStatus(final int id) {
|
||||||
|
return loanSubStatusEnum(LoanSubStatus.fromInt(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumOptionData loanSubStatusEnum(final LoanSubStatus type) {
|
||||||
|
final String codePrefix = "loanSubStatus.";
|
||||||
|
EnumOptionData optionData = null;
|
||||||
|
switch (type) {
|
||||||
|
case FORECLOSED:
|
||||||
|
optionData = new EnumOptionData(LoanSubStatus.FORECLOSED.getValue().longValue(), codePrefix
|
||||||
|
+ LoanSubStatus.FORECLOSED.getCode(), "Foreclosed");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
optionData = new EnumOptionData(LoanSubStatus.INVALID.getValue().longValue(), LoanSubStatus.INVALID.getCode(), "Invalid");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return optionData;
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
-49
@@ -18,6 +18,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -36,26 +57,6 @@ import org.joda.time.LocalDate;
|
|||||||
import org.joda.time.LocalDateTime;
|
import org.joda.time.LocalDateTime;
|
||||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
import javax.persistence.Temporal;
|
|
||||||
import javax.persistence.TemporalType;
|
|
||||||
import javax.persistence.UniqueConstraint;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All monetary transactions against a loan are modelled through this entity.
|
* All monetary transactions against a loan are modelled through this entity.
|
||||||
* Disbursements, Repayments, Waivers, Write-off etc
|
* Disbursements, Repayments, Waivers, Write-off etc
|
||||||
@@ -116,7 +117,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "created_date", nullable = false)
|
@Column(name = "created_date", nullable = false)
|
||||||
private final Date createdDate;
|
private Date createdDate;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "appuser_id", nullable = true)
|
@JoinColumn(name = "appuser_id", nullable = true)
|
||||||
@@ -133,8 +134,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
private boolean manuallyAdjustedOrReversed;
|
private boolean manuallyAdjustedOrReversed;
|
||||||
|
|
||||||
@LazyCollection(LazyCollectionOption.FALSE)
|
@LazyCollection(LazyCollectionOption.FALSE)
|
||||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
@JoinColumn(name = "loan_transaction_id", referencedColumnName= "id" , nullable = false)
|
@JoinColumn(name = "loan_transaction_id", referencedColumnName = "id", nullable = false)
|
||||||
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
|
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
|
||||||
|
|
||||||
protected LoanTransaction() {
|
protected LoanTransaction() {
|
||||||
@@ -146,19 +147,20 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
this.appUser = null;
|
this.appUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf,
|
public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf, final BigDecimal amount,
|
||||||
final BigDecimal amount, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
|
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
|
||||||
final BigDecimal penaltyChargesPortion, final AppUser appUser){
|
final AppUser appUser) {
|
||||||
final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
|
final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
|
||||||
final BigDecimal principalPortion = BigDecimal.ZERO;
|
final BigDecimal principalPortion = BigDecimal.ZERO;
|
||||||
final BigDecimal overPaymentPortion = BigDecimal.ZERO;
|
final BigDecimal overPaymentPortion = BigDecimal.ZERO;
|
||||||
final boolean reversed = false;
|
final boolean reversed = false;
|
||||||
final PaymentDetail paymentDetail = null;
|
final PaymentDetail paymentDetail = null;
|
||||||
final String externalId = null;
|
final String externalId = null;
|
||||||
final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
|
final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
|
return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
|
||||||
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser);
|
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
|
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
|
||||||
final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
|
final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) {
|
||||||
return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
|
return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
|
||||||
@@ -212,17 +214,25 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
|
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
|
||||||
createdDate, appUser);
|
createdDate, appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest,
|
||||||
|
final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate) {
|
||||||
|
final AppUser appUser = null;
|
||||||
|
return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(),
|
||||||
|
penaltyCharges.getAmount(), appUser);
|
||||||
|
}
|
||||||
|
|
||||||
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||||
final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
|
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
|
||||||
final BigDecimal penaltyChargesPortion, final AppUser appUser) {
|
final AppUser appUser) {
|
||||||
BigDecimal principalPortion = null;
|
BigDecimal principalPortion = null;
|
||||||
BigDecimal overPaymentPortion = null;
|
BigDecimal overPaymentPortion = null;
|
||||||
boolean reversed = false;
|
boolean reversed = false;
|
||||||
PaymentDetail paymentDetail = null;
|
PaymentDetail paymentDetail = null;
|
||||||
String externalId = null;
|
String externalId = null;
|
||||||
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
|
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf.toDate(), amount,
|
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf.toDate(), amount, principalPortion,
|
||||||
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
|
interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
|
||||||
createdDate, appUser);
|
createdDate, appUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,14 +675,13 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
|
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNonMonetaryTransaction(){
|
public boolean isNonMonetaryTransaction() {
|
||||||
return isNotReversed()
|
return isNotReversed()
|
||||||
&& (LoanTransactionType.CONTRA.equals(getTypeOf())
|
&& (LoanTransactionType.CONTRA.equals(getTypeOf()) || LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|
||||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
|
||||||
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
|
|| LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|
||||||
|| LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER
|
||||||
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf())
|
.equals(getTypeOf()));
|
||||||
|| LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||||
@@ -756,13 +765,19 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
|||||||
return isMappingUpdated;
|
return isMappingUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
|
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
|
||||||
return this.loanTransactionToRepaymentScheduleMappings;
|
return this.loanTransactionToRepaymentScheduleMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo(){
|
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
||||||
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
|
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateCreatedDate(Date createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualTransaction() {
|
||||||
|
return isAccrual();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+86
@@ -627,4 +627,90 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
return latestPaidCharge;
|
return latestPaidCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
|
||||||
|
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
|
||||||
|
if (!loanTransaction.isAccrualTransaction()) {
|
||||||
|
processTransactionFromDerivedFields(loanTransaction, currency, installments, charges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTransactionFromDerivedFields(final LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
|
||||||
|
Money principal = loanTransaction.getPrincipalPortion(currency);
|
||||||
|
Money interest = loanTransaction.getInterestPortion(currency);
|
||||||
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
|
interest = loanTransaction.getAmount(currency);
|
||||||
|
}
|
||||||
|
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||||
|
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
||||||
|
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||||
|
if (principal.isGreaterThanZero() || interest.isGreaterThanZero() || feeCharges.isGreaterThanZero()
|
||||||
|
|| penaltyCharges.isGreaterThanZero()) {
|
||||||
|
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||||
|
if (currentInstallment.isNotFullyPaidOff()) {
|
||||||
|
if (penaltyCharges.isGreaterThanZero()) {
|
||||||
|
Money penaltyChargesPortion = Money.zero(currency);
|
||||||
|
if (loanTransaction.isWaiver()) {
|
||||||
|
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, penaltyCharges);
|
||||||
|
} else {
|
||||||
|
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, penaltyCharges);
|
||||||
|
}
|
||||||
|
penaltyCharges = penaltyCharges.minus(penaltyChargesPortion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feeCharges.isGreaterThanZero()) {
|
||||||
|
Money feeChargesPortion = Money.zero(currency);
|
||||||
|
if (loanTransaction.isWaiver()) {
|
||||||
|
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, feeCharges);
|
||||||
|
} else {
|
||||||
|
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, feeCharges);
|
||||||
|
}
|
||||||
|
feeCharges = feeCharges.minus(feeChargesPortion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interest.isGreaterThanZero()) {
|
||||||
|
Money interestPortion = Money.zero(currency);
|
||||||
|
if (loanTransaction.isWaiver()) {
|
||||||
|
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, interest);
|
||||||
|
} else {
|
||||||
|
interestPortion = currentInstallment.payInterestComponent(transactionDate, interest);
|
||||||
|
}
|
||||||
|
interest = interest.minus(interestPortion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principal.isGreaterThanZero()) {
|
||||||
|
Money principalPortion = currentInstallment.payPrincipalComponent(transactionDate, principal);
|
||||||
|
principal = principal.minus(principalPortion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(principal.isGreaterThanZero() || interest.isGreaterThanZero() || feeCharges.isGreaterThanZero() || penaltyCharges
|
||||||
|
.isGreaterThanZero())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
||||||
|
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
||||||
|
Integer installmentNumber = null;
|
||||||
|
if (loanTransaction.isChargePayment() && installments.size() == 1) {
|
||||||
|
installmentNumber = installments.get(0).getInstallmentNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loanTransaction.isNotWaiver()) {
|
||||||
|
feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||||
|
penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
||||||
|
if (feeCharges.isGreaterThanZero()) {
|
||||||
|
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (penaltyCharges.isGreaterThanZero()) {
|
||||||
|
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+3
@@ -52,4 +52,7 @@ public interface LoanRepaymentScheduleTransactionProcessor {
|
|||||||
void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||||
final Set<LoanCharge> charges);
|
final Set<LoanCharge> charges);
|
||||||
|
|
||||||
|
void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges);
|
||||||
|
|
||||||
}
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class LoanForeclosureException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public LoanForeclosureException(final String errorCode, final String errorMessage, final Object... defaultUserMessageArgs) {
|
||||||
|
super(errorCode, errorMessage, defaultUserMessageArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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.handler;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOAN", action = "FORECLOSURE")
|
||||||
|
public class ForeClosureCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LoanWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ForeClosureCommandHandler(final LoanWritePlatformService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return writePlatformService.forecloseLoan(command.getLoanId(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+22
@@ -447,4 +447,26 @@ public final class LoanEventApiJsonValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateLoanForeclosure(final String json) {
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
|
|
||||||
|
final Set<String> foreclosureParameters = new HashSet<>(Arrays.asList("transactionDate", "note", "locale", "dateFormat"));
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, foreclosureParameters);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||||
|
|
||||||
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
|
||||||
|
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
|
||||||
|
|
||||||
|
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||||
|
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||||
|
|
||||||
|
validatePaymentDetails(baseDataValidator, element);
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+3
@@ -126,5 +126,8 @@ public interface LoanReadPlatformService {
|
|||||||
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
|
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
|
||||||
|
|
||||||
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
|
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
|
||||||
|
|
||||||
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
|
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
|
||||||
|
|
||||||
|
LoanTransactionData retrieveLoanForeclosureTemplate(final Long loanId, final LocalDate transactionDate);
|
||||||
}
|
}
|
||||||
+40
-2
@@ -93,6 +93,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
@@ -592,6 +593,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
+ " l.total_overpaid_derived as totalOverpaid,"
|
+ " l.total_overpaid_derived as totalOverpaid,"
|
||||||
+ " l.fixed_emi_amount as fixedEmiAmount,"
|
+ " l.fixed_emi_amount as fixedEmiAmount,"
|
||||||
+ " l.max_outstanding_loan_balance as outstandingLoanBalance,"
|
+ " l.max_outstanding_loan_balance as outstandingLoanBalance,"
|
||||||
|
+ " l.loan_sub_status_id as loanSubStatusId,"
|
||||||
+ " la.principal_overdue_derived as principalOverdue,"
|
+ " la.principal_overdue_derived as principalOverdue,"
|
||||||
+ " la.interest_overdue_derived as interestOverdue,"
|
+ " la.interest_overdue_derived as interestOverdue,"
|
||||||
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
|
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
|
||||||
@@ -783,6 +785,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
final Integer lifeCycleStatusId = JdbcSupport.getInteger(rs, "lifeCycleStatusId");
|
final Integer lifeCycleStatusId = JdbcSupport.getInteger(rs, "lifeCycleStatusId");
|
||||||
final LoanStatusEnumData status = LoanEnumerations.status(lifeCycleStatusId);
|
final LoanStatusEnumData status = LoanEnumerations.status(lifeCycleStatusId);
|
||||||
|
|
||||||
|
final Integer loanSubStatusId = JdbcSupport.getInteger(rs, "loanSubStatusId");
|
||||||
|
EnumOptionData loanSubStatus = null;
|
||||||
|
if (loanSubStatusId != null) {
|
||||||
|
loanSubStatus = LoanSubStatus.loanSubStatus(loanSubStatusId);
|
||||||
|
}
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
|
final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate");
|
||||||
final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
|
final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate");
|
||||||
@@ -936,7 +944,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears,
|
loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears,
|
||||||
graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||||
interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap,
|
interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap,
|
||||||
maximumGap);
|
maximumGap, loanSubStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2028,7 +2036,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
|
|
||||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
|
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN);
|
||||||
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
|
|
||||||
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
||||||
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null,
|
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null,
|
||||||
null, null, null, false);
|
null, null, null, false);
|
||||||
@@ -2100,4 +2107,35 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoanTransactionData retrieveLoanForeclosureTemplate(final Long loanId, final LocalDate transactionDate) {
|
||||||
|
this.context.authenticatedUser();
|
||||||
|
|
||||||
|
final Loan loan = this.loanRepository.findOne(loanId);
|
||||||
|
if (loan == null) { throw new LoanNotFoundException(loanId); }
|
||||||
|
loan.validateForForeclosure(transactionDate);
|
||||||
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
|
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||||
|
|
||||||
|
final CurrencyData currencyData = applicationCurrency.toData();
|
||||||
|
|
||||||
|
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getLocalDateOfTenant();
|
||||||
|
|
||||||
|
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchLoanForeclosureDetail(transactionDate);
|
||||||
|
BigDecimal unrecognizedIncomePortion = null;
|
||||||
|
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
|
||||||
|
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
|
final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
|
||||||
|
final Boolean isReversed = false;
|
||||||
|
|
||||||
|
final Money outStandingAmount = loanRepaymentScheduleInstallment.getTotalOutstanding(currency);
|
||||||
|
|
||||||
|
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
||||||
|
outStandingAmount.getAmount(), loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(),
|
||||||
|
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
|
||||||
|
.getFeeChargesOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(
|
||||||
|
currency).getAmount(), null, unrecognizedIncomePortion, paymentTypeOptions, null, null, null,
|
||||||
|
outstandingLoanBalance, isReversed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -104,6 +104,8 @@ public interface LoanWritePlatformService {
|
|||||||
|
|
||||||
void recalculateInterest(long loanId);
|
void recalculateInterest(long loanId);
|
||||||
|
|
||||||
CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
|
CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult forecloseLoan(final Long loanId, JsonCommand command);
|
||||||
|
|
||||||
}
|
}
|
||||||
+34
@@ -137,6 +137,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
@@ -145,6 +146,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
|
import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||||
@@ -822,6 +824,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
this.loanEventApiJsonValidator.validateTransaction(command.json());
|
||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
if(loan.status().isClosed() && loan.getLoanSubStatus().equals(LoanSubStatus.FORECLOSED.getValue())) {
|
||||||
|
final String defaultUserMessage = "The loan cannot reopend as it is foreclosed.";
|
||||||
|
throw new LoanForeclosureException("loan.cannot.be.reopened.as.it.is.foreclosured", defaultUserMessage,
|
||||||
|
loanId);
|
||||||
|
}
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
|
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findOne(transactionId);
|
||||||
if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
|
if (transactionToAdjust == null) { throw new LoanTransactionNotFoundException(transactionId); }
|
||||||
@@ -2883,6 +2890,33 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CommandProcessingResult forecloseLoan(final Long loanId, final JsonCommand command) {
|
||||||
|
final String json = command.json();
|
||||||
|
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||||
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.transactionDateParamName, element);
|
||||||
|
this.loanEventApiJsonValidator.validateLoanForeclosure(command.json());
|
||||||
|
loan.validateForForeclosure(transactionDate);
|
||||||
|
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||||
|
changes.put("transactionDate", transactionDate);
|
||||||
|
|
||||||
|
String noteText = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParamName, element);
|
||||||
|
LoanRescheduleRequest loanRescheduleRequest = null;
|
||||||
|
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.getRepaymentScheduleInstallments(),
|
||||||
|
loan, loanRescheduleRequest);
|
||||||
|
loan.updateInstallmentsPostDate(transactionDate);
|
||||||
|
|
||||||
|
final Map<String, Object> modifications = this.loanAccountDomainService.foreCloseLoan(loan, transactionDate, noteText);
|
||||||
|
changes.putAll(modifications);
|
||||||
|
|
||||||
|
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
|
||||||
|
return commandProcessingResultBuilder.withLoanId(loanId) //
|
||||||
|
.with(changes) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
|
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
|
||||||
if (!loan.isMultiDisburmentLoan()) {
|
if (!loan.isMultiDisburmentLoan()) {
|
||||||
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
|
||||||
|
('portfolio', 'FORECLOSURE_LOAN', 'LOAN', 'FORECLOSURE', 0),
|
||||||
|
('portfolio', 'FORECLOSURE_LOAN_CHECKER', 'LOAN', 'FORECLOSURE_CHECKER', 0);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `m_loan`
|
||||||
|
ADD COLUMN `loan_sub_status_id` SMALLINT(5) NULL DEFAULT NULL;
|
||||||
Reference in New Issue
Block a user