You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by na...@apache.org on 2016/06/13 12:28:56 UTC
[2/3] incubator-fineract git commit: [FINERACT-167] Loan foreclosure
implementation and integration test cases for the same.
[FINERACT-167] Loan foreclosure implementation and integration test cases for the same.
Project: http://git-wip-us.apache.org/repos/asf/incubator-fineract/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-fineract/commit/b9b345a5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-fineract/tree/b9b345a5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-fineract/diff/b9b345a5
Branch: refs/heads/develop
Commit: b9b345a563a5e14583fb4fbac7710addfc422af1
Parents: b43d3ef
Author: sachinkulkarni12 <sa...@confluxtechnologies.com>
Authored: Fri Jun 10 18:29:02 2016 +0530
Committer: sachinkulkarni12 <sa...@confluxtechnologies.com>
Committed: Fri Jun 10 18:59:26 2016 +0530
----------------------------------------------------------------------
api-docs/apiLive.htm | 51 ++++
.../ClientLoanIntegrationTest.java | 53 ++++
.../common/loans/LoanStatusChecker.java | 14 +
.../common/loans/LoanTransactionHelper.java | 17 ++
.../commands/service/CommandWrapperBuilder.java | 9 +
.../loanaccount/api/LoanApiConstants.java | 4 +
.../api/LoanTransactionsApiResource.java | 11 +
.../loanaccount/data/LoanAccountData.java | 40 +--
.../portfolio/loanaccount/domain/Loan.java | 259 ++++++++++++++++++-
.../domain/LoanAccountDomainService.java | 2 +
.../domain/LoanAccountDomainServiceJpa.java | 112 +++++++-
.../portfolio/loanaccount/domain/LoanEvent.java | 3 +-
.../LoanRepaymentScheduleInstallment.java | 6 +
.../loanaccount/domain/LoanSubStatus.java | 80 ++++++
.../loanaccount/domain/LoanTransaction.java | 117 +++++----
...anRepaymentScheduleTransactionProcessor.java | 86 ++++++
...anRepaymentScheduleTransactionProcessor.java | 3 +
.../exception/LoanForeclosureException.java | 28 ++
.../handler/ForeClosureCommandHandler.java | 45 ++++
.../LoanEventApiJsonValidator.java | 22 ++
.../service/LoanReadPlatformService.java | 3 +
.../service/LoanReadPlatformServiceImpl.java | 42 ++-
.../service/LoanWritePlatformService.java | 4 +-
...anWritePlatformServiceJpaRepositoryImpl.java | 34 +++
.../core_db/V311__foreclosure_details.sql | 7 +
25 files changed, 974 insertions(+), 78 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/api-docs/apiLive.htm
----------------------------------------------------------------------
diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index a6320c8..696e066 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -1304,6 +1304,14 @@
<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>
<td><a href="#loans_charges">Loan Charges</a></td>
<td>loans/{loanId}/charges</td>
@@ -10141,6 +10149,13 @@ No Request Body:
"total" "amount" is set
to the total amount paid in advance.
</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>
<p>Example Request:</p>
<div class=apiClick>loans/1/transactions/template?command=repayment</div>
@@ -10266,6 +10281,42 @@ Request Body:
</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>
<div class="method-section">
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 5696143..9a241a2 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -4956,6 +4956,59 @@ public class ClientLoanIntegrationTest {
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) {
String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
index 3535dfe..72d9ce8 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanStatusChecker.java
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests.common.loans;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import java.util.HashMap;
@@ -59,14 +60,27 @@ public class LoanStatusChecker {
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,
final Integer loanID) {
final String url = "/fineract-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
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) {
return (Boolean) loanStatusMap.get(nameOfLoanStatusString);
}
+ private static String getSubStatus(final HashMap loanStatusMap, final String nameOfLoanStatusString) {
+ return (String) loanStatusMap.get(nameOfLoanStatusString);
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index f416bfc..4f2c8cb 100755
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -52,6 +52,7 @@ public class LoanTransactionHelper {
private static final String WITHDRAW_LOAN_APPLICATION_COMMAND = "withdrawnByApplicant";
private static final String RECOVER_FROM_GUARANTORS_COMMAND = "recoverGuarantees";
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) {
this.requestSpec = requestSpec;
@@ -217,6 +218,11 @@ public class LoanTransactionHelper {
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) {
return performLoanTransaction(createLoanOperationURL(WITHDRAW_LOAN_APPLICATION_COMMAND, loanID),
getWithdrawLoanApplicationBodyAsJSON(date));
@@ -326,6 +332,17 @@ public class LoanTransactionHelper {
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) {
final HashMap<String, String> map = new HashMap<>();
map.put("dateFormat", "dd MMMM yyyy");
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 5751e0e..3b26e1d 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -723,6 +723,15 @@ public class CommandWrapperBuilder {
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() {
this.actionName = "CREATE";
this.entityName = "LOAN";
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 5f28010..ee38428 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -113,4 +113,8 @@ public interface LoanApiConstants {
//loan write off
public static final String WRITEOFFREASONS = "WriteOffReasons";
+ // fore closure constants
+ public static final String transactionDateParamName = "transactionDate";
+ public static final String noteParamName = "note";
+
}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 04603c4..e4b3036 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -129,6 +129,14 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
} else if (is(commandParam, "refundbytransfer")) {
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 {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -189,6 +197,9 @@ public class LoanTransactionsApiResource {
} else if (is(commandParam, "refundByCash")) {
final CommandWrapper commandRequest = builder.refundLoanTransactionByCash(loanId).build();
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); }
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 5975ec3..555ed3b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -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.group.data.GroupGeneralData;
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.loanschedule.data.LoanScheduleData;
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.LoanProductValueConditionType;
import org.apache.fineract.portfolio.note.data.NoteData;
+import org.hibernate.dialect.Sybase11Dialect;
import org.joda.time.LocalDate;
import org.springframework.util.CollectionUtils;
@@ -65,6 +67,7 @@ public class LoanAccountData {
// status
private final LoanStatusEnumData status;
+ private final EnumOptionData subStatus;
// related to
private final Long clientId;
@@ -214,6 +217,7 @@ public class LoanAccountData {
final Long id = null;
final String accountNo = null;
final LoanStatusEnumData status = null;
+ final EnumOptionData subStatus = null;
final String externalId = null;
final Long clientId = null;
final String clientName = null;
@@ -335,7 +339,7 @@ public class LoanAccountData {
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
- maximumGap);
+ maximumGap, subStatus);
}
@@ -348,6 +352,7 @@ public class LoanAccountData {
final Long id = null;
final String accountNo = null;
final LoanStatusEnumData status = null;
+ final EnumOptionData subStatus = null;
final String externalId = null;
final GroupGeneralData group = null;
final EnumOptionData loanType = null;
@@ -468,7 +473,7 @@ public class LoanAccountData {
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
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.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
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 String accountNo = null;
final LoanStatusEnumData status = null;
+ final EnumOptionData subStatus = null;
final String externalId = null;
final Long clientId = null;
final String clientAccountNo = null;
@@ -631,7 +637,7 @@ public class LoanAccountData {
maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
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.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
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 String accountNo = null;
final LoanStatusEnumData status = null;
+ final EnumOptionData subStatus = null;
final String externalId = null;
final Long clientId = null;
final String clientAccountNo = null;
@@ -808,7 +815,7 @@ public class LoanAccountData {
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
- product.getMaximumGapBetweenInstallments());
+ product.getMaximumGapBetweenInstallments(), subStatus);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -867,7 +874,7 @@ public class LoanAccountData {
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
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 EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
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 Collection<LoanTransactionData> transactions = null;
@@ -952,7 +959,7 @@ public class LoanAccountData {
outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
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.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
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,
@@ -1044,7 +1051,7 @@ public class LoanAccountData {
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, 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 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.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
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.isInterestRecalculationEnabled, interestRecalculationData, 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 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.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
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) {
@@ -1196,7 +1203,7 @@ public class LoanAccountData {
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
- acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
+ acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus);
}
private LoanAccountData(
@@ -1263,11 +1270,12 @@ public class LoanAccountData {
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance,
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.accountNo = accountNo;
this.status = status;
+ this.subStatus = subStatus;
this.externalId = externalId;
this.clientId = clientId;
this.clientAccountNo = clientAccountNo;
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 535bb9a..c5e494b 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -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.LoanApplicationDateException;
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.LoanOfficerAssignmentException;
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.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
+import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
@@ -395,6 +397,9 @@ public class Loan extends AbstractPersistable<Long> {
@JoinColumn(name = "writeoff_reason_cv_id", nullable = true)
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,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -4814,6 +4819,14 @@ public class Loan extends AbstractPersistable<Long> {
dataValidationErrors.add(error);
}
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:
break;
}
@@ -5982,12 +5995,246 @@ public class Loan extends AbstractPersistable<Long> {
this.writeOffReason = writeOffReason;
}
- public Group getGroup() {
- return group;
- }
- public LoanProduct getLoanProduct() {
- return loanProduct;
- }
+ public Group getGroup() {
+ 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;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
index 3dfbc6a..8273d9e 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.domain;
import java.math.BigDecimal;
+import java.util.Map;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
@@ -56,4 +57,5 @@ public interface LoanAccountDomainService {
void saveLoanWithDataIntegrityViolationChecks(Loan loan);
+ Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClourseDate, String noteText);
}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index ba0788f..63796cc 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -19,11 +19,12 @@
package org.apache.fineract.portfolio.loanaccount.domain;
import java.math.BigDecimal;
-import java.util.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Date;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -574,9 +575,118 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
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) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
+
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
index 5b7f4c1..fe46017 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanEvent.java
@@ -42,5 +42,6 @@ public enum LoanEvent {
LOAN_CHARGE_PAYMENT, //
LOAN_CLOSED, //
LOAN_EDIT_MULTI_DISBURSE_DATE, //
- LOAN_REFUND;
+ LOAN_REFUND, //
+ LOAN_FORECLOSURE;
}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 711a313..c305514 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -785,4 +785,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public List<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
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);
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSubStatus.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSubStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSubStatus.java
new file mode 100644
index 0000000..afe3247
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSubStatus.java
@@ -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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index a97f8dd..70a2238 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -18,6 +18,27 @@
*/
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.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -36,26 +57,6 @@ import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
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.
* Disbursements, Repayments, Waivers, Write-off etc
@@ -116,7 +117,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false)
- private final Date createdDate;
+ private Date createdDate;
@ManyToOne
@JoinColumn(name = "appuser_id", nullable = true)
@@ -133,8 +134,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
private boolean manuallyAdjustedOrReversed;
@LazyCollection(LazyCollectionOption.FALSE)
- @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
- @JoinColumn(name = "loan_transaction_id", referencedColumnName= "id" , nullable = false)
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
+ @JoinColumn(name = "loan_transaction_id", referencedColumnName = "id", nullable = false)
private Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = new HashSet<>();
protected LoanTransaction() {
@@ -146,19 +147,20 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.appUser = null;
}
- public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf,
- final BigDecimal amount, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
- final BigDecimal penaltyChargesPortion, final AppUser appUser){
- final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
- final BigDecimal principalPortion = BigDecimal.ZERO;
- final BigDecimal overPaymentPortion = BigDecimal.ZERO;
- final boolean reversed = false;
- final PaymentDetail paymentDetail = null;
- final String externalId = null;
- final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
- return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
+ public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf, final BigDecimal amount,
+ final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
+ final AppUser appUser) {
+ final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
+ final BigDecimal principalPortion = BigDecimal.ZERO;
+ final BigDecimal overPaymentPortion = BigDecimal.ZERO;
+ final boolean reversed = false;
+ final PaymentDetail paymentDetail = null;
+ final String externalId = null;
+ final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
+ return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser);
}
+
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) {
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,
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,
- final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
- final BigDecimal penaltyChargesPortion, final AppUser appUser) {
+ final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
+ final AppUser appUser) {
BigDecimal principalPortion = null;
BigDecimal overPaymentPortion = null;
boolean reversed = false;
PaymentDetail paymentDetail = null;
String externalId = null;
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
- return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf.toDate(), amount,
- principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
+ return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf.toDate(), amount, principalPortion,
+ interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId,
createdDate, appUser);
}
@@ -664,15 +674,14 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public boolean isAccrual() {
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
}
-
- public boolean isNonMonetaryTransaction(){
- return isNotReversed()
- && (LoanTransactionType.CONTRA.equals(getTypeOf())
- || LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
- || LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
- || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
- || LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf())
- || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()));
+
+ public boolean isNonMonetaryTransaction() {
+ return isNotReversed()
+ && (LoanTransactionType.CONTRA.equals(getTypeOf()) || LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
+ || LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
+ || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
+ || LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER
+ .equals(getTypeOf()));
}
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
@@ -756,13 +765,19 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return isMappingUpdated;
}
-
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
return this.loanTransactionToRepaymentScheduleMappings;
}
-
- public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo(){
- return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
+
+ public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
+ return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
+ }
+
+ public void updateCreatedDate(Date createdDate) {
+ this.createdDate = createdDate;
}
+ public boolean isAccrualTransaction() {
+ return isAccrual();
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 78cd9d7..5cc5918 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -627,4 +627,90 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
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);
+ }
+ }
+ }
+
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index b303d30..5b55df5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -51,5 +51,8 @@ public interface LoanRepaymentScheduleTransactionProcessor {
void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
final Set<LoanCharge> charges);
+
+ void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges);
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanForeclosureException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanForeclosureException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanForeclosureException.java
new file mode 100644
index 0000000..79eb7d9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanForeclosureException.java
@@ -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);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ForeClosureCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ForeClosureCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ForeClosureCommandHandler.java
new file mode 100644
index 0000000..e249dc9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ForeClosureCommandHandler.java
@@ -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);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b9b345a5/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index bdceb74..d284130 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -447,4 +447,26 @@ public final class LoanEventApiJsonValidator {
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);
+ }
}
\ No newline at end of file