[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 <sachin.kulka...@confluxtechnologies.com>
Authored: Fri Jun 10 18:29:02 2016 +0530
Committer: sachinkulkarni12 <sachin.kulka...@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">&nbsp;</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">&nbsp;</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


Reply via email to