commit for MIFOSX-1523, Loan Rescheduling module

This commit is contained in:
emmanuel
2014-09-11 16:37:22 +02:00
parent 58a15a0382
commit a630d89549
55 changed files with 4654 additions and 80 deletions
+456
View File
@@ -1041,6 +1041,46 @@
<td><a href="#collaterals_update">Update a Collateral</a></td>
<td><a href="#collaterals_delete">Delete a Collateral</a></td>
</tr>
<tr>
<td><a href="#loan_rescheduling">Loan Rescheduling</a></td>
<td>rescheduleloans</td>
<td><a href="#loan_reschedule_request_create">Create new loan reschedule request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}</td>
<td></td>
<td><a href="#loan_reschedule_request_retrieve">Retrieve a Loan Reschedule Request</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=previewLoanReschedule</td>
<td></td>
<td><a href="#loan_reschedule_preview_retrieve">Retrieve a Preview of The New Loan Repayment Schedule</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=reject</td>
<td><a href="#loan_reschedule_request_reject">Reject Loan Reschedule Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=approve</td>
<td><a href="#loan_reschedule_request_approve">Approve Loan Reschedule Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
</div>
@@ -9860,6 +9900,382 @@ No Request Body:
</code>
</div>
</div>
<!-- Loan Rescheduling content -->
<a id="loan_rescheduling" name="loan_rescheduling" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Loan Rescheduling</h3>
<p>Loan rescheduling provides the ability to give a client extra grace periods, extend loan term by adding extra installments and adjust the interest rate of a loan.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>loanId</td>
</tr>
<tr>
<td class=fielddesc>The ID of the loan to be rescheduled.</td>
</tr>
<tr class=alt>
<td>rescheduleFromDate</td>
</tr>
<tr>
<td class=fielddesc>Due date of the start point (installment) of the rescheduling.</td>
</tr>
<tr class=alt>
<td>graceOnPrincipal</td>
</tr>
<tr>
<td class=fielddesc>Number of installments to be added with zero principal.</td>
</tr>
<tr class=alt>
<td>graceOnInterest</td>
</tr>
<tr>
<td class=fielddesc>Number of installments to be added with zero interest.</td>
</tr>
<tr class=alt>
<td>extraTerms</td>
</tr>
<tr>
<td class=fielddesc>Number of installments to be added after the last loan repayment schedule installment.</td>
</tr>
<tr class=alt>
<td>recalculateInterest</td>
</tr>
<tr>
<td class=fielddesc>If recalculateInterest="true", the total interest amount of the loan is recalculated. If recalculateInterest="false", the total interest amount remains the same.</td>
</tr>
<tr class=alt>
<td>newInterestRate</td>
</tr>
<tr>
<td class=fielddesc>New interest rate to be used in calculating the interest amount for each rescheduled period/installment.</td>
</tr>
<tr class=alt>
<td>adjustedDueDate</td>
</tr>
<tr>
<td class=fielddesc>New due date for the start point (installment) of the rescheduling.</td>
</tr>
<tr class=alt>
<td>rescheduleReasonId</td>
</tr>
<tr>
<td class=fielddesc>The ID of the code value that indicates the reason for rescheduling the loan.</td>
</tr>
<tr class=alt>
<td>rescheduleReasonComment</td>
</tr>
<tr>
<td class=fielddesc>Text provided as extra reason for the rescheduling of the loan.</td>
</tr>
<tr class=alt>
<td>submittedOnDate</td>
</tr>
<tr>
<td class=fielddesc>The date on which the loan reschedule request was made.</td>
</tr>
</table>
</div>
</div>
<a id="loan_reschedule_request_create" name="loan_reschedule_request_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Create a Loan Reschedule Request</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>loanId, rescheduleFromDate, rescheduleReasonId, submittedOnDate,<br />
graceOnPrincipal OR graceOnInterest OR extraTerms OR newInterestRate OR adjustedDueDate
</td>
</tr>
</table>
<br />
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Optional Fields</div></td>
</tr>
<tr class=alt>
<td>recalculateInterest, rescheduleReasonComment</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/rescheduleloans
</code>
<code class="method-request">
POST rescheduleloans
Content-Type: application/json Request Body:
{
"loanId": 1,
"graceOnPrincipal": 2,
"graceOnInterest": 3,
"extraTerms": 2,
"rescheduleFromDate": "04 December 2014",
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"recalculateInterest": true,
"submittedOnDate": "04 September 2014",
"newInterestRate" : 28,
"rescheduleReasonId": 1
}
</code>
<code class="method-response">
{
"loanId": 1,
"resourceId": 1
}
</code>
</div>
<div class="method-example">
<code class="method-request">
POST rescheduleloans
Content-Type: application/json Request Body:
{
"loanId": 1,
"graceOnPrincipal": 2,
"rescheduleFromDate": "04 December 2014",
"adjustedDueDate": "20 December 2014",
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"submittedOnDate": "04 September 2014",
"rescheduleReasonComment" : "Client has gone AWOL",
"rescheduleReasonId": 1
}
</code>
<code class="method-response">
{
"loanId": 1,
"resourceId": 1
}
</code>
</div>
</div>
<a id="loan_reschedule_request_retrieve" name="loan_reschedule_request_retrieve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve a Loan Reschedule Request</h4>
<p>Example Requests:</p>
<div class=apiClick>rescheduleloans/1</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/rescheduleloans/{requestId}
</code>
<code class="method-response">
{
"id": 2,
"loanId": 17,
"clientId": 4,
"clientName": "Fernando Michael Torres",
"loanAccountNumber": "000000017",
"statusEnum": {
"id": 500,
"code": "loanStatusType.rejected",
"value": "Rejected",
"pendingApproval": false,
"approved": false,
"rejected": true
},
"rescheduleFromInstallment": 5,
"graceOnPrincipal": 3,
"rescheduleFromDate": [
2013,
12,
6
],
"recalculateInterest": false,
"rescheduleReasonCodeValue": {
"id": 239,
"name": "client cannot pay on time"
},
"timeline": {
"submittedOnDate": [
2014,
8,
4
],
"submittedByUsername": "mifos",
"submittedByFirstname": "App",
"submittedByLastname": "Administrator",
"rejectedOnDate": [
2014,
8,
5
],
"rejectedByUsername": "mifos",
"rejectedByFirstname": "App",
"rejectedByLastname": "Administrator"
},
"rescheduleReasonComment": ""
}
</code>
</div>
</div>
<a id="loan_reschedule_preview_retrieve" name="loan_reschedule_preview_retrieve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve a Preview of The New Loan Repayment Schedule</h4>
<p>Example Requests:</p>
<div class=apiClick>rescheduleloans/1?command=previewLoanReschedule</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/rescheduleloans/{requestId}?command=previewLoanReschedule
</code>
<code class="method-response">
"currency": {
"code": "KES",
"name": "Kenyan Shilling",
"decimalPlaces": 0,
"inMultiplesOf": 10,
"displaySymbol": "KSh",
"nameCode": "currency.KES",
"displayLabel": "Kenyan Shilling (KSh)"
},
"loanTermInDays": 182,
"totalPrincipalDisbursed": 10000,
"totalPrincipalExpected": 10000,
"totalPrincipalPaid": 900.000000,
"totalInterestCharged": 960,
"totalFeeChargesCharged": 0.000000,
"totalPenaltyChargesCharged": 2500.000000,
"totalRepaymentExpected": 13460,
"totalOutstanding": 12060.000000,
"periods": [
{
"period": 1,
"fromDate": [
2013,
11,
8
],
"dueDate": [
2013,
11,
8
],
"daysInPeriod": 0,
"principalOriginalDue": 0,
"principalDue": 0,
"principalOutstanding": 0,
"principalLoanBalanceOutstanding": 10000,
"interestOriginalDue": 0,
"interestDue": 0,
"interestOutstanding": 0,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 0,
"totalDueForPeriod": 0,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 0,
"totalOverdue": 0,
"totalActualCostOfLoanForPeriod": 0
},
...
...
{
"period": 30,
"fromDate": [
2014,
5,
23
],
"dueDate": [
2014,
5,
30
],
"daysInPeriod": 7,
"principalOriginalDue": 300,
"principalDue": 300,
"principalOutstanding": 300,
"principalLoanBalanceOutstanding": 0,
"interestOriginalDue": 130,
"interestDue": 130,
"interestOutstanding": 130,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 430,
"totalDueForPeriod": 430,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 430,
"totalOverdue": 430,
"totalActualCostOfLoanForPeriod": 130
}
]
}
</code>
</div>
</div>
<a id="loan_reschedule_request_reject" name="loan_reschedule_request_reject" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Reject a Loan Reschedule Request</h4>
</div>
<div class="method-example">
<code class="method-declaration">POST https://DomainName/api/v1/rescheduleloans/{requestId}?command=reject</code>
<code class="method-request">POST rescheduleloans/1?command=reject
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"rejectedOnDate": "11 September 2014"
}
</code>
<code class="method-response">
{
"loanId": 1,
"resourceId": 1,
"changes": {
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"rejectedOnDate": "11 September 2014",
"rejectedByUserId": 1
}
}
</code>
</div>
</div>
<a id="loan_reschedule_request_approve" name="loan_reschedule_request_approve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Approve a Loan Reschedule Request</h4>
<p>Rescheduling of a loan happens once a loan reschedule request is approved.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://DomainName/api/v1/rescheduleloans/{requestId}?command=approve</code>
<code class="method-request">POST rescheduleloans/1?command=approve
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"approvedOnDate": "11 September 2014"
}
</code>
<code class="method-response">
{
"loanId": 1,
"resourceId": 1,
"changes": {
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"approvedOnDate": "11 September 2014",
"approvedByUserId": 1
}
}
</code>
</div>
</div>
<!-- start of offices content -->
<a id="offices" name="offices" class="old-syle-anchor">&nbsp;</a>
@@ -32215,6 +32631,46 @@ No Request Body:
<td><a href="#collaterals_update">Update a Collateral</a></td>
<td><a href="#collaterals_delete">Delete a Collateral</a></td>
</tr>
<tr>
<td><a href="#loan_rescheduling">Loan Rescheduling</a></td>
<td>rescheduleloans</td>
<td><a href="#loan_reschedule_request_create">Create a Loan Reschedule Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}</td>
<td></td>
<td><a href="#loan_reschedule_request_retrieve">Retrieve a Loan Reschedule Request</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=previewLoanReschedule</td>
<td></td>
<td><a href="#loan_reschedule_preview_retrieve">Retrieve a Preview of The New Loan Repayment Schedule</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=reject</td>
<td><a href="#loan_reschedule_request_reject">Reject Loan Reschedule Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>rescheduleloans/{requestId}?command=approve</td>
<td><a href="#loan_reschedule_request_approve">Approve Loan Reschedule Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#glaccounts">General Ledger Account</a></td>
<td>glaccounts</td>
@@ -0,0 +1,73 @@
create table m_loan_reschedule_request (
id bigint(20) primary key auto_increment,
loan_id bigint(20) not null,
status_enum smallint(5) not null,
reschedule_from_installment smallint(5) not null comment 'Rescheduling will start from this installment',
grace_on_principal smallint(5) null comment 'Number of installments that should be added with 0 principal amount',
grace_on_interest smallint(5) null comment 'Number of installments that should be added with 0 interest rate',
reschedule_from_date date not null comment 'Rescheduling will start from the installment with due date similar to this date.',
extra_terms smallint(5) comment 'Number of extra terms to be added to the schedule',
interest_rate decimal(19,6) null comment 'If provided, the interest rate for the unpaid installments will be recalculated',
recalculate_interest tinyint(1) null comment 'If set to 1, interest will be recalculated starting from the reschedule period.',
adjusted_due_date date null comment 'New due date for the first rescheduled installment',
reschedule_reason_cv_id int(11) null comment 'ID of code value of reason for rescheduling',
reschedule_reason_comment varchar(500) null comment 'Text provided in addition to the reason code value',
submitted_on_date date not null,
submitted_by_user_id bigint(20) not null,
approved_on_date date null,
approved_by_user_id bigint(20) null,
rejected_on_date date null,
rejected_by_user_id bigint(20) null,
foreign key (loan_id) references m_loan(id),
foreign key (reschedule_reason_cv_id) references m_code_value(id),
foreign key (submitted_by_user_id) references m_appuser(id),
foreign key (approved_by_user_id) references m_appuser(id),
foreign key (rejected_by_user_id) references m_appuser(id)
);
create table m_loan_repayment_schedule_history (
id bigint(20) primary key auto_increment,
loan_id bigint(20) not null,
loan_reschedule_request_id bigint(20) null,
fromdate date null,
duedate date not null,
installment smallint(5) not null,
principal_amount decimal(19,6) null,
principal_completed_derived decimal(19,6) null,
principal_writtenoff_derived decimal(19,6) null,
interest_amount decimal(19,6) null,
interest_completed_derived decimal(19,6) null,
interest_writtenoff_derived decimal(19,6) null,
interest_waived_derived decimal(19,6) null,
accrual_interest_derived decimal(19,6) null,
fee_charges_amount decimal(19,6) null,
fee_charges_completed_derived decimal(19,6) null,
fee_charges_writtenoff_derived decimal(19,6) null,
fee_charges_waived_derived decimal(19,6) null,
accrual_fee_charges_derived decimal(19,6) null,
penalty_charges_amount decimal(19,6) null,
penalty_charges_completed_derived decimal(19,6) null,
penalty_charges_writtenoff_derived decimal(19,6) null,
penalty_charges_waived_derived decimal(19,6) null,
accrual_penalty_charges_derived decimal(19,6) null,
total_paid_in_advance_derived decimal(19,6) null,
total_paid_late_derived decimal(19,6) null,
completed_derived bit(1) not null,
obligations_met_on_date date null,
createdby_id bigint(20) null,
created_date datetime null,
lastmodified_date datetime null,
lastmodifiedby_id bigint(20) null,
foreign key (loan_id) references m_loan(id),
foreign key (loan_reschedule_request_id) references m_loan_reschedule_request(id)
);
alter table m_loan add rescheduledon_userid bigint(20) null after rescheduledon_date;
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'READ_RESCHEDULELOAN', 'RESCHEDULELOAN', 'READ', '0');
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'CREATE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'CREATE', '0');
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'REJECT_RESCHEDULELOAN', 'RESCHEDULELOAN', 'REJECT', '0');
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) values ('loan_reschedule', 'APPROVE_RESCHEDULELOAN', 'RESCHEDULELOAN', 'APPROVE', '0');
@@ -0,0 +1,206 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.integrationtests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.mifosplatform.integrationtests.common.ClientHelper;
import org.mifosplatform.integrationtests.common.LoanRescheduleRequestHelper;
import org.mifosplatform.integrationtests.common.Utils;
import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
/**
* Test the creation, approval and rejection of a loan reschedule request
**/
@SuppressWarnings({ "rawtypes" })
public class LoanRescheduleRequestTest {
private ResponseSpecification responseSpec;
private ResponseSpecification generalResponseSpec;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper;
private LoanRescheduleRequestHelper loanRescheduleRequestHelper;
private Integer clientId;
private Integer loanProductId;
private Integer loanId;
private Integer loanRescheduleRequestId;
private String loanPrincipalAmount = "100000.00";
private String numberOfRepayments = "12";
private String interestRatePerPeriod = "18";
private String dateString = "4 September 2014";
@Before
public void initialize() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec);
this.generalResponseSpec = new ResponseSpecBuilder().build();
// create all required entities
this.createRequiredEntities();
}
/**
* Creates the client, loan product, and loan entities
**/
private void createRequiredEntities() {
this.createClientEntity();
this.createLoanProductEntity();
this.createLoanEntity();
}
/**
* create a new client
**/
private void createClientEntity() {
this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
}
/**
* create a new loan product
**/
private void createLoanProductEntity() {
System.out.println("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
final String loanProductJSON = new LoanProductTestBuilder()
.withPrincipal(loanPrincipalAmount)
.withNumberOfRepayments(numberOfRepayments)
.withinterestRatePerPeriod(interestRatePerPeriod)
.withInterestRateFrequencyTypeAsYear()
.build(null);
this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
System.out.println("Successfully created loan product (ID: " + this.loanProductId + ")");
}
/**
* submit a new loan application, approve and disburse the loan
**/
private void createLoanEntity() {
System.out.println("---------------------------------NEW LOAN APPLICATION------------------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder()
.withPrincipal(loanPrincipalAmount)
.withLoanTermFrequency(numberOfRepayments)
.withLoanTermFrequencyAsMonths()
.withNumberOfRepayments(numberOfRepayments)
.withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths()
.withAmortizationTypeAsEqualInstallments()
.withInterestCalculationPeriodTypeAsDays()
.withInterestRatePerPeriod(interestRatePerPeriod)
.withLoanTermFrequencyAsMonths()
.withSubmittedOnDate(dateString)
.withExpectedDisbursementDate(dateString)
.withPrincipalGrace("2")
.withInterestGrace("2")
.build(this.clientId.toString(), this.loanProductId.toString(), null);
this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
System.out.println("Sucessfully created loan (ID: " + this.loanId + ")");
this.approveLoanApplication();
this.disburseLoan();
}
/**
* approve the loan application
**/
private void approveLoanApplication() {
if(this.loanId != null) {
this.loanTransactionHelper.approveLoan(this.dateString, this.loanId);
System.out.println("Successfully approved loan (ID: " + this.loanId + ")");
}
}
/**
* disburse the newly created loan
**/
private void disburseLoan() {
if(this.loanId != null) {
this.loanTransactionHelper.disburseLoan(this.dateString, this.loanId);
System.out.println("Successfully disbursed loan (ID: " + this.loanId + ")");
}
}
/**
* create new loan reschedule request
**/
private void createLoanRescheduleRequest() {
System.out.println("---------------------------------CREATING LOAN RESCHEDULE REQUEST------------------------------------------");
final String requestJSON = new LoanRescheduleRequestTestBuilder().build(this.loanId.toString());
this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
System.out.println("Successfully created loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
}
@Test
public void testCreateLoanRescheduleRequest() {
this.createLoanRescheduleRequest();
}
@Test
public void testRejectLoanRescheduleRequest() {
this.createLoanRescheduleRequest();
System.out.println("-----------------------------REJECTING LOAN RESCHEDULE REQUEST--------------------------");
final String requestJSON = new LoanRescheduleRequestTestBuilder().getRejectLoanRescheduleRequestJSON();
this.loanRescheduleRequestHelper.rejectLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON);
final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
assertTrue((Boolean)response.get("rejected"));
System.out.println("Successfully rejected loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
}
@Test
public void testApproveLoanRescheduleRequest() {
this.createLoanRescheduleRequest();
System.out.println("-----------------------------APPROVING LOAN RESCHEDULE REQUEST--------------------------");
final String requestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON);
final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
assertTrue((Boolean)response.get("approved"));
final Integer numberOfRepayments = (Integer) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId, "numberOfRepayments");
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals("NUMBER OF REPAYMENTS SHOULD BE 16, NOT 12", "16", numberOfRepayments.toString());
assertEquals("TOTAL EXPECTED REPAYMENT MUST BE EQUAL TO 118000.0", "118000.0", totalExpectedRepayment.toString());
System.out.println("Successfully approved loan reschedule request (ID: " + this.loanRescheduleRequestId + ")");
}
}
@@ -28,7 +28,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
*/
public class BatchHelper {
private static final String BATCH_API_URL = "/mifosng-provider/api/v1/batches?tenantIdentifier=default";
private static final String BATCH_API_URL = "/mifosng-provider/api/v1/batches?" + Utils.TENANT_IDENTIFIER;
private static final String BATCH_API_URL_EXT = BATCH_API_URL + "&enclosingTransaction=true";
private BatchHelper() {
@@ -346,7 +346,7 @@ public class BatchHelper {
public static void verifyClientCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String externalId) {
System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
final String CLIENT_URL = "/mifosng-provider/api/v1/clients?externalId=" + externalId + "&tenantIdentifier=default";
final String CLIENT_URL = "/mifosng-provider/api/v1/clients?externalId=" + externalId + "&" + Utils.TENANT_IDENTIFIER;
final Integer responseRecords = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "totalFilteredRecords");
Assert.assertEquals("No records found with given externalId", (long) responseRecords, (long) 0);
}
@@ -15,7 +15,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
public class ClientHelper {
private static final String CREATE_CLIENT_URL = "/mifosng-provider/api/v1/clients?tenantIdentifier=default";
private static final String CREATE_CLIENT_URL = "/mifosng-provider/api/v1/clients?" + Utils.TENANT_IDENTIFIER;
public static Integer createClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
return createClient(requestSpec, responseSpec, "04 March 2011");
@@ -50,7 +50,7 @@ public class ClientHelper {
public static void verifyClientCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedClientID) {
System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
final String CLIENT_URL = "/mifosng-provider/api/v1/clients/" + generatedClientID + "?tenantIdentifier=default";
final String CLIENT_URL = "/mifosng-provider/api/v1/clients/" + generatedClientID + "?" + Utils.TENANT_IDENTIFIER;
final Integer responseClientID = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "id");
assertEquals("ERROR IN CREATING THE CLIENT", generatedClientID, responseClientID);
}
@@ -25,7 +25,7 @@ public class GlobalConfigurationHelper {
}
public static ArrayList getAllGlobalConfigurations(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
final String GET_ALL_GLOBAL_CONFIG_URL = "/mifosng-provider/api/v1/configurations?tenantIdentifier=default";
final String GET_ALL_GLOBAL_CONFIG_URL = "/mifosng-provider/api/v1/configurations?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING ALL GLOBAL CONFIGURATIONS -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_GLOBAL_CONFIG_URL, "");
return (ArrayList) response.get("globalConfiguration");
@@ -33,14 +33,14 @@ public class GlobalConfigurationHelper {
public static HashMap getGlobalConfigurationById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String configId) {
final String GET_GLOBAL_CONFIG_BY_ID_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?tenantIdentifier=default";
final String GET_GLOBAL_CONFIG_BY_ID_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING GLOBAL CONFIGURATION BY ID -------------------------");
return Utils.performServerGet(requestSpec, responseSpec, GET_GLOBAL_CONFIG_BY_ID_URL, "");
}
public static Integer updateValueForGlobalConfiguration(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String configId, final String value) {
final String GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?tenantIdentifier=default";
final String GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------UPDATE VALUE FOR GLOBAL CONFIG---------------------------------------------");
return Utils.performServerPut(requestSpec, responseSpec, GLOBAL_CONFIG_UPDATE_URL, updateGlobalConfigUpdateValueAsJSON(value),
"resourceId");
@@ -48,7 +48,7 @@ public class GlobalConfigurationHelper {
public static Integer updateEnabledFlagForGlobalConfiguration(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String configId, final Boolean enabled) {
final String GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?tenantIdentifier=default";
final String GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/configurations/" + configId + "?" + Utils.TENANT_IDENTIFIER;
System.out
.println("---------------------------------UPDATE GLOBAL CONFIG FOR ENABLED FLAG---------------------------------------------");
return Utils.performServerPut(requestSpec, responseSpec, GLOBAL_CONFIG_UPDATE_URL,
@@ -57,7 +57,7 @@ public class GlobalConfigurationHelper {
public static ArrayList getGlobalConfigurationIsCacheEnabled(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) {
final String GET_IS_CACHE_GLOBAL_CONFIG_URL = "/mifosng-provider/api/v1/caches?tenantIdentifier=default";
final String GET_IS_CACHE_GLOBAL_CONFIG_URL = "/mifosng-provider/api/v1/caches?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING IS CACHE ENABLED GLOBAL CONFIGURATION -------------------------");
final ArrayList<HashMap> response = Utils.performServerGet(requestSpec, responseSpec, GET_IS_CACHE_GLOBAL_CONFIG_URL, "");
return response;
@@ -65,7 +65,7 @@ public class GlobalConfigurationHelper {
public static HashMap updateIsCacheEnabledForGlobalConfiguration(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String cacheType) {
final String IS_CACHE_GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/caches?tenantIdentifier=default";
final String IS_CACHE_GLOBAL_CONFIG_UPDATE_URL = "/mifosng-provider/api/v1/caches?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------UPDATE GLOBAL CONFIG FOR IS CACHE ENABLED----------------------");
return Utils.performServerPut(requestSpec, responseSpec, IS_CACHE_GLOBAL_CONFIG_UPDATE_URL,
updateIsCacheEnabledGlobalConfigUpdateAsJSON(cacheType), "changes");
@@ -21,7 +21,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
public class GroupHelper {
private static final String CREATE_GROUP_URL = "/mifosng-provider/api/v1/groups?tenantIdentifier=default";
private static final String CREATE_GROUP_URL = "/mifosng-provider/api/v1/groups?" + Utils.TENANT_IDENTIFIER;
public static Integer createGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
@SuppressWarnings("unused") final boolean active) {
@@ -43,7 +43,7 @@ public class GroupHelper {
public static Integer associateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId, final String clientMember) {
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId
+ "?command=associateClients&tenantIdentifier=default";
+ "?command=associateClients&" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------Associate Client To A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
}
@@ -51,27 +51,27 @@ public class GroupHelper {
public static Integer disAssociateClient(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId, final String clientMember) {
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId
+ "?command=disassociateClients&tenantIdentifier=default";
+ "?command=disassociateClients&" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------Disassociate Client To A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, associateClientAsJSON(clientMember), "groupId");
}
public static Integer activateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String groupId) {
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=activate&tenantIdentifier=default";
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?command=activate&" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------Activate A GROUP---------------------------------------------");
return Utils.performServerPost(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, activateGroupAsJSON(""), "groupId");
}
public static Integer updateGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String name,
final String groupId) {
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------UPDATE GROUP---------------------------------------------");
return Utils.performServerPut(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, updateGroupAsJSON(name), "groupId");
}
public static Integer deleteGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String groupId) {
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?tenantIdentifier=default";
final String GROUP_ASSOCIATE_URL = "/mifosng-provider/api/v1/groups/" + groupId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("---------------------------------DELETE GROUP---------------------------------------------");
return Utils.performServerDelete(requestSpec, responseSpec, GROUP_ASSOCIATE_URL, "groupId");
}
@@ -129,7 +129,7 @@ public class GroupHelper {
public static void verifyGroupCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID) {
System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
final Integer responseGroupID = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "id");
assertEquals("ERROR IN CREATING THE GROUP", generatedGroupID, responseGroupID);
}
@@ -137,7 +137,7 @@ public class GroupHelper {
public static void verifyGroupDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID, final String field, final String expectedValue) {
System.out.println("------------------------------CHECK GROUP DETAILS------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
final String responseValue = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, field);
assertEquals("ERROR IN CREATING THE GROUP", expectedValue, responseValue);
}
@@ -145,7 +145,7 @@ public class GroupHelper {
public static void verifyGroupActivatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedGroupID, final boolean generatedGroupStatus) {
System.out.println("------------------------------CHECK GROUP STATUS------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?tenantIdentifier=default";
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID + "?" + Utils.TENANT_IDENTIFIER;
final Boolean responseGroupStatus = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "active");
assertEquals("ERROR IN ACTIVATING THE GROUP", generatedGroupStatus, responseGroupStatus);
}
@@ -155,7 +155,7 @@ public class GroupHelper {
List<String> list = new ArrayList<>();
System.out.println("------------------------------CHECK GROUP MEMBERS------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID
+ "?associations=clientMembers&tenantIdentifier=default";
+ "?associations=clientMembers&" + Utils.TENANT_IDENTIFIER;
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
assertTrue("ERROR IN GROUP MEMBER", list.toString().contains("id=" + groupMember.toString()));
}
@@ -165,7 +165,7 @@ public class GroupHelper {
List<String> list = new ArrayList<>();
System.out.println("------------------------------CHECK EMPTY GROUP MEMBER LIST------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupID
+ "?associations=clientMembers&tenantIdentifier=default";
+ "?associations=clientMembers&" + Utils.TENANT_IDENTIFIER;
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "clientMembers");
assertEquals("GROUP MEMBER LIST NOT EMPTY", list, null);
}
@@ -174,7 +174,7 @@ public class GroupHelper {
final Integer generatedGroupID) {
List<String> list = new ArrayList<>();
System.out.println("------------------------------CHECK GROUP DELETED------------------------------------\n");
final String GROUP_URL = "/mifosng-provider/api/v1/groups/?tenantIdentifier=default";
final String GROUP_URL = "/mifosng-provider/api/v1/groups/?" + Utils.TENANT_IDENTIFIER;
list = Utils.performServerGet(requestSpec, responseSpec, GROUP_URL, "pageItems");
assertFalse("GROUP NOT DELETED", list.toString().contains("id=" + generatedGroupID.toString()));
@@ -64,13 +64,13 @@ public class HolidayHelper {
}
public static Integer activateHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String holidayID) {
final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate";
final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate&" + Utils.TENANT_IDENTIFIER;
return Utils.performServerPost(requestSpec, responseSpec, ACTIVATE_HOLIDAY_URL, getActivateHolidayDataAsJSON(), "resourceId");
}
public static HashMap getHolidayById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String holidayID) {
final String GET_HOLIDAY_BY_ID_URL = HOLIDAYS_URL + "/" + holidayID + "?tenantIdentifier=default";
final String GET_HOLIDAY_BY_ID_URL = HOLIDAYS_URL + "/" + holidayID + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING HOLIDAY BY ID -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_HOLIDAY_BY_ID_URL, "");
return response;
@@ -0,0 +1,54 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.integrationtests.common;
import static org.junit.Assert.assertEquals;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
public class LoanRescheduleRequestHelper {
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
private static final String LOAN_RESCHEDULE_REQUEST_URL = "/mifosng-provider/api/v1/rescheduleloans";
public LoanRescheduleRequestHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
this.responseSpec = responseSpec;
}
public Integer createLoanRescheduleRequest(final String requestJSON) {
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "?" + Utils.TENANT_IDENTIFIER;
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
}
public Integer rejectLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=reject";
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
}
public Integer approveLoanRescheduleRequest(final Integer requestId, final String requestJSON) {
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER + "&command=approve";
return Utils.performServerPost(this.requestSpec, this.responseSpec, URL, requestJSON, "resourceId");
}
public Object getLoanRescheduleRequest(final Integer requestId, final String param) {
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, URL, param);
}
public void verifyCreationOfLoanRescheduleRequest(final Integer requestId) {
final String URL = LOAN_RESCHEDULE_REQUEST_URL + "/" + requestId + "?" + Utils.TENANT_IDENTIFIER;
final Integer id = Utils.performServerGet(requestSpec, responseSpec, URL, "id");
assertEquals("ERROR IN CREATING LOAN RESCHEDULE REQUEST", requestId, id);
}
}
@@ -28,7 +28,7 @@ public class SchedulerJobHelper {
}
public static ArrayList getAllSchedulerJobs(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
final String GET_ALL_SCHEDULER_JOBS_URL = "/mifosng-provider/api/v1/jobs?tenantIdentifier=default";
final String GET_ALL_SCHEDULER_JOBS_URL = "/mifosng-provider/api/v1/jobs?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING ALL SCHEDULER JOBS -------------------------");
final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_SCHEDULER_JOBS_URL, "");
return response;
@@ -36,28 +36,28 @@ public class SchedulerJobHelper {
public static HashMap getSchedulerJobById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String jobId) {
final String GET_SCHEDULER_JOB_BY_ID_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?tenantIdentifier=default";
final String GET_SCHEDULER_JOB_BY_ID_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING SCHEDULER JOB BY ID -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_JOB_BY_ID_URL, "");
return response;
}
public static HashMap getSchedulerStatus(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
final String GET_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/scheduler?tenantIdentifier=default";
final String GET_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/scheduler?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING SCHEDULER STATUS -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_STATUS_URL, "");
return response;
}
public static void updateSchedulerStatus(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String command) {
final String UPDATE_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/scheduler?command=" + command +"&tenantIdentifier=default";
final String UPDATE_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/scheduler?command=" + command +"&" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ UPDATING SCHEDULER STATUS -------------------------");
Utils.performServerPost(requestSpec, responseSpec, UPDATE_SCHEDULER_STATUS_URL, runSchedulerJobAsJSON(), null);
}
public static HashMap updateSchedulerJob(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String jobId, final String active) {
final String UPDATE_SCHEDULER_JOB_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?tenantIdentifier=default";
final String UPDATE_SCHEDULER_JOB_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ UPDATING SCHEDULER JOB -------------------------");
final HashMap response = Utils.performServerPut(requestSpec, responseSpec, UPDATE_SCHEDULER_JOB_URL,
updateSchedulerJobAsJSON(active), "changes");
@@ -73,7 +73,7 @@ public class SchedulerJobHelper {
public static ArrayList getSchedulerJobHistory(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String jobId) {
final String GET_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "/runhistory?tenantIdentifier=default";
final String GET_SCHEDULER_STATUS_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "/runhistory?" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RETRIEVING SCHEDULER JOB HISTORY -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_SCHEDULER_STATUS_URL, "");
return (ArrayList) response.get("pageItems");
@@ -81,7 +81,7 @@ public class SchedulerJobHelper {
public static void runSchedulerJob(final RequestSpecification requestSpec, final String jobId) {
final ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(202).build();
final String RUN_SCHEDULER_JOB_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?command=executeJob&tenantIdentifier=default";
final String RUN_SCHEDULER_JOB_URL = "/mifosng-provider/api/v1/jobs/" + jobId + "?command=executeJob&" + Utils.TENANT_IDENTIFIER;
System.out.println("------------------------ RUN SCHEDULER JOB -------------------------");
Utils.performServerPost(requestSpec, responseSpec, RUN_SCHEDULER_JOB_URL, runSchedulerJobAsJSON(), null);
}
@@ -15,7 +15,7 @@ import static org.junit.Assert.assertEquals;
public class SurveyHelper {
private static final String FULFIL_SURVEY_URL = "/mifosng-provider/api/v1/survey/ppi_kenya_2009/clientId?tenantIdentifier=default";
private static final String FULFIL_SURVEY_URL = "/mifosng-provider/api/v1/survey/ppi_kenya_2009/clientId?" + Utils.TENANT_IDENTIFIER;
public static Integer fulfilSurvey(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
return fulfilSurvey(requestSpec, responseSpec, "04 March 2011");
@@ -52,7 +52,7 @@ public class SurveyHelper {
public static void verifySurveyCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer generatedClientID) {
System.out.println("------------------------------CHECK CLIENT DETAILS------------------------------------\n");
final String SURVEY_URL = "/mifosng-provider/api/v1/Survey/ppi_kenya_2009/clientid/entryId" + generatedClientID + "?tenantIdentifier=default";
final String SURVEY_URL = "/mifosng-provider/api/v1/Survey/ppi_kenya_2009/clientid/entryId" + generatedClientID + "?" + Utils.TENANT_IDENTIFIER;
final Integer responseClientID = Utils.performServerGet(requestSpec, responseSpec, SURVEY_URL, "id");
assertEquals("ERROR IN CREATING THE CLIENT", generatedClientID, responseClientID);
}
@@ -16,7 +16,7 @@ import com.jayway.restassured.specification.ResponseSpecification;
@SuppressWarnings("rawtypes")
public class AccountHelper {
private final String CREATE_GL_ACCOUNT_URL = "/mifosng-provider/api/v1/glaccounts?tenantIdentifier=default";
private final String CREATE_GL_ACCOUNT_URL = "/mifosng-provider/api/v1/glaccounts?" + Utils.TENANT_IDENTIFIER;
private final String GL_ACCOUNT_ID_RESPONSE = "resourceId";
private final RequestSpecification requestSpec;
@@ -43,6 +43,7 @@ public class LoanApplicationTestBuilder {
private String fixedEmiAmount = "10000";
private String maxOutstandingLoanBalance = "36000";
private String graceOnPrincipalPayment = null;
private String graceOnInterestPayment = null;
private List<HashMap> disbursementData = null;
private List<HashMap> charges = new ArrayList<HashMap>();
@@ -81,6 +82,10 @@ public class LoanApplicationTestBuilder {
if (graceOnPrincipalPayment != null) {
map.put("graceOnPrincipalPayment", graceOnPrincipalPayment);
}
if(graceOnInterestPayment != null) {
map.put("graceOnInterestPayment", graceOnInterestPayment);
}
if (disbursementData != null) {
map.put("disbursementData", disbursementData);
@@ -146,6 +151,11 @@ public class LoanApplicationTestBuilder {
this.repaymentFrequencyType = WEEKS;
return this;
}
public LoanApplicationTestBuilder withRepaymentFrequencyTypeAsYear() {
this.repaymentFrequencyType = YEARS;
return this;
}
public LoanApplicationTestBuilder withInterestRatePerPeriod(final String interestRate) {
this.interestRate = interestRate;
@@ -206,6 +216,11 @@ public class LoanApplicationTestBuilder {
this.graceOnPrincipalPayment = graceOnPrincipalPayment;
return this;
}
public LoanApplicationTestBuilder withInterestGrace(final String graceOnInterestPayment) {
this.graceOnInterestPayment = graceOnInterestPayment;
return this;
}
public LoanApplicationTestBuilder withTranches(final List<HashMap> disbursementData) {
this.disbursementData = disbursementData;
@@ -0,0 +1,136 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.integrationtests.common.loans;
import java.util.HashMap;
import com.google.gson.Gson;
public class LoanRescheduleRequestTestBuilder {
private String rescheduleFromDate = "04 December 2014";
private String graceOnPrincipal = "2";
private String graceOnInterest = "2";
private String extraTerms = "2";
private Boolean recalculateInterest = false;
private String newInterestRate = null;
private String adjustedDueDate = null;
private String rescheduleReasonId = "1";
private String rescheduleReasonComment = null;
private String submittedOnDate = "04 September 2014";
public String build(final String loanId) {
final HashMap<String, Object> map = new HashMap<>();
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en_GB");
map.put("loanId", loanId);
map.put("submittedOnDate", submittedOnDate);
map.put("rescheduleFromDate", rescheduleFromDate);
if(graceOnPrincipal != null) {
map.put("graceOnPrincipal", graceOnPrincipal);
}
if(graceOnInterest != null) {
map.put("graceOnInterest", graceOnInterest);
}
if(extraTerms != null) {
map.put("extraTerms", extraTerms);
}
map.put("recalculateInterest", recalculateInterest);
if(newInterestRate != null) {
map.put("newInterestRate", newInterestRate);
}
if(adjustedDueDate != null) {
map.put("adjustedDueDate", adjustedDueDate);
}
map.put("rescheduleReasonId", rescheduleReasonId);
if(rescheduleReasonComment != null) {
map.put("rescheduleReasonComment", rescheduleReasonComment);
}
return new Gson().toJson(map);
}
public LoanRescheduleRequestTestBuilder updateRescheduleFromDate(final String rescheduleFromDate) {
if(rescheduleFromDate != null) {
this.rescheduleFromDate = rescheduleFromDate;
}
return this;
}
public LoanRescheduleRequestTestBuilder updateGraceOnPrincipal(final String graceOnPrincipal) {
this.graceOnPrincipal = graceOnPrincipal;
return this;
}
public LoanRescheduleRequestTestBuilder updateGraceOnInterest(final String graceOnInterest) {
this.graceOnInterest = graceOnInterest;
return this;
}
public LoanRescheduleRequestTestBuilder updateExtraTerms(final String extraTerms) {
this.extraTerms = extraTerms;
return this;
}
public LoanRescheduleRequestTestBuilder updateRecalculateInterest(final Boolean recalculateInterest) {
this.recalculateInterest = recalculateInterest;
return this;
}
public LoanRescheduleRequestTestBuilder updateNewInterestRate(final String newInterestRate) {
this.newInterestRate = newInterestRate;
return this;
}
public LoanRescheduleRequestTestBuilder updateAdjustedDueDate(final String adjustedDueDate) {
this.adjustedDueDate = adjustedDueDate;
return this;
}
public LoanRescheduleRequestTestBuilder updateRescheduleReasonId(final String rescheduleReasonId) {
this.rescheduleReasonId = rescheduleReasonId;
return this;
}
public LoanRescheduleRequestTestBuilder updateRescheduleReasonComment(final String rescheduleReasonComment) {
this.rescheduleReasonComment = rescheduleReasonComment;
return this;
}
public String getRejectLoanRescheduleRequestJSON() {
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy");
map.put("rejectedOnDate", submittedOnDate);
return new Gson().toJson(map);
}
public String getApproveLoanRescheduleRequestJSON() {
final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy");
map.put("approvedOnDate", submittedOnDate);
return new Gson().toJson(map);
}
}
@@ -48,7 +48,7 @@ public class LoanStatusChecker {
public static HashMap getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer loanID) {
final String url = "/mifosng-provider/api/v1/loans/" + loanID + "?tenantIdentifier=default";
final String url = "/mifosng-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, url, "status");
}
@@ -24,8 +24,8 @@ public class LoanTransactionHelper {
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
private static final String CREATE_LOAN_PRODUCT_URL = "/mifosng-provider/api/v1/loanproducts?tenantIdentifier=default";
private static final String APPLY_LOAN_URL = "/mifosng-provider/api/v1/loans?tenantIdentifier=default";
private static final String CREATE_LOAN_PRODUCT_URL = "/mifosng-provider/api/v1/loanproducts?" + Utils.TENANT_IDENTIFIER;
private static final String APPLY_LOAN_URL = "/mifosng-provider/api/v1/loans?" + Utils.TENANT_IDENTIFIER;
private static final String APPROVE_LOAN_COMMAND = "approve";
private static final String UNDO_APPROVAL_LOAN_COMMAND = "undoApproval";
private static final String DISBURSE_LOAN_COMMAND = "disburse";
@@ -51,25 +51,25 @@ public class LoanTransactionHelper {
public Integer updateLoan(final Integer id, final String loanApplicationJSON) {
return Utils.performServerPut(this.requestSpec, this.responseSpec, "/mifosng-provider/api/v1/loans/" + id
+ "?tenantIdentifier=default", loanApplicationJSON, "loanId");
+ "?" + Utils.TENANT_IDENTIFIER, loanApplicationJSON, "loanId");
}
public ArrayList getLoanRepaymentSchedule(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer loanID) {
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=repaymentSchedule&tenantIdentifier=default";
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=repaymentSchedule&" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "repaymentSchedule");
return (ArrayList) response.get("periods");
}
public HashMap getLoanSummary(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID) {
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=all&tenantIdentifier=default";
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary");
return response;
}
public Object getLoanDetail(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID,
final String param) {
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=all&tenantIdentifier=default";
final String URL = "/mifosng-provider/api/v1/loans/" + loanID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, URL, param);
}
@@ -133,7 +133,7 @@ public class LoanTransactionHelper {
public Integer addChargesForLoan(final Integer loanId, final String request) {
System.out.println("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
final String ADD_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges?tenantIdentifier=default";
final String ADD_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerPost(requestSpec, responseSpec, ADD_CHARGES_URL, request, "");
return (Integer) response.get("resourceId");
}
@@ -141,7 +141,7 @@ public class LoanTransactionHelper {
public Integer updateChargesForLoan(final Integer loanId, final Integer loanchargeId, final String request) {
System.out.println("--------------------------------- ADD CHARGES FOR LOAN --------------------------------");
final String UPDATE_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId
+ "?tenantIdentifier=default";
+ "?" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerPut(requestSpec, responseSpec, UPDATE_CHARGES_URL, request, "");
return (Integer) response.get("resourceId");
}
@@ -149,7 +149,7 @@ public class LoanTransactionHelper {
public Integer deleteChargesForLoan(final Integer loanId, final Integer loanchargeId) {
System.out.println("--------------------------------- DELETE CHARGES FOR LOAN --------------------------------");
final String DELETE_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId
+ "?tenantIdentifier=default";
+ "?" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerDelete(requestSpec, responseSpec, DELETE_CHARGES_URL, "");
return (Integer) response.get("resourceId");
}
@@ -157,7 +157,7 @@ public class LoanTransactionHelper {
public Integer waiveChargesForLoan(final Integer loanId, final Integer loanchargeId, final String json) {
System.out.println("--------------------------------- WAIVE CHARGES FOR LOAN --------------------------------");
final String CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId
+ "?command=waive&tenantIdentifier=default";
+ "?command=waive&" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
return (Integer) response.get("resourceId");
}
@@ -165,7 +165,7 @@ public class LoanTransactionHelper {
public Integer payChargesForLoan(final Integer loanId, final Integer loanchargeId, final String json) {
System.out.println("--------------------------------- WAIVE CHARGES FOR LOAN --------------------------------");
final String CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId
+ "?command=pay&tenantIdentifier=default";
+ "?command=pay&" + Utils.TENANT_IDENTIFIER;
final HashMap response = Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, "");
return (Integer) response.get("resourceId");
}
@@ -326,11 +326,11 @@ public class LoanTransactionHelper {
}
private String createLoanOperationURL(final String command, final Integer loanID) {
return "/mifosng-provider/api/v1/loans/" + loanID + "?command=" + command + "&tenantIdentifier=default";
return "/mifosng-provider/api/v1/loans/" + loanID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
}
private String createLoanTransactionURL(final String command, final Integer loanID) {
return "/mifosng-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&tenantIdentifier=default";
return "/mifosng-provider/api/v1/loans/" + loanID + "/transactions?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
}
private HashMap performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent) {
@@ -19,8 +19,8 @@ public class XBRLIntegrationTestHelper {
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
private static final String GET_TAXONOMY_LIST_URL = "/mifosng-provider/api/v1/mixtaxonomy?tenantIdentifier=default";
private static final String TAXONOMY_MAPPING_URL = "/mifosng-provider/api/v1/mixmapping?tenantIdentifier=default";
private static final String GET_TAXONOMY_LIST_URL = "/mifosng-provider/api/v1/mixtaxonomy?" + Utils.TENANT_IDENTIFIER;
private static final String TAXONOMY_MAPPING_URL = "/mifosng-provider/api/v1/mixmapping?" + Utils.TENANT_IDENTIFIER;
public XBRLIntegrationTestHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
@@ -5,6 +5,8 @@
*/
package org.mifosplatform.commands.domain;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
public class CommandWrapper {
private final Long commandId;
@@ -239,6 +241,29 @@ public class CommandWrapper {
return this.entityName.equalsIgnoreCase("SMS");
}
public boolean isSmsCampaignResource(){
return this.entityName.equals("SMS_CAMPAIGN");
}
public boolean isLoanRescheduleResource() {
return this.entityName.equals(RescheduleLoansApiConstants.ENTITY_NAME);
}
public boolean isApprove() {
return this.actionName.equalsIgnoreCase("APPROVE");
}
public boolean isReject() {
return this.actionName.equalsIgnoreCase("REJECT");
}
public boolean isSmsCampaignActivation() {
return this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityName.equalsIgnoreCase("SMS_CAMPAIGN");
}
public boolean isSmsCampaignClosure() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("SMS_CAMPAIGN");
}
public boolean isCodeResource() {
return this.entityName.equalsIgnoreCase("CODE");
}
@@ -2050,4 +2050,27 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder createLoanRescheduleRequest(final String entityName) {
this.actionName = "CREATE";
this.entityName = entityName;
this.entityId = null;
this.href = "/rescheduleloans";
return this;
}
public CommandWrapperBuilder approveLoanRescheduleRequest(final String entityName, final Long requestId) {
this.actionName = "APPROVE";
this.entityName = entityName;
this.entityId = requestId;
this.href = "/rescheduleloans/" + requestId + "?command=approve";
return this;
}
public CommandWrapperBuilder rejectLoanRescheduleRequest(final String entityName, final Long requestId) {
this.actionName = "REJECT";
this.entityName = entityName;
this.entityId = requestId;
this.href = "/rescheduleloans/" + requestId + "?command=reject";
return this;
}
}
@@ -822,6 +822,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isLoanRescheduleResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createLoanRescheduleRequestCommandHandler", NewCommandSourceHandler.class);
} else if(wrapper.isApprove()) {
handler = this.applicationContext.getBean("approveLoanRescheduleRequestCommandHandler", NewCommandSourceHandler.class);
} else if(wrapper.isReject()) {
handler = this.applicationContext.getBean("rejectLoanRescheduleRequestCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
@@ -13,6 +13,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
@@ -61,6 +62,7 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
@@ -91,8 +93,11 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleG
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
@@ -221,6 +226,10 @@ public class Loan extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
@Column(name = "rescheduledon_date")
private Date rescheduledOnDate;
@ManyToOne(optional = true)
@JoinColumn(name = "rescheduledon_userid", nullable = true)
private AppUser rescheduledByUser;
@Temporal(TemporalType.DATE)
@Column(name = "expected_maturedon_date")
@@ -4289,4 +4298,173 @@ public class Loan extends AbstractPersistable<Long> {
return this.isNpa;
}
/**
* @return List of loan repayments schedule objects
**/
public List<LoanRepaymentScheduleInstallment> getRepaymentScheduleInstallments() {
return this.repaymentScheduleInstallments;
}
/**
* @return Loan product minimum repayments schedule related detail
**/
public LoanProductRelatedDetail getLoanRepaymentScheduleDetail() {
return this.loanRepaymentScheduleDetail;
}
/**
* @return Loan Fixed Emi amount
**/
public BigDecimal getFixedEmiAmount() {
return this.fixedEmiAmount;
}
/**
* @return maximum outstanding loan balance
**/
public BigDecimal getMaxOutstandingLoanBalance() {
return this.maxOutstandingLoanBalance;
}
/**
* @param dueDate the due date of the installment
* @return a schedule installment with similar due date to the one provided
**/
public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDate dueDate) {
LoanRepaymentScheduleInstallment installment = null;
if(dueDate != null) {
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : this.repaymentScheduleInstallments) {
if(repaymentScheduleInstallment.getDueDate().isEqual(dueDate)) {
installment = repaymentScheduleInstallment;
break;
}
}
}
return installment;
}
/**
* @param loan the Loan object
* @return loan disbursement data
**/
public List<DisbursementData> getDisbursmentData() {
Iterator<LoanDisbursementDetails> iterator = this.getDisbursementDetails().iterator();
List<DisbursementData> disbursementData = new ArrayList<DisbursementData>();
while(iterator.hasNext()) {
LoanDisbursementDetails loanDisbursementDetails = iterator.next();
LocalDate expectedDisbursementDate = null;
LocalDate actualDisbursementDate = null;
if(loanDisbursementDetails.expectedDisbursementDate() != null) {
expectedDisbursementDate = new LocalDate(loanDisbursementDetails.expectedDisbursementDate());
}
if(loanDisbursementDetails.actualDisbursementDate() != null) {
actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
}
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate,
actualDisbursementDate, loanDisbursementDetails.principal(), loanDisbursementDetails.approvedPrincipal()));
}
return disbursementData;
}
/**
* @param loan the Loan object
* @return application terms of the Loan object
**/
@SuppressWarnings({ "unused" })
public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency) {
LoanProduct loanProduct = loanProduct();
//LoanProductRelatedDetail loanProductRelatedDetail = getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getCurrency();
final Integer loanTermFrequency = getTermFrequency();
final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments();
final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod();
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail.getInterestCalculationPeriodMethod();
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal();
final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate();
final LocalDate repaymentsStartingFromDate = getExpectedFirstRepaymentOnDate();
LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
// TODO get calender linked to loan if any exist. As of 17-07-2014, could not find a link in the database.
// skip for now and set the Calender object to null
Calendar calendar = null;
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged();
final LocalDate interestChargedFromDate = getInterestChargedFromDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate();
final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
final BigDecimal emiAmount = getFixedEmiAmount();
final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
final List<DisbursementData> disbursementData = getDisbursmentData();
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney,
this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance,
loanVariationTermsData, interestChargedFromDate);
}
/**
* @return Loan summary embedded object
**/
public LoanSummary getLoanSummary() {
return this.summary;
}
public void updateRescheduledByUser(AppUser rescheduledByUser) {
this.rescheduledByUser = rescheduledByUser;
}
public LoanProductRelatedDetail getLoanProductRelatedDetail() {
return this.loanRepaymentScheduleDetail;
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments);
}
public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
if(rescheduledOnDate != null) {
this.rescheduledOnDate = rescheduledOnDate.toDate();
}
}
public void updateTermFrequency(Integer termFrequency) {
if(termFrequency != null) {
this.termFrequency = termFrequency;
}
}
}
@@ -84,6 +84,10 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
public BigDecimal principal() {
return this.principal;
}
public BigDecimal approvedPrincipal() {
return this.approvedPrincipal;
}
public void updatePrincipal(BigDecimal principal) {
this.principal = principal;
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Date;
import javax.persistence.Column;
@@ -31,7 +32,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
private Loan loan;
@Column(name = "installment", nullable = false)
private final Integer installmentNumber;
private Integer installmentNumber;
@Temporal(TemporalType.DATE)
@Column(name = "fromdate", nullable = true)
@@ -123,6 +124,14 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.obligationsMet = false;
this.recalculatedInterestComponent = recalculatedInterestComponent;
}
public LoanRepaymentScheduleInstallment(final Loan loan) {
this.loan = loan;
this.installmentNumber = null;
this.fromDate = null;
this.dueDate = null;
this.obligationsMet = false;
}
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
@@ -252,6 +261,11 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void updateLoan(final Loan loan) {
this.loan = loan;
}
public boolean isPartlyPaid() {
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null
|| this.principalCompleted != null);
}
public boolean isObligationsMet() {
return this.obligationsMet;
@@ -552,4 +566,59 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) {
this.recalculatedInterestComponent = recalculatedInterestComponent;
}
public void updateInstallmentNumber(final Integer installmentNumber) {
if(installmentNumber != null) {
this.installmentNumber = installmentNumber;
}
}
public void updateInterestCharged(final BigDecimal interestCharged) {
this.interestCharged = interestCharged;
}
public void updateObligationMet(final Boolean obligationMet) {
this.obligationsMet = obligationMet;
}
public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) {
this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null;
}
public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) {
this.interestWrittenOff = interestWrittenOff;
}
public void updatePrincipal(final BigDecimal principal) {
this.principal = principal;
}
public static Comparator<LoanRepaymentScheduleInstallment> installmentNumberComparator
= new Comparator<LoanRepaymentScheduleInstallment>() {
@Override
public int compare(LoanRepaymentScheduleInstallment arg0,
LoanRepaymentScheduleInstallment arg1) {
return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
}
};
public BigDecimal getTotalPaidInAdvance() {
return this.totalPaidInAdvance;
}
public BigDecimal getTotalPaidLate() {
return this.totalPaidLate;
}
public LocalDate getObligationsMetOnDate() {
LocalDate obligationsMetOnDate = null;
if(this.obligationsMetOnDate != null) {
obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate);
}
return obligationsMetOnDate;
}
}
@@ -286,4 +286,19 @@ public final class LoanSummary {
public BigDecimal getTotalWrittenOff() {
return this.totalWrittenOff;
}
/**
* @return total interest repaid
**/
public BigDecimal getTotalInterestRepaid() {
return this.totalInterestRepaid;
}
public BigDecimal getTotalFeeChargesCharged() {
return this.totalFeeChargesCharged;
}
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
}
@@ -10,6 +10,8 @@ import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -25,13 +27,21 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.useradministration.domain.AppUser;
/**
*
@@ -472,6 +482,355 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstanding);
}
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
// create an archive of the current loan schedule installments
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = createLoanScheduleArchive(loanRescheduleRequest);
// get the initial list of repayment installments
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
// sort list by installment number in ASC order
Collections.sort(repaymentScheduleInstallments, LoanRepaymentScheduleInstallment.installmentNumberComparator);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<LoanRescheduleModelRepaymentPeriod>();
Money outstandingLoanBalance = loan.getPrincpal();
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);
outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(oldPeriodNumber, oldPeriodNumber,
fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue, feeChargesDue, penaltyChargesDue,
totalDue, false);
periods.add(period);
}
Money outstandingBalance = loan.getPrincpal();
Money totalCumulativePrincipal = Money.zero(currency);
Money totalCumulativeInterest = Money.zero(currency);
Money actualTotalCumulativeInterest = Money.zero(currency);
Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);
LocalDate installmentDueDate = null;
LocalDate adjustedInstallmentDueDate = null;
LocalDate installmentFromDate = null;
Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(loanRescheduleRequest.getRescheduleFromInstallment());
Integer installmentNumber = rescheduleFromInstallmentNo;
Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
Integer numberOfRepayments = repaymentScheduleInstallments.size();
Integer rescheduleNumberOfRepayments = numberOfRepayments;
final Money principal = loan.getPrincpal();
final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
int loanTermInDays = Integer.valueOf(0);
if(rescheduleFromInstallmentNo > 0) {
// this will hold the loan repayment installment that is before the reschedule start installment
// (rescheduleFrominstallment)
LoanRepaymentScheduleInstallment previousInstallment = null;
// get the install number of the previous installment
int previousInstallmentNo = rescheduleFromInstallmentNo - 1;
// only fetch the installment if the number is greater than 0
if(previousInstallmentNo > 0) {
previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
}
LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);
// the "installment from date" is equal to the due date of the previous installment, if it exists
if(previousInstallment != null) {
installmentFromDate = previousInstallment.getDueDate();
}
else {
installmentFromDate = firstInstallment.getFromDate();
}
installmentDueDate = installmentFromDate;
LocalDate periodStartDateApplicableForInterest = installmentFromDate;
Integer periodNumber = 1;
outstandingLoanBalance = loan.getPrincpal();
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {
totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod.plus(period.principalDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
rescheduleNumberOfRepayments--;
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
while(graceOnPrincipal > 0 || graceOnInterest > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
if(graceOnPrincipal > 0) {
graceOnPrincipal--;
}
if(graceOnInterest > 0) {
graceOnInterest--;
}
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
while(extraTerms > 0) {
LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0, new LocalDate(),
new LocalDate(), Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
Money.zero(currency), Money.zero(currency), true);
periods.add(period);
extraTerms--;
rescheduleNumberOfRepayments++;
numberOfRepayments++;
}
// get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency);
// update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
if(newInterestRate != null) {
loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
}
graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments);
loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments);
loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest);
Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mathContext);
if(!recalculateInterest && newInterestRate == null) {
totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm.minus(actualTotalCumulativeInterest);
loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
}
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate()) ||
period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) ||
period.isNew()) {
installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms,
false);
if(adjustedDueDate != null && periodNumber == 1) {
installmentDueDate = adjustedDueDate;
}
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
period.updatePeriodNumber(installmentNumber);
period.updatePeriodFromDate(installmentFromDate);
period.updatePeriodDueDate(adjustedInstallmentDueDate);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, adjustedInstallmentDueDate,
periodStartDateApplicableForInterest, loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery());
// ========================= Calculate the interest due ========================================
// change the principal to => Principal Disbursed - Total Principal Paid
// interest calculation is always based on the total principal outstanding
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
// determine the interest & principal for the period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInInstallment, outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
// update the interest due for the period
period.updateInterestDue(principalInterestForThisPeriod.interest());
// =============================================================================================
// ========================== Calculate the principal due ======================================
// change the principal to => Principal Disbursed - Total cumulative Principal Amount before the reschedule installment
loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));
principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInInstallment, outstandingBalance, loanApplicationTerms, periodNumber, mathContext);
period.updatePrincipalDue(principalInterestForThisPeriod.principal());
// ==============================================================================================
outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
period.updateOutstandingLoanBalance(outstandingLoanBalance);
Money principalDue = Money.of(currency, period.principalDue());
Money interestDue = Money.of(currency, period.interestDue());
Money feeChargesDue = Money.of(currency, period.feeChargesDue());
Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
Money totalDue = principalDue
.plus(interestDue)
.plus(feeChargesDue)
.plus(penaltyChargesDue);
period.updateTotalDue(totalDue);
// update cumulative fields for principal & interest
totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
installmentFromDate = adjustedInstallmentDueDate;
installmentNumber++;
periodNumber++;
loanTermInDays += daysInInstallment;
outstandingBalance = outstandingBalance.minus(period.principalDue());
}
}
}
final Money totalRepaymentExpected = principal // get the loan Principal amount
.plus(actualTotalCumulativeInterest) // add the actual total cumulative interest
.plus(loanSummary.getTotalFeeChargesCharged()) // add the total fees charged
.plus(loanSummary.getTotalPenaltyChargesCharged()); // finally add the total penalty charged
return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency, loanTermInDays,
loan.getPrincpal(), loan.getPrincpal().getAmount(), loanSummary.getTotalPrincipalRepaid(), actualTotalCumulativeInterest.getAmount(),
loanSummary.getTotalFeeChargesCharged(), loanSummary.getTotalPenaltyChargesCharged(), totalRepaymentExpected.getAmount(),
loanSummary.getTotalOutstanding());
}
private Collection<LoanRepaymentScheduleHistory> createLoanScheduleArchive(final LoanRescheduleRequest loanRescheduleRequest) {
final Loan loan = loanRescheduleRequest.getLoan();
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
List<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = new ArrayList<LoanRepaymentScheduleHistory>();
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
final Integer installmentNumber = repaymentScheduleInstallment.getInstallmentNumber();
Date fromDate = null;
Date dueDate = null;
if(repaymentScheduleInstallment.getFromDate() != null) {
fromDate = repaymentScheduleInstallment.getFromDate().toDate();
}
if(repaymentScheduleInstallment.getDueDate() != null) {
dueDate = repaymentScheduleInstallment.getDueDate().toDate();
}
final BigDecimal principal = repaymentScheduleInstallment.getPrincipal(currency).getAmount();
final BigDecimal principalCompleted = repaymentScheduleInstallment.getPrincipalCompleted(currency).getAmount();
final BigDecimal principalWrittenOff = repaymentScheduleInstallment.getPrincipalWrittenOff(currency).getAmount();
final BigDecimal interestCharged = repaymentScheduleInstallment.getInterestCharged(currency).getAmount();
final BigDecimal interestPaid = repaymentScheduleInstallment.getInterestPaid(currency).getAmount();
final BigDecimal interestWaived = repaymentScheduleInstallment.getInterestWaived(currency).getAmount();
final BigDecimal interestWrittenOff = repaymentScheduleInstallment.getInterestWrittenOff(currency).getAmount();
final BigDecimal feeChargesCharged = repaymentScheduleInstallment.getFeeChargesCharged(currency).getAmount();
final BigDecimal feeChargesPaid = repaymentScheduleInstallment.getFeeChargesPaid(currency).getAmount();
final BigDecimal feeChargesWrittenOff = repaymentScheduleInstallment.getFeeChargesWrittenOff(currency).getAmount();
final BigDecimal feeChargesWaived = repaymentScheduleInstallment.getFeeChargesWaived(currency).getAmount();
final BigDecimal penaltyCharges = repaymentScheduleInstallment.getPenaltyChargesCharged(currency).getAmount();
final BigDecimal penaltyChargesPaid = repaymentScheduleInstallment.getPenaltyChargesPaid(currency).getAmount();
final BigDecimal penaltyChargesWrittenOff = repaymentScheduleInstallment.getPenaltyChargesWrittenOff(currency).getAmount();
final BigDecimal penaltyChargesWaived = repaymentScheduleInstallment.getPenaltyChargesWaived(currency).getAmount();
final BigDecimal totalPaidInAdvance = repaymentScheduleInstallment.getTotalPaidInAdvance();
final BigDecimal totalPaidLate = repaymentScheduleInstallment.getTotalPaidLate();
final boolean obligationsMet = repaymentScheduleInstallment.isObligationsMet();
Date obligationsMetOnDate = null;
if(repaymentScheduleInstallment.getObligationsMetOnDate() != null) {
obligationsMetOnDate = repaymentScheduleInstallment.getObligationsMetOnDate().toDate();
}
Date createdOnDate = null;
if(repaymentScheduleInstallment.getCreatedDate() != null) {
createdOnDate = repaymentScheduleInstallment.getCreatedDate().toDate();
}
final AppUser createdByUser = repaymentScheduleInstallment.getCreatedBy();
final AppUser lastModifiedByUser = repaymentScheduleInstallment.getLastModifiedBy();
Date lastModifiedOnDate = null;
if(repaymentScheduleInstallment.getLastModifiedDate() != null) {
lastModifiedOnDate = repaymentScheduleInstallment.getLastModifiedDate().toDate();
}
LoanRepaymentScheduleHistory loanRepaymentScheduleHistory = LoanRepaymentScheduleHistory.instance(loan,
loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal, principalCompleted,
principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges,
penaltyChargesPaid, penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate,
obligationsMet, obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
loanRepaymentScheduleHistoryList.add(loanRepaymentScheduleHistory);
}
return loanRepaymentScheduleHistoryList;
}
private BigDecimal calculateInterestForSpecificDays(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
int periodNumber, Money totalOutstandingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest,
@@ -1205,5 +1564,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
return precloseTransaction;
}
/**
* set the value to zero if the provided value is null
*
* @return integer value equal/greater than 0
**/
private Integer defaultToZeroIfNull(Integer value) {
return (value != null) ? value : 0;
}
}
@@ -36,17 +36,17 @@ import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType
public final class LoanApplicationTerms {
private final ApplicationCurrency currency;
private final Integer loanTermFrequency;
private Integer loanTermFrequency;
private final PeriodFrequencyType loanTermPeriodFrequencyType;
private final Integer numberOfRepayments;
private Integer numberOfRepayments;
private final Integer repaymentEvery;
private final PeriodFrequencyType repaymentPeriodFrequencyType;
private final AmortizationMethod amortizationMethod;
private final InterestMethod interestMethod;
private final BigDecimal interestRatePerPeriod;
private BigDecimal interestRatePerPeriod;
private final PeriodFrequencyType interestRatePeriodFrequencyType;
private final BigDecimal annualNominalInterestRate;
private BigDecimal annualNominalInterestRate;
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
private Money principal;
@@ -58,7 +58,7 @@ public final class LoanApplicationTerms {
* installments where 'grace' should apply to the principal component of a
* loans repayment period (installment).
*/
private final Integer principalGrace;
private Integer principalGrace;
/**
* Integer representing the number of 'repayment frequencies' or
@@ -68,7 +68,7 @@ public final class LoanApplicationTerms {
* <b>Note:</b> Interest is still calculated taking into account the full
* loan term, the interest is simply offset to a later period.
*/
private final Integer interestPaymentGrace;
private Integer interestPaymentGrace;
/**
* Integer representing the number of 'repayment frequencies' or
@@ -93,7 +93,7 @@ public final class LoanApplicationTerms {
* to indicate from whence interest should be charged.
* </p>
*/
private final LocalDate interestChargedFromDate;
private LocalDate interestChargedFromDate;
private final Money inArrearsTolerance;
private final Integer graceOnArrearsAgeing;
@@ -110,6 +110,8 @@ public final class LoanApplicationTerms {
private BigDecimal maxOutstandingBalance;
private List<LoanTermVariationsData> emiAmountVariations;
private Money totalInterestDue;
private final DaysInMonthType daysInMonthType;
@@ -498,6 +500,15 @@ public final class LoanApplicationTerms {
return periodNumber == numberOfRepayments;
}
private Money calculateTotalDueForEqualInstallmentLoan(final BigDecimal periodicInterestRate) {
final int periodsElapsed = 0;
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
}
/**
* general method to calculate totalInterestDue discounting any grace
* settings
@@ -509,14 +520,18 @@ public final class LoanApplicationTerms {
switch (this.interestMethod) {
case FLAT:
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
break;
case DECLINING_BALANCE:
break;
case INVALID:
break;
}
if(this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue;
}
return totalInterestDue;
}
@@ -529,7 +544,7 @@ public final class LoanApplicationTerms {
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm();
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
.multiply(loanTermFrequencyBigDecimal);
}
@@ -752,7 +767,7 @@ public final class LoanApplicationTerms {
return periodNumber > 0 && periodNumber <= getInterestChargingGrace();
}
private Integer getPrincipalGrace() {
public Integer getPrincipalGrace() {
Integer graceOnPrincipalPayments = Integer.valueOf(0);
if (this.principalGrace != null) {
graceOnPrincipalPayments = this.principalGrace;
@@ -760,7 +775,7 @@ public final class LoanApplicationTerms {
return graceOnPrincipalPayments;
}
private Integer getInterestPaymentGrace() {
public Integer getInterestPaymentGrace() {
Integer graceOnInterestPayments = Integer.valueOf(0);
if (this.interestPaymentGrace != null) {
graceOnInterestPayments = this.interestPaymentGrace;
@@ -768,7 +783,7 @@ public final class LoanApplicationTerms {
return graceOnInterestPayments;
}
private Integer getInterestChargingGrace() {
public Integer getInterestChargingGrace() {
Integer graceOnInterestCharged = Integer.valueOf(0);
if (this.interestChargingGrace != null) {
graceOnInterestCharged = this.interestChargingGrace;
@@ -1067,4 +1082,55 @@ public final class LoanApplicationTerms {
public RecalculationFrequencyType getRecalculationFrequencyType() {
return this.recalculationFrequencyType;
}
public void updateNumberOfRepayments(final Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public void updatePrincipalGrace(final Integer principalGrace) {
this.principalGrace = principalGrace;
}
public void updateInterestPaymentGrace(final Integer interestPaymentGrace) {
this.interestPaymentGrace = interestPaymentGrace;
}
public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) {
if(interestRatePerPeriod != null) {
this.interestRatePerPeriod = interestRatePerPeriod;
}
}
public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) {
if(annualNominalInterestRate != null) {
this.annualNominalInterestRate = annualNominalInterestRate;
}
}
public BigDecimal getInterestRatePerPeriod() {
return this.interestRatePerPeriod;
}
public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate;
}
public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) {
if(interestChargedFromDate != null) {
this.interestChargedFromDate = interestChargedFromDate;
}
}
public void updateLoanTermFrequency(Integer loanTermFrequency) {
if(loanTermFrequency != null) {
this.loanTermFrequency = loanTermFrequency;
}
}
public void updateTotalInterestDue(Money totalInterestDue) {
if(totalInterestDue != null) {
this.totalInterestDue = totalInterestDue;
}
}
}
@@ -18,6 +18,8 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
public interface LoanScheduleGenerator {
@@ -32,4 +34,8 @@ public interface LoanScheduleGenerator {
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc);
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays);
}
@@ -0,0 +1,48 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class RescheduleLoansApiConstants {
public final static String ENTITY_NAME = "RESCHEDULELOAN";
// general
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
// create action request parameters
public static final String loanIdParamName = "loanId";
public static final String graceOnPrincipalParamName = "graceOnPrincipal";
public static final String graceOnInterestParamName = "graceOnInterest";
public static final String extraTermsParamName = "extraTerms";
public static final String rescheduleFromDateParamName = "rescheduleFromDate";
public static final String recalculateInterestParamName = "recalculateInterest";
public static final String newInterestRateParamName = "newInterestRate";
public static final String rescheduleReasonIdParamName = "rescheduleReasonId";
public static final String rescheduleReasonCommentParamName = "rescheduleReasonComment";
public static final String submittedOnDateParamName = "submittedOnDate";
public static final String adjustedDueDateParamName = "adjustedDueDate";
public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, graceOnPrincipalParamName, graceOnInterestParamName, extraTermsParamName,
rescheduleFromDateParamName, newInterestRateParamName, rescheduleReasonIdParamName, rescheduleReasonCommentParamName,
submittedOnDateParamName, loanIdParamName, adjustedDueDateParamName, recalculateInterestParamName));
// reject action request parameters
public static final String rejectedOnDateParam = "rejectedOnDate";
public static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, rejectedOnDateParam));
// approve action request parameters
public static final String approvedOnDateParam = "approvedOnDate";
public static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, approvedOnDateParam));
}
@@ -0,0 +1,150 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.api;
import java.util.HashSet;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanReschedulePreviewPlatformService;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/rescheduleloans")
@Component
@Scope("singleton")
public class RescheduleLoansApiResource {
private final DefaultToApiJsonSerializer<LoanRescheduleRequestData> loanRescheduleRequestToApiJsonSerializer;
private final DefaultToApiJsonSerializer<LoanScheduleData> loanRescheduleToApiJsonSerializer;
private final PlatformSecurityContext platformSecurityContext;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
private final LoanReschedulePreviewPlatformService loanReschedulePreviewPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
@Autowired
public RescheduleLoansApiResource(
final DefaultToApiJsonSerializer<LoanRescheduleRequestData> loanRescheduleRequestToApiJsonSerializer,
final PlatformSecurityContext platformSecurityContext,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final DefaultToApiJsonSerializer<LoanScheduleData> loanRescheduleToApiJsonSerializer,
final LoanReschedulePreviewPlatformService loanReschedulePreviewPlatformService) {
this.loanRescheduleRequestToApiJsonSerializer = loanRescheduleRequestToApiJsonSerializer;
this.platformSecurityContext = platformSecurityContext;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.loanRescheduleToApiJsonSerializer = loanRescheduleToApiJsonSerializer;
this.loanReschedulePreviewPlatformService = loanReschedulePreviewPlatformService;
}
@GET
@Path("{requestId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String readLoanRescheduleRequest(@Context final UriInfo uriInfo, @PathParam("requestId") final Long requestId,
@QueryParam("command") final String command) {
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(RescheduleLoansApiConstants.ENTITY_NAME);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
.process(uriInfo.getQueryParameters());
if(compareIgnoreCase(command, "previewLoanReschedule")) {
final LoanRescheduleModel loanRescheduleModel = this.loanReschedulePreviewPlatformService.previewLoanReschedule(requestId);
return this.loanRescheduleToApiJsonSerializer.serialize(settings, loanRescheduleModel.toData(), new HashSet<String>());
}
final LoanRescheduleRequestData loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequest(requestId);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(settings, loanRescheduleRequestData);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createLoanRescheduleRequest(final String apiRequestBodyAsJson) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder()
.createLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME)
.withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(commandProcessingResult);
}
@POST
@Path("{requestId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateLoanRescheduleRequest(@PathParam("requestId") final Long requestId,
@QueryParam("command") final String command, final String apiRequestBodyAsJson) {
CommandWrapper commandWrapper = null;
if(compareIgnoreCase(command, "approve")) {
commandWrapper = new CommandWrapperBuilder()
.approveLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME, requestId)
.withJson(apiRequestBodyAsJson)
.build();
}
else if(compareIgnoreCase(command, "reject")) {
commandWrapper = new CommandWrapperBuilder()
.rejectLoanRescheduleRequest(RescheduleLoansApiConstants.ENTITY_NAME, requestId)
.withJson(apiRequestBodyAsJson)
.build();
}
else {
throw new UnrecognizedQueryParamException("command", command, new Object[] { "approve", "reject" });
}
final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
return this.loanRescheduleRequestToApiJsonSerializer.serialize(commandProcessingResult);
}
/**
* Compares two strings, ignoring differences in case
*
* @param firstString the first string
* @param secondString the second string
* @return true if the two strings are equal, else false
**/
private boolean compareIgnoreCase(String firstString, String secondString) {
return StringUtils.isNotBlank(firstString) && firstString.trim().equalsIgnoreCase(secondString);
}
}
@@ -0,0 +1,202 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
/**
* Immutable data object representing loan reschedule request data.
**/
public class LoanRescheduleRequestData {
private final Long id;
private final Long loanId;
private final Long clientId;
private final String clientName;
private final String loanAccountNumber;
private final LoanRescheduleRequestStatusEnumData statusEnum;
private final Integer rescheduleFromInstallment;
private final Integer graceOnPrincipal;
private final Integer graceOnInterest;
private final LocalDate rescheduleFromDate;
private final Integer extraTerms;
private final BigDecimal interestRate;
private final Boolean recalculateInterest;
private final CodeValueData rescheduleReasonCodeValue;
private final LoanRescheduleRequestTimelineData timeline;
private final String rescheduleReasonComment;
private final LocalDate adjustedDueDate;
/**
* LoanRescheduleRequestData constructor
**/
private LoanRescheduleRequestData(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest,
LocalDate rescheduleFromDate, LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate,
CodeValueData rescheduleReasonCodeValue, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline,
final String clientName, final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest) {
this.id = id;
this.loanId = loanId;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.adjustedDueDate = adjustedDueDate;
this.timeline = timeline;
this.clientName = clientName;
this.loanAccountNumber = loanAccountNumber;
this.clientId = clientId;
this.recalculateInterest = recalculateInterest;
}
/**
* @return an instance of the LoanRescheduleRequestData class
**/
public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanRescheduleRequestStatusEnumData statusEnum,
Integer rescheduleFromInstallment, Integer graceOnPrincipal, Integer graceOnInterest,
LocalDate rescheduleFromDate, LocalDate adjustedDueDate, Integer extraTerms, BigDecimal interestRate,
CodeValueData rescheduleReasonCodeValue, String rescheduleReasonComment, LoanRescheduleRequestTimelineData timeline,
final String clientName, final String loanAccountNumber, final Long clientId, final Boolean recalculateInterest) {
return new LoanRescheduleRequestData(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment,
timeline, clientName, loanAccountNumber, clientId, recalculateInterest);
}
/**
* @return the id
*/
public Long getId() {
return id;
}
/**
* @return the loanId
*/
public Long getLoanId() {
return loanId;
}
/**
* @return the statusEnum
*/
public LoanRescheduleRequestStatusEnumData getStatusEnum() {
return statusEnum;
}
/**
* @return the reschedule from installment number
*/
public Integer getRescheduleFromInstallment() {
return rescheduleFromInstallment;
}
/**
* @return the graceOnPrincipal
*/
public Integer getGraceOnPrincipal() {
return graceOnPrincipal;
}
/**
* @return the graceOnInterest
*/
public Integer getGraceOnInterest() {
return graceOnInterest;
}
/**
* @return the reschedule from date
*/
public LocalDate getRescheduleFromDate() {
return rescheduleFromDate;
}
/**
* @return the extraTerms
*/
public Integer getExtraTerms() {
return extraTerms;
}
/**
* @return the interestRate
*/
public BigDecimal getInterestRate() {
return interestRate;
}
/**
* @return the rescheduleReasonCodeValueId
*/
public CodeValueData getRescheduleReasonCodeValueId() {
return rescheduleReasonCodeValue;
}
/**
* @return the rescheduleReasonText
*/
public String getRescheduleReasonComment() {
return rescheduleReasonComment;
}
/**
* @return the timeline
**/
public LoanRescheduleRequestTimelineData getTimeline() {
return this.timeline;
}
/**
* @return the adjustedDueDate
*/
public LocalDate getAdjustedDueDate() {
return adjustedDueDate;
}
/**
* @return the clientName
*/
public String getClientName() {
return clientName;
}
/**
* @return the loanAccountNumber
*/
public String getLoanAccountNumber() {
return loanAccountNumber;
}
/**
* @return the clientId
*/
public Long getClientId() {
return clientId;
}
/**
* @return the recalculateInterest
*/
public Boolean getRecalculateInterest() {
boolean value = false;
if(recalculateInterest != null) {
value = recalculateInterest;
}
return value;
}
}
@@ -0,0 +1,376 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.data;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public class LoanRescheduleRequestDataValidator {
private final FromJsonHelper fromJsonHelper;
private final LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService;
@Autowired
public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper,
LoanRescheduleRequestReadPlatformService loanRescheduleRequestReadPlatformService) {
this.fromJsonHelper = fromJsonHelper;
this.loanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService;
}
/**
* Validates the request to create a new loan reschedule entry
*
* @param jsonCommand the JSON command object (instance of the JsonCommand class)
* @return void
**/
public void validateForCreateAction(final JsonCommand jsonCommand, final Loan loan) {
final String jsonString = jsonCommand.json();
if (StringUtils.isBlank(jsonString)) {
throw new InvalidJsonException();
}
final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
RescheduleLoansApiConstants.CREATE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors)
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
final JsonElement jsonElement = jsonCommand.parsedJson();
if(!loan.status().isActive()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
}
final Long loanId = this.fromJsonHelper.extractLongNamed(
RescheduleLoansApiConstants.loanIdParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName)
.value(loanId).notNull().integerGreaterThanZero();
final LocalDate submittedOnDate = this.fromJsonHelper.extractLocalDateNamed(
RescheduleLoansApiConstants.submittedOnDateParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
.value(submittedOnDate).notNull();
if(submittedOnDate != null && loan.getDisbursementDate().isAfter(submittedOnDate)) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
.failWithCode("before.loan.disbursement.date",
"Submission date cannot be before the loan disbursement date");
}
final LocalDate rescheduleFromDate = this.fromJsonHelper.extractLocalDateNamed(
RescheduleLoansApiConstants.rescheduleFromDateParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.value(rescheduleFromDate).notNull();
final Integer graceOnPrincipal = this.fromJsonHelper.extractIntegerWithLocaleNamed(
RescheduleLoansApiConstants.graceOnPrincipalParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName)
.value(graceOnPrincipal).ignoreIfNull().integerGreaterThanZero();
final Integer graceOnInterest = this.fromJsonHelper.extractIntegerWithLocaleNamed(
RescheduleLoansApiConstants.graceOnInterestParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName)
.value(graceOnInterest).ignoreIfNull().integerGreaterThanZero();
final Integer extraTerms = this.fromJsonHelper.extractIntegerWithLocaleNamed(
RescheduleLoansApiConstants.extraTermsParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName)
.value(extraTerms).ignoreIfNull().integerGreaterThanZero();
final Long rescheduleReasonId = this.fromJsonHelper.extractLongNamed(
RescheduleLoansApiConstants.rescheduleReasonIdParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName)
.value(rescheduleReasonId).notNull().integerGreaterThanZero();
final String rescheduleReasonComment = this.fromJsonHelper.extractStringNamed(
RescheduleLoansApiConstants.rescheduleReasonCommentParamName, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName)
.value(rescheduleReasonComment).ignoreIfNull().notExceedingLengthOf(500);
final LocalDate adjustedDueDate = this.fromJsonHelper.extractLocalDateNamed(
RescheduleLoansApiConstants.adjustedDueDateParamName, jsonElement);
if(adjustedDueDate != null && rescheduleFromDate != null && adjustedDueDate.isBefore(rescheduleFromDate)) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("adjustedDueDate.before.rescheduleFromDate",
"Adjusted due date cannot be before the reschedule from date");
}
// at least one of the following must be provided => graceOnPrincipal, graceOnInterest, extraTerms, newInterestRate
if(!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName, jsonElement)
&& !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName, jsonElement)
&& !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName, jsonElement)
&& !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName, jsonElement)
&& !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName, jsonElement)) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName)
.notNull();
}
if(rescheduleFromDate != null) {
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
if(installment == null) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("repayment.schedule.installment.does.not.exist",
"Repayment schedule installment does not exist");
}
if(installment != null && installment.isObligationsMet()) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("repayment.schedule.installment.obligation.met",
"Repayment schedule installment obligation met");
}
if(installment != null && installment.isPartlyPaid()) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
.failWithCode("repayment.schedule.installment.partly.paid",
"Repayment schedule installment is partly paid");
}
if(installment != null) {
if(installment.getPenaltyChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("repayment.schedule.installment.has.an.outstanding.penalty.charge",
"Repayment schedule installment has an outsatnding penalty charge.");
}
if(installment.getFeeChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("repayment.schedule.installment.has.an.outstanding.fee.charge",
"Repayment schedule installment has an outsatnding fee charge.");
}
}
}
if(loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if(loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
if(!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
/**
* Validates a user request to approve a loan reschedule request
*
* @param jsonCommand the JSON command object (instance of the JsonCommand class)
* @return void
**/
public void validateForApproveAction(final JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
final String jsonString = jsonCommand.json();
if (StringUtils.isBlank(jsonString)) {
throw new InvalidJsonException();
}
final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
RescheduleLoansApiConstants.APPROVE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors)
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
final JsonElement jsonElement = jsonCommand.parsedJson();
final LocalDate approvedOnDate = this.fromJsonHelper.extractLocalDateNamed(
RescheduleLoansApiConstants.approvedOnDateParam, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam)
.value(approvedOnDate).notNull();
if(approvedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(approvedOnDate)) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.approvedOnDateParam)
.failWithCode("before.submission.date",
"Approval date cannot be before the request submission date.");
}
LoanRescheduleRequestStatusEnumData loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
.status(loanRescheduleRequest.getStatusEnum());
if(!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode(
"request.is.not.in.submitted.and.pending.state",
"Loan reschedule request approval is not allowed. "
+ "Loan reschedule request is not in submitted and pending approval state.");
}
LocalDate rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
final Loan loan = loanRescheduleRequest.getLoan();
if(loan != null) {
Long loanId = loan.getId();
if(!loan.status().isActive()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
}
if(rescheduleFromDate != null) {
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
if(installment == null) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.repayment.schedule.installment.does.not.exist",
"Repayment schedule installment does not exist");
}
if(installment != null && installment.isObligationsMet()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.repayment.schedule.installment."
+ "obligation.met", "Repayment schedule installment obligation met");
}
if(installment != null) {
if(installment.getPenaltyChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("repayment.schedule.installment.has.an.outstanding.penalty.charge",
"Repayment schedule installment has an outsatnding penalty charge.");
}
if(installment.getFeeChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("repayment.schedule.installment.has.an.outstanding.fee.charge",
"Repayment schedule installment has an outsatnding fee charge.");
}
}
}
if(loanId != null) {
List<LoanRescheduleRequestData> loanRescheduleRequestData = this.loanRescheduleRequestReadPlatformService
.readLoanRescheduleRequests(loanId, LoanStatus.APPROVED.getValue());
if(loanRescheduleRequestData.size() > 0) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled",
"The loan can only be rescheduled once.");
}
}
}
if(!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
/**
* Validates a user request to reject a loan reschedule request
*
* @param jsonCommand the JSON command object (instance of the JsonCommand class)
* @return void
**/
public void validateForRejectAction(final JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
final String jsonString = jsonCommand.json();
if (StringUtils.isBlank(jsonString)) {
throw new InvalidJsonException();
}
final Type typeToken = new TypeToken<Map<String, Object>>() {}.getType();
this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
RescheduleLoansApiConstants.REJECT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors)
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
final JsonElement jsonElement = jsonCommand.parsedJson();
final LocalDate rejectedOnDate = this.fromJsonHelper.extractLocalDateNamed(
RescheduleLoansApiConstants.rejectedOnDateParam, jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam)
.value(rejectedOnDate).notNull();
if(rejectedOnDate != null && loanRescheduleRequest.getSubmittedOnDate().isAfter(rejectedOnDate)) {
dataValidatorBuilder
.reset()
.parameter(RescheduleLoansApiConstants.rejectedOnDateParam)
.failWithCode("before.submission.date",
"Rejection date cannot be before the request submission date.");
}
LoanRescheduleRequestStatusEnumData loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
.status(loanRescheduleRequest.getStatusEnum());
if(!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
dataValidatorBuilder
.reset()
.failWithCodeNoParameterAddedToErrorCode(
"request.is.not.in.submitted.and.pending.state",
"Loan reschedule request rejection is not allowed. "
+ "Loan reschedule request is not in submitted and pending approval state.");
}
if(!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -0,0 +1,55 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.data;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
public class LoanRescheduleRequestEnumerations {
public static EnumOptionData status(final LoanRescheduleRequestStatusEnumData status) {
Long id = status.id();
String code = status.code();
String value = status.value();
return new EnumOptionData(id, code, value);
}
public static LoanRescheduleRequestStatusEnumData status(final Integer statusId) {
return status(LoanStatus.fromInt(statusId));
}
public static LoanRescheduleRequestStatusEnumData status(final LoanStatus status) {
LoanRescheduleRequestStatusEnumData enumData = new LoanRescheduleRequestStatusEnumData(
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
switch(status) {
case SUBMITTED_AND_PENDING_APPROVAL:
enumData = new LoanRescheduleRequestStatusEnumData(
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
break;
case APPROVED:
enumData = new LoanRescheduleRequestStatusEnumData(
LoanStatus.APPROVED.getValue().longValue(),
LoanStatus.APPROVED.getCode(), "Approved");
break;
case REJECTED:
enumData = new LoanRescheduleRequestStatusEnumData(
LoanStatus.REJECTED.getValue().longValue(),
LoanStatus.REJECTED.getCode(), "Rejected");
break;
default:
break;
}
return enumData;
}
}
@@ -0,0 +1,59 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.data;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
/**
* Immutable data object represent loan reschedule request status enumerations.
**/
@SuppressWarnings("unused")
public class LoanRescheduleRequestStatusEnumData {
private final Long id;
private final String code;
private final String value;
private final boolean pendingApproval;
private final boolean approved;
private final boolean rejected;
/**
* LoanRescheduleRequestStatusEnumData constructor
*
* Note: Same status types/states for loan accounts are used in here
**/
public LoanRescheduleRequestStatusEnumData(Long id, String code, String value) {
this.id = id;
this.code = code;
this.value = value;
this.pendingApproval = Long.valueOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue()).equals(this.id);
this.approved = Long.valueOf(LoanStatus.APPROVED.getValue()).equals(this.id);
this.rejected = Long.valueOf(LoanStatus.REJECTED.getValue()).equals(this.id);
}
public Long id() {
return this.id;
}
public String code() {
return this.code;
}
public String value() {
return this.value;
}
public boolean isPendingApproval() {
return this.pendingApproval;
}
public boolean isApproved() {
return this.approved;
}
public boolean isRejected() {
return this.rejected;
}
}
@@ -0,0 +1,51 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.data;
import org.joda.time.LocalDate;
/**
* Immutable data object represent the timeline events of a loan reschedule request
**/
@SuppressWarnings("unused")
public class LoanRescheduleRequestTimelineData {
private final LocalDate submittedOnDate;
private final String submittedByUsername;
private final String submittedByFirstname;
private final String submittedByLastname;
private final LocalDate approvedOnDate;
private final String approvedByUsername;
private final String approvedByFirstname;
private final String approvedByLastname;
private final LocalDate rejectedOnDate;
private final String rejectedByUsername;
private final String rejectedByFirstname;
private final String rejectedByLastname;
public LoanRescheduleRequestTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, final String submittedByFirstname,
final String submittedByLastname, final LocalDate approvedOnDate, final String approvedByUsername, final String approvedByFirstname,
final String approvedByLastname, final LocalDate rejectedOnDate, final String rejectedByUsername, final String rejectedByFirstname,
final String rejectedByLastname) {
this.submittedOnDate = submittedOnDate;
this.submittedByUsername = submittedByUsername;
this.submittedByFirstname = submittedByFirstname;
this.submittedByLastname = submittedByLastname;
this.approvedOnDate = approvedOnDate;
this.approvedByUsername = approvedByUsername;
this.approvedByFirstname = approvedByFirstname;
this.approvedByLastname = approvedByLastname;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUsername = rejectedByUsername;
this.rejectedByFirstname = rejectedByFirstname;
this.rejectedByLastname = rejectedByLastname;
}
}
@@ -0,0 +1,45 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import java.util.List;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext,
final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(
mathContext, loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
break;
case INVALID:
break;
}
return loanRescheduleModel;
}
}
@@ -0,0 +1,210 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_repayment_schedule_history")
public class LoanRepaymentScheduleHistory extends AbstractPersistable<Long> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id")
private Loan loan;
@OneToOne(optional = true)
@JoinColumn(name = "loan_reschedule_request_id")
private LoanRescheduleRequest loanRescheduleRequest;
@Column(name = "installment", nullable = false)
private Integer installmentNumber;
@Temporal(TemporalType.DATE)
@Column(name = "fromdate", nullable = true)
private Date fromDate;
@Temporal(TemporalType.DATE)
@Column(name = "duedate", nullable = false)
private Date dueDate;
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal;
@Column(name = "principal_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalCompleted;
@Column(name = "principal_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalWrittenOff;
@Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal interestCharged;
@Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPaid;
@Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWaived;
@Column(name = "interest_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWrittenOff;
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCharged;
@Column(name = "fee_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPaid;
@Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWrittenOff;
@Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWaived;
@Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges;
@Column(name = "penalty_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPaid;
@Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWrittenOff;
@Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWaived;
@Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidInAdvance;
@Column(name = "total_paid_late_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidLate;
@Column(name = "completed_derived", nullable = false)
private boolean obligationsMet;
@Temporal(TemporalType.DATE)
@Column(name = "obligations_met_on_date")
private Date obligationsMetOnDate;
@Temporal(TemporalType.DATE)
@Column(name = "created_date")
private Date createdOnDate;
@ManyToOne
@JoinColumn(name = "createdby_id")
private AppUser createdByUser;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedByUser;
@Temporal(TemporalType.DATE)
@Column(name = "lastmodified_date")
private Date lastModifiedOnDate;
/**
* LoanRepaymentScheduleHistory constructor
**/
protected LoanRepaymentScheduleHistory() {}
/**
* LoanRepaymentScheduleHistory constructor
**/
private LoanRepaymentScheduleHistory(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
this.loan = loan;
this.loanRescheduleRequest = loanRescheduleRequest;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principal = principal;
this.principalCompleted = principalCompleted;
this.principalWrittenOff = principalWrittenOff;
this.interestCharged = interestCharged;
this.interestPaid = interestPaid;
this.interestWaived = interestWaived;
this.interestWrittenOff = interestWrittenOff;
this.feeChargesCharged = feeChargesCharged;
this.feeChargesPaid = feeChargesPaid;
this.feeChargesWrittenOff = feeChargesWrittenOff;
this.feeChargesWaived = feeChargesWaived;
this.penaltyCharges = penaltyCharges;
this.penaltyChargesPaid = penaltyChargesPaid;
this.penaltyChargesWrittenOff = penaltyChargesWrittenOff;
this.penaltyChargesWaived = penaltyChargesWaived;
this.totalPaidInAdvance = totalPaidInAdvance;
this.totalPaidLate = totalPaidLate;
this.obligationsMet = obligationsMet;
this.obligationsMetOnDate = obligationsMetOnDate;
this.createdOnDate = createdOnDate;
this.createdByUser = createdByUser;
this.lastModifiedByUser = lastModifiedByUser;
this.lastModifiedOnDate = lastModifiedOnDate;
}
/**
* @return an instance of the LoanRepaymentScheduleHistory class
**/
public static LoanRepaymentScheduleHistory instance(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
return new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
principalCompleted, principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges, penaltyChargesPaid,
penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate, obligationsMet,
obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
}
@SuppressWarnings("unused")
public static void create(final Loan loan, final LoanRescheduleRequest loanRescheduleRequest,
final Integer installmentNumber, final Date fromDate, final Date dueDate, final BigDecimal principal,
final BigDecimal principalCompleted, final BigDecimal principalWrittenOff, final BigDecimal interestCharged,
final BigDecimal interestPaid, final BigDecimal interestWaived, final BigDecimal interestWrittenOff,
final BigDecimal feeChargesCharged, final BigDecimal feeChargesPaid, final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesWaived, final BigDecimal penaltyCharges, final BigDecimal penaltyChargesPaid,
final BigDecimal penaltyChargesWrittenOff, final BigDecimal penaltyChargesWaived,
final BigDecimal totalPaidInAdvance, final BigDecimal totalPaidLate, final boolean obligationsMet,
final Date obligationsMetOnDate, final Date createdOnDate, final AppUser createdByUser,
final AppUser lastModifiedByUser, final Date lastModifiedOnDate) {
new LoanRepaymentScheduleHistory(loan, loanRescheduleRequest, installmentNumber, fromDate, dueDate, principal,
principalCompleted, principalWrittenOff, interestCharged, interestPaid, interestWaived, interestWrittenOff,
feeChargesCharged, feeChargesPaid, feeChargesWrittenOff, feeChargesWaived, penaltyCharges, penaltyChargesPaid,
penaltyChargesWrittenOff, penaltyChargesWaived, totalPaidInAdvance, totalPaidLate, obligationsMet,
obligationsMetOnDate, createdOnDate, createdByUser, lastModifiedByUser, lastModifiedOnDate);
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LoanRepaymentScheduleHistoryRepository extends JpaRepository<LoanRepaymentScheduleHistory, Long>, JpaSpecificationExecutor<LoanRepaymentScheduleHistory> {
}
@@ -0,0 +1,34 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
public interface LoanRescheduleModalPeriod {
LoanSchedulePeriodData toData();
Integer periodNumber();
Integer oldPeriodNumber();
LocalDate periodFromDate();
LocalDate periodDueDate();
BigDecimal principalDue();
BigDecimal interestDue();
BigDecimal feeChargesDue();
BigDecimal penaltyChargesDue();
boolean isNew();
}
@@ -0,0 +1,93 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
public final class LoanRescheduleModel {
private final Collection<LoanRescheduleModelRepaymentPeriod> periods;
private final Collection<LoanRepaymentScheduleHistory> oldPeriods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Money totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected;
private final BigDecimal totalPrincipalPaid;
private final BigDecimal totalInterestCharged;
private final BigDecimal totalFeeChargesCharged;
private final BigDecimal totalPenaltyChargesCharged;
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding;
public LoanRescheduleModel(final Collection<LoanRescheduleModelRepaymentPeriod> periods, final Collection<LoanRepaymentScheduleHistory> oldPeriods,
final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed,
final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected,
final BigDecimal totalOutstanding) {
this.periods = periods;
this.oldPeriods = oldPeriods;
this.applicationCurrency = applicationCurrency;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = principalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = totalPrincipalPaid;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstanding = totalOutstanding;
}
public static LoanRescheduleModel instance(final Collection<LoanRescheduleModelRepaymentPeriod> periods, final Collection<LoanRepaymentScheduleHistory> oldPeriods,
final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed,
final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged,
final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected,
final BigDecimal totalOutstanding) {
return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstanding);
}
public LoanScheduleData toData() {
final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<LoanSchedulePeriodData>();
for (final LoanRescheduleModalPeriod modelPeriod : this.periods) {
periodsData.add(modelPeriod.toData());
}
final BigDecimal totalWaived = null;
final BigDecimal totalWrittenOff = null;
final BigDecimal totalRepayment = null;
final BigDecimal totalPaidInAdvance = null;
final BigDecimal totalPaidLate = null;
return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
totalPaidInAdvance, totalPaidLate, this.totalOutstanding);
}
public Collection<LoanRescheduleModelRepaymentPeriod> getPeriods() {
return this.periods;
}
public Collection<LoanRepaymentScheduleHistory> getOldPeriods() {
return this.oldPeriods;
}
}
@@ -0,0 +1,171 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
public final class LoanRescheduleModelRepaymentPeriod implements LoanRescheduleModalPeriod {
private int periodNumber;
private int oldPeriodNumber;
private LocalDate fromDate;
private LocalDate dueDate;
private Money principalDue;
private Money outstandingLoanBalance;
private Money interestDue;
private Money feeChargesDue;
private Money penaltyChargesDue;
private Money totalDue;
private boolean isNew;
public LoanRescheduleModelRepaymentPeriod(final int periodNumber, final int oldPeriodNumber, LocalDate fromDate,
final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, final boolean isNew) {
this.periodNumber = periodNumber;
this.oldPeriodNumber = oldPeriodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principalDue = principalDue;
this.outstandingLoanBalance = outstandingLoanBalance;
this.interestDue = interestDue;
this.feeChargesDue = feeChargesDue;
this.penaltyChargesDue = penaltyChargesDue;
this.totalDue = totalDue;
this.isNew = isNew;
}
public static LoanRescheduleModelRepaymentPeriod instance(final int periodNumber, final int oldPeriodNumber, LocalDate fromDate,
final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, final boolean isNew) {
return new LoanRescheduleModelRepaymentPeriod(periodNumber, oldPeriodNumber, fromDate, dueDate, principalDue,
outstandingLoanBalance, interestDue, feeChargesDue, penaltyChargesDue, totalDue, isNew);
}
@Override
public LoanSchedulePeriodData toData() {
return LoanSchedulePeriodData.repaymentOnlyPeriod(this.periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
this.penaltyChargesDue.getAmount(), this.totalDue.getAmount());
}
@Override
public Integer periodNumber() {
return this.periodNumber;
}
@Override
public Integer oldPeriodNumber() {
return this.oldPeriodNumber;
}
@Override
public LocalDate periodFromDate() {
return this.fromDate;
}
@Override
public LocalDate periodDueDate() {
return this.dueDate;
}
@Override
public BigDecimal principalDue() {
BigDecimal value = null;
if (this.principalDue != null) {
value = this.principalDue.getAmount();
}
return value;
}
@Override
public BigDecimal interestDue() {
BigDecimal value = null;
if (this.interestDue != null) {
value = this.interestDue.getAmount();
}
return value;
}
@Override
public BigDecimal feeChargesDue() {
BigDecimal value = null;
if (this.feeChargesDue != null) {
value = this.feeChargesDue.getAmount();
}
return value;
}
@Override
public BigDecimal penaltyChargesDue() {
BigDecimal value = null;
if (this.penaltyChargesDue != null) {
value = this.penaltyChargesDue.getAmount();
}
return value;
}
@Override
public boolean isNew() {
return isNew;
}
public void updatePeriodNumber(Integer periodNumber) {
this.periodNumber = periodNumber;
}
public void updateOldPeriodNumber(Integer oldPeriodNumber) {
this.oldPeriodNumber = oldPeriodNumber;
}
public void updatePeriodFromDate(LocalDate periodFromDate) {
this.fromDate = periodFromDate;
}
public void updatePeriodDueDate(LocalDate periodDueDate) {
this.dueDate = periodDueDate;
}
public void updatePrincipalDue(Money principalDue) {
this.principalDue = principalDue;
}
public void updateInterestDue(Money interestDue) {
this.interestDue = interestDue;
}
public void updateFeeChargesDue(Money feeChargesDue) {
this.feeChargesDue = feeChargesDue;
}
public void updatePenaltyChargesDue(Money penaltyChargesDue) {
this.penaltyChargesDue = penaltyChargesDue;
}
public void updateOutstandingLoanBalance(Money outstandingLoanBalance) {
this.outstandingLoanBalance = outstandingLoanBalance;
}
public void updateTotalDue(Money totalDue) {
this.totalDue = totalDue;
}
public void updateIsNew(boolean isNew) {
this.isNew = isNew;
}
}
@@ -0,0 +1,338 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_reschedule_request")
public class LoanRescheduleRequest extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "status_enum", nullable = false)
private Integer statusEnum;
@Column(name = "reschedule_from_installment")
private Integer rescheduleFromInstallment;
@Column(name = "grace_on_principal")
private Integer graceOnPrincipal;
@Column(name = "grace_on_interest")
private Integer graceOnInterest;
@Temporal(TemporalType.DATE)
@Column(name = "reschedule_from_date")
private Date rescheduleFromDate;
@Temporal(TemporalType.DATE)
@Column(name = "adjusted_due_date")
private Date adjustedDueDate;
@Column(name = "extra_terms")
private Integer extraTerms;
@Column(name = "recalculate_interest")
private Boolean recalculateInterest;
@Column(name = "interest_rate", scale = 6, precision = 19)
private BigDecimal interestRate;
@ManyToOne
@JoinColumn(name = "reschedule_reason_cv_id")
private CodeValue rescheduleReasonCodeValue;
@Column(name = "reschedule_reason_comment")
private String rescheduleReasonComment;
@Temporal(TemporalType.DATE)
@Column(name = "submitted_on_date")
private Date submittedOnDate;
@ManyToOne
@JoinColumn(name = "submitted_by_user_id")
private AppUser submittedByUser;
@Temporal(TemporalType.DATE)
@Column(name = "approved_on_date")
private Date approvedOnDate;
@ManyToOne
@JoinColumn(name = "approved_by_user_id")
private AppUser approvedByUser;
@Temporal(TemporalType.DATE)
@Column(name = "rejected_on_date")
private Date rejectedOnDate;
@ManyToOne
@JoinColumn(name = "rejected_by_user_id")
private AppUser rejectedByUser;
/**
* LoanRescheduleRequest constructor
**/
protected LoanRescheduleRequest() {}
/**
* LoanRescheduleRequest constructor
**/
private LoanRescheduleRequest(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser,
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
this.loan = loan;
this.statusEnum = statusEnum;
this.rescheduleFromInstallment = rescheduleFromInstallment;
this.graceOnPrincipal = graceOnPrincipal;
this.graceOnInterest = graceOnInterest;
this.rescheduleFromDate = rescheduleFromDate;
this.extraTerms = extraTerms;
this.interestRate = interestRate;
this.rescheduleReasonCodeValue = rescheduleReasonCodeValue;
this.rescheduleReasonComment = rescheduleReasonComment;
this.submittedOnDate = submittedOnDate;
this.submittedByUser = submittedByUser;
this.approvedOnDate = approvedOnDate;
this.approvedByUser = approvedByUser;
this.rejectedOnDate = rejectedOnDate;
this.rejectedByUser = rejectedByUser;
this.adjustedDueDate = adjustedDueDate;
this.recalculateInterest = recalculateInterest;
}
/**
* @return a new instance of the LoanRescheduleRequest class
**/
public static LoanRescheduleRequest instance(final Loan loan, final Integer statusEnum, final Integer rescheduleFromInstallment,
final Integer graceOnPrincipal, final Integer graceOnInterest, final Date rescheduleFromDate, final Date adjustedDueDate,
final Integer extraTerms, final Boolean recalculateInterest, final BigDecimal interestRate, final CodeValue rescheduleReasonCodeValue,
final String rescheduleReasonComment, final Date submittedOnDate, final AppUser submittedByUser,
final Date approvedOnDate, final AppUser approvedByUser, final Date rejectedOnDate, AppUser rejectedByUser) {
return new LoanRescheduleRequest(loan, statusEnum, rescheduleFromInstallment, graceOnPrincipal, graceOnInterest,
rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue, rescheduleReasonComment, submittedOnDate,
submittedByUser, approvedOnDate, approvedByUser, rejectedOnDate, rejectedByUser);
}
/**
* @return the reschedule request loan object
**/
public Loan getLoan() {
return this.loan;
}
/**
* @return the status enum
**/
public Integer getStatusEnum() {
return this.statusEnum;
}
/**
* @return installment number of the rescheduling start point
**/
public Integer getRescheduleFromInstallment() {
return this.rescheduleFromInstallment;
}
/**
* @return the grace on principal
**/
public Integer getGraceOnPrincipal() {
return this.graceOnPrincipal;
}
/**
* @return the grace on interest
**/
public Integer getGraceOnInterest() {
return this.graceOnInterest;
}
/**
* @return due date of the rescheduling start point
**/
public LocalDate getRescheduleFromDate() {
LocalDate localDate = null;
if(this.rescheduleFromDate != null) {
localDate = new LocalDate(this.rescheduleFromDate);
}
return localDate;
}
/**
* @return due date of the first rescheduled installment
**/
public LocalDate getAdjustedDueDate() {
LocalDate localDate = null;
if(this.adjustedDueDate != null) {
localDate = new LocalDate(this.adjustedDueDate);
}
return localDate;
}
/**
* @return extra terms to be added after the last loan installment
**/
public Integer getExtraTerms() {
return this.extraTerms;
}
/**
* @return the new interest rate to be applied to unpaid installments
**/
public BigDecimal getInterestRate() {
return this.interestRate;
}
/**
* @return the reschedule reason code value object
**/
public CodeValue getRescheduleReasonCodeValue() {
return this.rescheduleReasonCodeValue;
}
/**
* @return the reschedule reason comment added by the "submittedByUser"
**/
public String getRescheduleReasonComment() {
return this.rescheduleReasonComment;
}
/**
* @return the date the request was submitted
**/
public LocalDate getSubmittedOnDate() {
LocalDate localDate = null;
if(this.submittedOnDate != null) {
localDate = new LocalDate(this.submittedOnDate);
}
return localDate;
}
/**
* @return the user that submitted the request
**/
public AppUser getSubmittedByUser() {
return this.submittedByUser;
}
/**
* @return the date the request was approved
**/
public LocalDate getApprovedOnDate() {
LocalDate localDate = null;
if(this.approvedOnDate != null) {
localDate = new LocalDate(this.approvedOnDate);
}
return localDate;
}
/**
* @return the user that approved the request
**/
public AppUser getApprovedByUser() {
return this.approvedByUser;
}
/**
* @return the date the request was rejected
**/
public LocalDate getRejectedOnDate() {
LocalDate localDate = null;
if(this.rejectedOnDate != null) {
localDate = new LocalDate(this.rejectedOnDate);
}
return localDate;
}
/**
* @return the recalculate interest option (true/false)
**/
public Boolean getRecalculateInterest() {
boolean recalculateInterest = false;
if(this.recalculateInterest != null) {
recalculateInterest = this.recalculateInterest;
}
return recalculateInterest;
}
/**
* @return the user that rejected the request
**/
public AppUser getRejectedByUser() {
return this.approvedByUser;
}
/**
* change the status of the loan reschedule request to approved, also updating the
* approvedByUser and approvedOnDate properties
*
* @param approvedByUser the user who approved the request
* @param approvedOnDate the date of the approval
* @return void
**/
public void approve(final AppUser approvedByUser, final LocalDate approvedOnDate) {
if(approvedOnDate != null) {
this.approvedByUser = approvedByUser;
this.approvedOnDate = approvedOnDate.toDate();
this.statusEnum = LoanStatus.APPROVED.getValue();
}
}
/**
* change the status of the loan reschedule request to rejected, also updating the
* approvedByUser and approvedOnDate properties
*
* @param approvedByUser the user who approved the request
* @param approvedOnDate the date of the approval
* @return void
**/
public void reject(final AppUser approvedByUser, final LocalDate approvedOnDate) {
if(approvedOnDate != null) {
this.rejectedByUser = approvedByUser;
this.rejectedOnDate = approvedOnDate.toDate();
this.statusEnum = LoanStatus.REJECTED.getValue();
}
}
}
@@ -0,0 +1,14 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LoanRescheduleRequestRepository extends JpaRepository<LoanRescheduleRequest, Long>, JpaSpecificationExecutor<LoanRescheduleRequest> {
}
@@ -0,0 +1,21 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext;
import java.util.List;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays);
}
@@ -0,0 +1,26 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when loan reschedule request resources are not found.
**/
public class LoanRescheduleRequestNotFoundException extends AbstractPlatformResourceNotFoundException {
/**
* LoanRescheduleRequestNotFoundException constructor
*
* @param requestId the loan reschedule request identifier
* @return void
**/
public LoanRescheduleRequestNotFoundException(final Long requestId) {
super("error.msg.loan.reschedule.request.id.invalid",
"Loan reschedule request with identifier " + requestId + " does not exist", requestId);
}
}
@@ -0,0 +1,31 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ApproveLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
@Autowired
public ApproveLoanRescheduleRequestCommandHandler(
LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
return this.loanRescheduleRequestWritePlatformService.approve(jsonCommand);
}
}
@@ -0,0 +1,33 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
@Autowired
public CreateLoanRescheduleRequestCommandHandler(
LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
return this.loanRescheduleRequestWritePlatformService.create(jsonCommand);
}
}
@@ -0,0 +1,31 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RejectLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
@Autowired
public RejectLoanRescheduleRequestCommandHandler(
LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
return this.loanRescheduleRequestWritePlatformService.reject(jsonCommand);
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
public interface LoanReschedulePreviewPlatformService {
public LoanRescheduleModel previewLoanReschedule(Long requestId);
}
@@ -0,0 +1,82 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.List;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoanReschedulePreviewPlatformServiceImpl implements LoanReschedulePreviewPlatformService {
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
@Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final ConfigurationDomainService configurationDomainService,
final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
}
@Override
public LoanRescheduleModel previewLoanReschedule(Long requestId) {
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(requestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(requestId);
}
Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mathContext = new MathContext(8, roundingMode);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
return loanRescheduleModel;
}
}
@@ -0,0 +1,37 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.util.List;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
public interface LoanRescheduleRequestReadPlatformService {
/**
* get all loan reschedule requests by loan ID
*
* @param loanId the loan identifier
* @return list of LoanRescheduleRequestData objects
**/
public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId);
/**
* get a single loan reschedule request by ID (primary key)
*
* @param requestId the loan reschedule request identifier
* @return a LoanRescheduleRequestData object
**/
public LoanRescheduleRequestData readLoanRescheduleRequest(Long requestId);
/**
* get all loan reschedule requests filter by loan ID and status enum
*
* @param loanId the loan identifier
* @return list of LoanRescheduleRequestData objects
**/
public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId, Integer statusEnum);
}
@@ -0,0 +1,193 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestData;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestEnumerations;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestTimelineData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class LoanRescheduleRequestReadPlatformServiceImpl implements LoanRescheduleRequestReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final LoanRepository loanRepository;
private final LoanRescheduleRequestRowMapper loanRescheduleRequestRowMapper = new LoanRescheduleRequestRowMapper();
@Autowired
public LoanRescheduleRequestReadPlatformServiceImpl(final RoutingDataSource dataSource,
LoanRepository loanRepository) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.loanRepository = loanRepository;
}
private static final class LoanRescheduleRequestRowMapper implements RowMapper<LoanRescheduleRequestData> {
private final String schema;
public LoanRescheduleRequestRowMapper() {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("lr.id as id, lr.loan_id as loanId, lr.status_enum as statusEnum, ");
sqlBuilder.append("mc.display_name as clientName, ");
sqlBuilder.append("mc.id as clientId, ");
sqlBuilder.append("ml.account_no as loanAccountNumber, ");
sqlBuilder.append("lr.reschedule_from_installment as rescheduleFromInstallment, ");
sqlBuilder.append("lr.grace_on_principal as graceOnPrincipal, ");
sqlBuilder.append("lr.grace_on_interest as graceOnInterest, ");
sqlBuilder.append("lr.reschedule_from_date as rescheduleFromDate, ");
sqlBuilder.append("lr.adjusted_due_date as adjustedDueDate, ");
sqlBuilder.append("lr.extra_terms as extraTerms, ");
sqlBuilder.append("lr.recalculate_interest as recalculateInterest, ");
sqlBuilder.append("lr.interest_rate as interestRate, ");
sqlBuilder.append("lr.reschedule_reason_cv_id as rescheduleReasonCvId, ");
sqlBuilder.append("cv.code_value as rescheduleReasonCvValue, ");
sqlBuilder.append("lr.reschedule_reason_comment as rescheduleReasonComment, ");
sqlBuilder.append("lr.submitted_on_date as submittedOnDate, ");
sqlBuilder.append("sbu.username as submittedByUsername, ");
sqlBuilder.append("sbu.firstname as submittedByFirstname, ");
sqlBuilder.append("sbu.lastname as submittedByLastname, ");
sqlBuilder.append("lr.approved_on_date as approvedOnDate, ");
sqlBuilder.append("abu.username as approvedByUsername, ");
sqlBuilder.append("abu.firstname as approvedByFirstname, ");
sqlBuilder.append("abu.lastname as approvedByLastname, ");
sqlBuilder.append("lr.rejected_on_date as rejectedOnDate, ");
sqlBuilder.append("rbu.username as rejectedByUsername, ");
sqlBuilder.append("rbu.firstname as rejectedByFirstname, ");
sqlBuilder.append("rbu.lastname as rejectedByLastname ");
sqlBuilder.append("from " + loanRescheduleRequestTableName() + " lr ");
sqlBuilder.append("left join m_code_value cv on cv.id = lr.reschedule_reason_cv_id ");
sqlBuilder.append("left join m_appuser sbu on sbu.id = lr.submitted_by_user_id ");
sqlBuilder.append("left join m_appuser abu on abu.id = lr.approved_by_user_id ");
sqlBuilder.append("left join m_appuser rbu on rbu.id = lr.rejected_by_user_id ");
sqlBuilder.append("left join m_loan ml on ml.id = lr.loan_id ");
sqlBuilder.append("left join m_client mc on mc.id = ml.client_id ");
this.schema = sqlBuilder.toString();
}
public String schema() {
return this.schema;
}
public String loanRescheduleRequestTableName() {
return "m_loan_reschedule_request";
}
@Override
@SuppressWarnings("unused")
public LoanRescheduleRequestData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long loanId = rs.getLong("loanId");
final Integer statusEnumId = JdbcSupport.getInteger(rs, "statusEnum");
final LoanRescheduleRequestStatusEnumData statusEnum = LoanRescheduleRequestEnumerations
.status(statusEnumId);
final String clientName = rs.getString("clientName");
final String loanAccountNumber = rs.getString("loanAccountNumber");
final Long clientId = rs.getLong("clientId");
final Integer rescheduleFromInstallment = JdbcSupport.getInteger(rs, "rescheduleFromInstallment");
final Integer graceOnPrincipal = JdbcSupport.getInteger(rs, "graceOnPrincipal");
final Integer graceOnInterest = JdbcSupport.getInteger(rs, "graceOnInterest");
final LocalDate rescheduleFromDate = JdbcSupport.getLocalDate(rs, "rescheduleFromDate");
final LocalDate adjustedDueDate = JdbcSupport.getLocalDate(rs, "adjustedDueDate");
final Integer extraTerms = JdbcSupport.getInteger(rs, "extraTerms");
final BigDecimal interestRate = rs.getBigDecimal("interestRate");
final Long rescheduleReasonCvId = JdbcSupport.getLong(rs, "rescheduleReasonCvId");
final String rescheduleReasonCvValue = rs.getString("rescheduleReasonCvValue");
final CodeValueData rescheduleReasonCodeValue = CodeValueData
.instance(rescheduleReasonCvId, rescheduleReasonCvValue);
final String rescheduleReasonComment = rs.getString("rescheduleReasonComment");
final Boolean recalculateInterest = rs.getBoolean("recalculateInterest");
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname");
final String submittedByLastname = rs.getString("submittedByLastname");
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
final String approvedByUsername = rs.getString("approvedByUsername");
final String approvedByFirstname = rs.getString("approvedByFirstname");
final String approvedByLastname = rs.getString("approvedByLastname");
final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
final String rejectedByUsername = rs.getString("rejectedByUsername");
final String rejectedByFirstname = rs.getString("rejectedByFirstname");
final String rejectedByLastname = rs.getString("rejectedByLastname");
final LoanRescheduleRequestTimelineData timeline = new LoanRescheduleRequestTimelineData(submittedOnDate,
submittedByUsername, submittedByFirstname, submittedByLastname, approvedOnDate, approvedByUsername,
approvedByFirstname, approvedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname,
rejectedByLastname);
return LoanRescheduleRequestData.instance(id, loanId, statusEnum, rescheduleFromInstallment, graceOnPrincipal,
graceOnInterest, rescheduleFromDate, adjustedDueDate, extraTerms, interestRate, rescheduleReasonCodeValue,
rescheduleReasonComment, timeline, clientName, loanAccountNumber, clientId, recalculateInterest);
}
}
@Override
public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId) {
final Loan loan = this.loanRepository.findOne(loanId);
if(loan == null) {
throw new LoanNotFoundException(loanId);
}
final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.loan_id = ?";
return this.jdbcTemplate.query(sql, this.loanRescheduleRequestRowMapper, new Object[] { loanId });
}
@Override
public LoanRescheduleRequestData readLoanRescheduleRequest(Long requestId) {
try {
final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.loanRescheduleRequestRowMapper, new Object[] { requestId });
}
catch(final EmptyResultDataAccessException e) {
return null;
}
}
@Override
public List<LoanRescheduleRequestData> readLoanRescheduleRequests(Long loanId, Integer statusEnum) {
final Loan loan = this.loanRepository.findOne(loanId);
if(loan == null) {
throw new LoanNotFoundException(loanId);
}
final String sql = "select " + this.loanRescheduleRequestRowMapper.schema() + " where lr.loan_id = ?"
+ " and lr.status_enum = ?";
return this.jdbcTemplate.query(sql, this.loanRescheduleRequestRowMapper, new Object[] { loanId, statusEnum });
}
}
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface LoanRescheduleRequestWritePlatformService {
CommandProcessingResult create(JsonCommand jsonCommand);
CommandProcessingResult approve(JsonCommand jsonCommand);
CommandProcessingResult reject(JsonCommand jsonCommand);
}
@@ -0,0 +1,479 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRepaymentScheduleHistoryRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final PlatformSecurityContext platformSecurityContext;
private final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator;
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository;
/**
* LoanRescheduleRequestWritePlatformServiceImpl constructor
*
* @return void
**/
@Autowired
public LoanRescheduleRequestWritePlatformServiceImpl(LoanRepositoryWrapper loanRepositoryWrapper,
CodeValueRepositoryWrapper codeValueRepositoryWrapper,
PlatformSecurityContext platformSecurityContext,
LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
LoanRescheduleRequestRepository loanRescheduleRequestRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository,
WorkingDaysRepositoryWrapper workingDaysRepository,
LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository) {
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.platformSecurityContext = platformSecurityContext;
this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
}
/**
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object
* and persist
*
* @return CommandProcessingResult object
**/
@Override
@Transactional
public CommandProcessingResult create(JsonCommand jsonCommand) {
try {
// get the loan id from the JsonCommand object
final Long loanId = jsonCommand.longValueOfParameterNamed(
RescheduleLoansApiConstants.loanIdParamName);
// use the loan id to get a Loan entity object
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForCreateAction(jsonCommand, loan);
// get the reschedule reason code value id from the JsonCommand object
final Long rescheduleReasonId = jsonCommand.longValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleReasonIdParamName);
// use the reschedule reason code value id to get a CodeValue entity object
final CodeValue rescheduleReasonCodeValue = this.codeValueRepositoryWrapper
.findOneWithNotFoundDetection(rescheduleReasonId);
// get the grace on principal integer value from the JsonCommand object
final Integer graceOnPrincipal = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.graceOnPrincipalParamName);
// get the grace on interest integer value from the JsonCommand object
final Integer graceOnInterest = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.graceOnInterestParamName);
// get the extra terms to be added at the end of the new schedule from the JsonCommand object
final Integer extraTerms = jsonCommand.integerValueOfParameterNamed(
RescheduleLoansApiConstants.extraTermsParamName);
// get the new interest rate that would be applied to the new loan schedule
final BigDecimal interestRate = jsonCommand.bigDecimalValueOfParameterNamed(
RescheduleLoansApiConstants.newInterestRateParamName);
// get the reschedule reason comment text from the JsonCommand object
final String rescheduleReasonComment = jsonCommand.stringValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleReasonCommentParamName);
// get the recalculate interest option
final Boolean recalculateInterest = jsonCommand.booleanObjectValueOfParameterNamed(
RescheduleLoansApiConstants.recalculateInterestParamName);
// initialize set the value to null
Date submittedOnDate = null;
// check if the parameter is in the JsonCommand object
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.submittedOnDateParamName)) {
// create a LocalDate object from the "submittedOnDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.submittedOnDateParamName);
if(localDate != null) {
// update the value of the "submittedOnDate" variable
submittedOnDate = localDate.toDate();
}
}
// initially set the value to null
Date rescheduleFromDate = null;
// start point of the rescheduling exercise
Integer rescheduleFromInstallment = null;
// initially set the value to null
Date adjustedDueDate = null;
// check if the parameter is in the JsonCommand object
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)) {
// create a LocalDate object from the "rescheduleFromDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.rescheduleFromDateParamName);
if(localDate != null) {
// get installment by due date
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(localDate);
rescheduleFromInstallment = installment.getInstallmentNumber();
// update the value of the "rescheduleFromDate" variable
rescheduleFromDate = localDate.toDate();
}
}
if(jsonCommand.hasParameter(RescheduleLoansApiConstants.adjustedDueDateParamName)) {
// create a LocalDate object from the "adjustedDueDate" Date string
LocalDate localDate = jsonCommand.localDateValueOfParameterNamed(
RescheduleLoansApiConstants.adjustedDueDateParamName);
if(localDate != null) {
// update the value of the "adjustedDueDate"variable
adjustedDueDate = localDate.toDate();
}
}
final LoanRescheduleRequest loanRescheduleRequest = LoanRescheduleRequest.instance(
loan, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), rescheduleFromInstallment, graceOnPrincipal,
graceOnInterest, rescheduleFromDate, adjustedDueDate, extraTerms, recalculateInterest, interestRate, rescheduleReasonCodeValue,
rescheduleReasonComment, submittedOnDate, this.platformSecurityContext.authenticatedUser(),
null, null, null, null);
// create a new entry in the m_loan_reschedule_request table
this.loanRescheduleRequestRepository.save(loanRescheduleRequest);
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequest.getId())
.withLoanId(loan.getId())
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleCreateActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult approve(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = this.loanRescheduleRequestRepository.findOne(loanRescheduleRequestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId);
}
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForApproveAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat
.forPattern(jsonCommand.dateFormat())
.withLocale(jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("approvedOnDate", approvedOnDate.toString(dateTimeFormatter));
changes.put("approvedByUserId", appUser.getId());
if(!changes.isEmpty()) {
Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mathContext = new MathContext(8, roundingMode);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for(LoanRescheduleModelRepaymentPeriod period : periods) {
if(period.isNew()) {
LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(
loan, period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(),
period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false);
repaymentScheduleInstallments.add(repaymentScheduleInstallment);
}
else {
for(LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {
if(repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) {
repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber());
repaymentScheduleInstallment.updateFromDate(period.periodFromDate());
repaymentScheduleInstallment.updateDueDate(period.periodDueDate());
repaymentScheduleInstallment.updatePrincipal(period.principalDue());
repaymentScheduleInstallment.updateInterestCharged(period.interestDue());
if(Money.of(currency, period.principalDue()).isZero() &&
Money.of(currency, period.interestDue()).isZero() &&
repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isZero() &&
repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isZero() &&
repaymentScheduleInstallment.isNotFullyPaidOff()) {
repaymentScheduleInstallment.updateObligationMet(true);
repaymentScheduleInstallment.updateObligationMetOnDate(new LocalDate());
}
break;
}
}
}
}
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = loanRescheduleModel.getOldPeriods();
for(LoanRepaymentScheduleHistory loanRepaymentScheduleHistory : loanRepaymentScheduleHistoryList) {
this.loanRepaymentScheduleHistoryRepository.save(loanRepaymentScheduleHistory);
}
loan.updateRescheduledByUser(appUser);
loan.updateRescheduledOnDate(new LocalDate());
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(),
true);
// update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size());
// update the loan term frequency (loan term frequency = number of repayments)
loan.updateTermFrequency(periods.size());
// update the status of the request
loanRescheduleRequest.approve(appUser, approvedOnDate);
}
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId())
.with(changes)
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleApproveActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
@Override
@Transactional
public CommandProcessingResult reject(JsonCommand jsonCommand) {
try {
final Long loanRescheduleRequestId = jsonCommand.entityId();
final LoanRescheduleRequest loanRescheduleRequest = loanRescheduleRequestRepository
.findOne(loanRescheduleRequestId);
if(loanRescheduleRequest == null) {
throw new LoanRescheduleRequestNotFoundException(loanRescheduleRequestId);
}
// validate the request in the JsonCommand object passed as parameter
this.loanRescheduleRequestDataValidator.validateForRejectAction(jsonCommand, loanRescheduleRequest);
final AppUser appUser = this.platformSecurityContext.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat
.forPattern(jsonCommand.dateFormat())
.withLocale(jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat());
changes.put("rejectedOnDate", rejectedOnDate.toString(dateTimeFormatter));
changes.put("rejectedByUserId", appUser.getId());
if(!changes.isEmpty()) {
loanRescheduleRequest.reject(appUser, rejectedOnDate);
}
return new CommandProcessingResultBuilder()
.withCommandId(jsonCommand.commandId())
.withEntityId(loanRescheduleRequestId)
.withLoanId(loanRescheduleRequest.getLoan().getId())
.with(changes)
.build();
}
catch(final DataIntegrityViolationException dve) {
// handle the data integrity violation
handleRejectActionDataIntegrityViolation(jsonCommand, dve);
// return an empty command processing result object
return CommandProcessingResult.empty();
}
}
/**
* handles the data integrity violation exception caught in the create method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleCreateActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
/**
* handles the data integrity violation exception caught in the reject method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleRejectActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
/**
* handles the data integrity violation exception caught in the approve method
*
* @param jsonCommand JSON command object
* @param dve data integrity violation exception
* @return void
**/
@SuppressWarnings("unused")
private void handleApproveActionDataIntegrityViolation(final JsonCommand jsonCommand,
final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
}
@@ -5,6 +5,10 @@
*/
package org.mifosplatform.portfolio.loanproduct.domain;
import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
/**
@@ -13,9 +17,35 @@ import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
*/
public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
MonetaryCurrency getCurrency();
Money getPrincipal();
Integer graceOnInterestCharged();
Integer graceOnInterestPayment();
Integer graceOnPrincipalPayment();
Money getInArrearsTolerance();
BigDecimal getNominalInterestRatePerPeriod();
PeriodFrequencyType getInterestPeriodFrequencyType();
BigDecimal getAnnualNominalInterestRate();
InterestMethod getInterestMethod();
InterestCalculationPeriodMethod getInterestCalculationPeriodMethod();
Integer getRepayEvery();
PeriodFrequencyType getRepaymentPeriodFrequencyType();
Integer getNumberOfRepayments();
AmortizationMethod getAmortizationMethod();
Integer getGraceOnDueDate();
}
@@ -169,51 +169,62 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return defaultTo;
}
public MonetaryCurrency getCurrency() {
@Override
public MonetaryCurrency getCurrency() {
return this.currency.copy();
}
public Money getPrincipal() {
@Override
public Money getPrincipal() {
return Money.of(this.currency, this.principal);
}
public void setPrincipal(BigDecimal principal) {
this.principal = principal;
}
public Integer graceOnInterestCharged() {
@Override
public Integer graceOnInterestCharged() {
return this.graceOnInterestCharged;
}
public Integer graceOnInterestPayment() {
@Override
public Integer graceOnInterestPayment() {
return this.graceOnInterestPayment;
}
public Integer graceOnPrincipalPayment() {
@Override
public Integer graceOnPrincipalPayment() {
return this.graceOnPrincipalPayment;
}
public Money getInArrearsTolerance() {
@Override
public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance);
}
public BigDecimal getNominalInterestRatePerPeriod() {
@Override
public BigDecimal getNominalInterestRatePerPeriod() {
return BigDecimal.valueOf(Double.valueOf(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString()));
}
public PeriodFrequencyType getInterestPeriodFrequencyType() {
@Override
public PeriodFrequencyType getInterestPeriodFrequencyType() {
return this.interestPeriodFrequencyType;
}
public BigDecimal getAnnualNominalInterestRate() {
@Override
public BigDecimal getAnnualNominalInterestRate() {
return BigDecimal.valueOf(Double.valueOf(this.annualNominalInterestRate.stripTrailingZeros().toString()));
}
public InterestMethod getInterestMethod() {
@Override
public InterestMethod getInterestMethod() {
return this.interestMethod;
}
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
@Override
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
return this.interestCalculationPeriodMethod;
}
@@ -232,7 +243,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return this.numberOfRepayments;
}
public AmortizationMethod getAmortizationMethod() {
@Override
public AmortizationMethod getAmortizationMethod() {
return this.amortizationMethod;
}
@@ -466,7 +478,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.interestPeriodFrequencyType = interestPeriodFrequencyType;
}
public Integer getGraceOnDueDate() {
@Override
public Integer getGraceOnDueDate() {
return this.graceOnArrearsAgeing;
}
@@ -485,4 +498,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
public void updateIsInterestRecalculationEnabled(final boolean isInterestRecalculationEnabled) {
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
}