MIFOSX-1824 API changes and test case for loan approval in case of multi-disburse loans.

This commit is contained in:
Chandrika
2015-02-24 17:36:09 +05:30
parent 7ba9a81e53
commit f6f04d7ae3
19 changed files with 448 additions and 123 deletions
+7 -1
View File
@@ -8545,6 +8545,9 @@ Request Body:
<tr class=alt> <tr class=alt>
<td>approvedLoanAmount</td> <td>approvedLoanAmount</td>
</tr> </tr>
<tr class=alt>
<td>expectedDisbursementDate</td>
</tr>
</table> </table>
</div> </div>
<div class="method-example"> <div class="method-example">
@@ -8559,7 +8562,10 @@ Request Body:
"locale": "en", "locale": "en",
"dateFormat": "dd MMMM yyyy", "dateFormat": "dd MMMM yyyy",
"approvedOnDate": "20 September 2011", "approvedOnDate": "20 September 2011",
"note": "Loan approval note" "expectedDisbursementDate" : "20 September 2011",
"note": "Loan approval note",
"disbursementData" : [{ id=226, principal="5", expectedDisbursementDate="20 September 2011"},
{ id=227, principal="91", expectedDisbursementDate="21 September 2011"}]
} }
</code> </code>
<code class="method-response"> <code class="method-response">
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.integrationtests; package org.mifosplatform.integrationtests;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -32,6 +33,7 @@ public class LoanApplicationApprovalTest {
private ResponseSpecification responseSpec; private ResponseSpecification responseSpec;
private ResponseSpecification responseSpecForStatusCode403; private ResponseSpecification responseSpecForStatusCode403;
private ResponseSpecification responseSpecForStatusCode400;
private RequestSpecification requestSpec; private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper; private LoanTransactionHelper loanTransactionHelper;
@@ -42,6 +44,7 @@ public class LoanApplicationApprovalTest {
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build(); this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build();
this.responseSpecForStatusCode400 = new ResponseSpecBuilder().expectStatusCode(400).build();
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
} }
@@ -63,7 +66,9 @@ public class LoanApplicationApprovalTest {
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, approvalAmount, loanID); final String expectedDisbursementDate = null;
List<HashMap> approveTranches = null;
loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate,expectedDisbursementDate, approvalAmount, loanID, approveTranches);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
} }
@@ -97,6 +102,161 @@ public class LoanApplicationApprovalTest {
} }
private HashMap createTrancheDetail(final String date, final String amount) {
HashMap<String, Object> detail = new HashMap<>();
detail.put("expectedDisbursementDate", date);
detail.put("principal", amount);
return detail;
}
@Test
public void loanApplicationApprovalAndValidationForMultiDisburseLoans() {
List<HashMap> createTranches = new ArrayList<>();
createTranches.add(createTrancheDetail("1 March 2014", "1000"));
createTranches.add(createTrancheDetail("23 March 2014", "4000"));
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"+clientID);
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
.withInterestTypeAsDecliningBalance().
withTranches(true).build(null));
System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"+loanProductID);
this.trancheLoansApprovedAmountLesserThanProposedAmount(clientID, loanProductID, createTranches);
this.trancheLoansApprovalValidation(clientID, loanProductID, createTranches);
}
private void trancheLoansApprovedAmountLesserThanProposedAmount(
Integer clientID, Integer loanProductID,
List<HashMap> createTranches) {
final String proposedAmount = "5000";
final String approvalAmount = "2000";
final String approveDate = "1 March 2014";
final String expectedDisbursementDate = "1 March 2014";
List<HashMap> approveTranches = new ArrayList<>();
approveTranches.add(createTrancheDetail("1 March 2014", "1000"));
approveTranches.add(createTrancheDetail("23 March 2014", "1000"));
final Integer loanID = applyForLoanApplicationWithTranches(clientID,
loanProductID, proposedAmount, createTranches);
System.out
.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+ loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(
this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
System.out
.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper
.approveLoanWithApproveAmount(approveDate,
expectedDisbursementDate, approvalAmount, loanID,
approveTranches);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
System.out
.println("-----------------------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY---------------------------------------");
}
private void trancheLoansApprovalValidation(Integer clientID,
Integer loanProductID, List<HashMap> createTranches) {
final String proposedAmount = "5000";
final String approvalAmount1 = "10000";
final String approvalAmount2 = "3000";
final String approvalAmount3 = "400";
final String approvalAmount4 = "200";
final String approvalAmount5 = "400";
final String approveDate = "1 March 2014";
final String expectedDisbursementDate = "1 March 2014";
List<HashMap> approveTranche1 = new ArrayList<>();
approveTranche1.add(createTrancheDetail("1 March 2014", "5000"));
approveTranche1.add(createTrancheDetail("23 March 2014", "5000"));
List<HashMap> approveTranche2 = new ArrayList<>();
approveTranche2.add(createTrancheDetail("1 March 2014", "1000"));
approveTranche2.add(createTrancheDetail("23 March 2014", "1000"));
approveTranche2.add(createTrancheDetail("23 March 2014", "1000"));
List<HashMap> approveTranche3 = new ArrayList<>();
approveTranche3.add(createTrancheDetail("1 March 2014", "100"));
approveTranche3.add(createTrancheDetail("23 March 2014", "100"));
approveTranche3.add(createTrancheDetail("24 March 2014", "100"));
approveTranche3.add(createTrancheDetail("25 March 2014", "100"));
List<HashMap> approveTranche4 = new ArrayList<>();
approveTranche4.add(createTrancheDetail("1 March 2014", "100"));
approveTranche4.add(createTrancheDetail("23 March 2014", "100"));
approveTranche4.add(createTrancheDetail("24 March 2014", "100"));
List<HashMap> approveTranche5 = new ArrayList<>();
final Integer loanID = applyForLoanApplicationWithTranches(clientID,
loanProductID, proposedAmount, createTranches);
System.out
.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+ loanID);
HashMap<String, Object> loanStatusHashMap = LoanStatusChecker
.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
System.out
.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
this.loanTransactionHelper = new LoanTransactionHelper(
this.requestSpec, this.responseSpecForStatusCode400);
/* Tranches with same expected disbursement date */
List<HashMap<String, Object>> error = this.loanTransactionHelper
.approveLoanForTranches(approveDate, expectedDisbursementDate,
approvalAmount2, loanID, approveTranche2,
CommonConstants.RESPONSE_ERROR);
assertEquals(
"validation.msg.loan.expectedDisbursementDate.disbursement.date.must.be.unique.for.tranches",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
/* Sum of tranches is greater than approved amount */
error = this.loanTransactionHelper.approveLoanForTranches(approveDate,
expectedDisbursementDate, approvalAmount4, loanID,
approveTranche4, CommonConstants.RESPONSE_ERROR);
assertEquals(
"validation.msg.loan.principal.sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
this.loanTransactionHelper = new LoanTransactionHelper(
this.requestSpec, this.responseSpecForStatusCode403);
/* Sum of tranches exceeds the proposed amount */
error = this.loanTransactionHelper.approveLoanForTranches(approveDate,
expectedDisbursementDate, approvalAmount1, loanID,
approveTranche1, CommonConstants.RESPONSE_ERROR);
assertEquals(
"error.msg.loan.approval.amount.can't.be.greater.than.loan.amount.demanded",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
/* No. of tranches exceeds the max tranche count at product level */
error = this.loanTransactionHelper.approveLoanForTranches(approveDate,
expectedDisbursementDate, approvalAmount3, loanID,
approveTranche3, CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.disbursementData.exceeding.max.tranche.count",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
/* If tranches are not specified for a multi-disburse loan */
error = this.loanTransactionHelper.approveLoanForTranches(approveDate,
expectedDisbursementDate, approvalAmount5, loanID,
approveTranche5, CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.disbursementData.required",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
}
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) { private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) {
final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5") final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1") .withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1")
@@ -104,4 +264,30 @@ public class LoanApplicationApprovalTest {
.withSubmittedOnDate("02 April 2012").build(clientID.toString(), loanProductID.toString(), null); .withSubmittedOnDate("02 April 2012").build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplication); return this.loanTransactionHelper.getLoanId(loanApplication);
} }
private Integer applyForLoanApplicationWithTranches(final Integer clientID,
final Integer loanProductID, String principal,
List<HashMap> tranches) {
System.out
.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder()
//
.withPrincipal(principal)
//
.withLoanTermFrequency("5")
//
.withLoanTermFrequencyAsMonths()
//
.withNumberOfRepayments("5").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths() //
.withInterestRatePerPeriod("2") //
.withExpectedDisbursementDate("1 March 2014") //
.withTranches(tranches) //
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate("1 March 2014") //
.build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
} }
@@ -77,7 +77,7 @@ public class LoanProductTestBuilder {
private Boolean multiDisburseLoan = false; private Boolean multiDisburseLoan = false;
private final String outstandingLoanBalance = "35000"; private final String outstandingLoanBalance = "35000";
private final String maxTrancheCount = "35000"; private final String maxTrancheCount = "3";
private Boolean isInterestRecalculationEnabled = false; private Boolean isInterestRecalculationEnabled = false;
private String daysInYearType = "1"; private String daysInYearType = "1";
@@ -46,7 +46,7 @@ public class LoanStatusChecker {
assertTrue(getStatus(loanStatusHashMap, "overpaid")); assertTrue(getStatus(loanStatusHashMap, "overpaid"));
} }
public static HashMap getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, public static HashMap<String, Object> getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer loanID) { final Integer loanID) {
final String url = "/mifosng-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER; final String url = "/mifosng-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER;
return Utils.performServerGet(requestSpec, responseSpec, url, "status"); return Utils.performServerGet(requestSpec, responseSpec, url, "status");
@@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.integrationtests.common.Utils; import org.mifosplatform.integrationtests.common.Utils;
@@ -107,15 +108,22 @@ public class LoanTransactionHelper {
return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID), getApproveLoanAsJSON(approvalDate)); return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID), getApproveLoanAsJSON(approvalDate));
} }
public HashMap approveLoanWithApproveAmount(final String approvalDate, final String approvalAmount, final Integer loanID) { public HashMap approveLoanWithApproveAmount(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount, final Integer loanID,
List<HashMap> tranches) {
return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID), return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID),
getApproveLoanAsJSON(approvalDate, approvalAmount)); getApproveLoanAsJSON(approvalDate, expectedDisbursementDate, approvalAmount, tranches));
}
public List<HashMap<String, Object>> approveLoanForTranches(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount, final Integer loanID,
List<HashMap> tranches, final String responseAttribute) {
return (List<HashMap<String, Object>>) performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID),
getApproveLoanAsJSON(approvalDate, expectedDisbursementDate, approvalAmount, tranches), responseAttribute);
} }
public Object approveLoan(final String approvalDate, final String approvalAmount, final Integer loanID, final String responseAttribute) { public Object approveLoan(final String approvalDate, final String approvalAmount, final Integer loanID, final String responseAttribute) {
final String approvalURL = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID); final String approvalURL = createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID);
final String approvalJSONData = getApproveLoanAsJSON(approvalDate, approvalAmount); final String approvalJSONData = getApproveLoanAsJSON(approvalDate,null, approvalAmount, null);
return performLoanTransaction(approvalURL, approvalJSONData, responseAttribute); return performLoanTransaction(approvalURL, approvalJSONData, responseAttribute);
} }
@@ -225,17 +233,24 @@ public class LoanTransactionHelper {
} }
private String getApproveLoanAsJSON(final String approvalDate) { private String getApproveLoanAsJSON(final String approvalDate) {
return getApproveLoanAsJSON(approvalDate, null); return getApproveLoanAsJSON(approvalDate,null, null, null);
} }
private String getApproveLoanAsJSON(final String approvalDate, final String approvalAmount) { private String getApproveLoanAsJSON(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount,
final HashMap<String, String> map = new HashMap<>(); List<HashMap> tranches) {
final HashMap<String, Object> map = new HashMap<>();
map.put("locale", "en"); map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy"); map.put("dateFormat", "dd MMMM yyyy");
if (approvalAmount != null) { if (approvalAmount != null) {
map.put("approvedLoanAmount", approvalAmount); map.put("approvedLoanAmount", approvalAmount);
} }
map.put("approvedOnDate", approvalDate); map.put("approvedOnDate", approvalDate);
if(expectedDisbursementDate != null){
map.put("expectedDisbursementDate", expectedDisbursementDate);
}
if(tranches != null && tranches.size() > 0){
map.put("disbursementData",tranches);
}
map.put("note", "Approval NOTE"); map.put("note", "Approval NOTE");
return new Gson().toJson(map); return new Gson().toJson(map);
} }
@@ -392,7 +407,7 @@ public class LoanTransactionHelper {
private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, final String responseAttribute) { private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, final String responseAttribute) {
return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute); return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute);
} }
public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date, public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date,
final String transactionAmount, final String responseAttribute) { final String transactionAmount, final String responseAttribute) {
return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute); return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute);
@@ -37,5 +37,6 @@ public interface LoanApiConstants {
public static final String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal"; public static final String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal";
public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches"; public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches";
public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date"; public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date";
public static final String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal";
} }
@@ -19,16 +19,13 @@ public class DisbursementData implements Comparable<DisbursementData> {
private final LocalDate expectedDisbursementDate; private final LocalDate expectedDisbursementDate;
private final LocalDate actualDisbursementDate; private final LocalDate actualDisbursementDate;
private final BigDecimal principal; private final BigDecimal principal;
@SuppressWarnings("unused")
private final BigDecimal approvedPrincipal;
public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate, public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final BigDecimal principalDisbursed, final BigDecimal approvedPrincipal) { final BigDecimal principalDisbursed) {
this.id = id; this.id = id;
this.expectedDisbursementDate = expectedDisbursementDate; this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate; this.actualDisbursementDate = actualDisbursementDate;
this.principal = principalDisbursed; this.principal = principalDisbursed;
this.approvedPrincipal = approvedPrincipal;
} }
public LocalDate disbursementDate() { public LocalDate disbursementDate() {
@@ -42,7 +39,7 @@ public class DisbursementData implements Comparable<DisbursementData> {
public BigDecimal amount() { public BigDecimal amount() {
return this.principal; return this.principal;
} }
public boolean isDisbursed() { public boolean isDisbursed() {
return this.actualDisbursementDate != null; return this.actualDisbursementDate != null;
} }
@@ -133,6 +133,6 @@ public class LoanApplicationTimelineData {
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal, public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal,
final BigDecimal approvedPrincipal, final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) { final BigDecimal approvedPrincipal, final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) {
return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal, return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal,
approvedPrincipal, inArrearsTolerance, totalFeeChargesAtDisbursement); inArrearsTolerance, totalFeeChargesAtDisbursement);
} }
} }
@@ -19,18 +19,16 @@ public class RepaymentScheduleRelatedLoanData {
private final LocalDate actualDisbursementDate; private final LocalDate actualDisbursementDate;
private final CurrencyData currency; private final CurrencyData currency;
private final BigDecimal principal; private final BigDecimal principal;
private final BigDecimal approvedPrincipal;
private final BigDecimal inArrearsTolerance; private final BigDecimal inArrearsTolerance;
private final BigDecimal totalFeeChargesAtDisbursement; private final BigDecimal totalFeeChargesAtDisbursement;
public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate, public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final CurrencyData currency, final BigDecimal principal, final BigDecimal approvedPrincipal, final CurrencyData currency, final BigDecimal principal,
final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) { final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) {
this.expectedDisbursementDate = expectedDisbursementDate; this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate; this.actualDisbursementDate = actualDisbursementDate;
this.currency = currency; this.currency = currency;
this.principal = principal; this.principal = principal;
this.approvedPrincipal = approvedPrincipal;
this.inArrearsTolerance = inArrearsTolerance; this.inArrearsTolerance = inArrearsTolerance;
this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement; this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement;
} }
@@ -64,7 +62,6 @@ public class RepaymentScheduleRelatedLoanData {
} }
public DisbursementData disbursementData() { public DisbursementData disbursementData() {
return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal);
this.approvedPrincipal);
} }
} }
@@ -1588,7 +1588,8 @@ public class Loan extends AbstractPersistable<Long> {
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) {
id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong();
} }
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, null, principal); Date actualDisbursementDate = null;
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal);
if (id == null) { if (id == null) {
disbursementDetails.updateLoan(this); disbursementDetails.updateLoan(this);
this.disbursementDetails.add(disbursementDetails); this.disbursementDetails.add(disbursementDetails);
@@ -1611,7 +1612,6 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
} }
} }
public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) { public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) {
@@ -1865,6 +1865,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
public Map<String, Object> loanApplicationApproval(final AppUser currentUser, final JsonCommand command, public Map<String, Object> loanApplicationApproval(final AppUser currentUser, final JsonCommand command,
final JsonArray disbursementDataArray,
final LoanLifecycleStateMachine loanLifecycleStateMachine) { final LoanLifecycleStateMachine loanLifecycleStateMachine) {
validateAccountStatus(LoanEvent.LOAN_APPROVED); validateAccountStatus(LoanEvent.LOAN_APPROVED);
@@ -1899,11 +1900,14 @@ public class Loan extends AbstractPersistable<Long> {
approvedOn = command.localDateValueOfParameterNamed("eventDate"); approvedOn = command.localDateValueOfParameterNamed("eventDate");
approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); approvedOnDateChange = command.stringValueOfParameterNamed("eventDate");
} }
LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName); BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
if (approvedLoanAmount != null) { if (approvedLoanAmount != null) {
// Approved amount has to be less than or equal to principal // Approved amount has to be less than or equal to principal
// amount demanded // amount demanded
@@ -1921,14 +1925,36 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount); actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount); actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
} else if(approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
} else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
final String errorMessage = "Loan approved amount can't be greater than loan amount demanded."; final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
errorMessage, this.proposedPrincipal, approvedLoanAmount); errorMessage, this.proposedPrincipal, approvedLoanAmount);
} }
}
/* Update disbursement details */
if (disbursementDataArray != null) {
updateDisbursementDetails(command, actualChanges);
}
}
if (loanProduct.isMultiDisburseLoan()) {
if (this.disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(
LoanApiConstants.disbursementDataParameterName,
errorMessage);
}
if (this.disbursementDetails.size() > loanProduct
.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than "
+ loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(
LoanApiConstants.disbursementDataParameterName,
errorMessage, loanProduct.maxTrancheCount(),
disbursementDetails.size());
}
}
this.approvedOnDate = approvedOn.toDate(); this.approvedOnDate = approvedOn.toDate();
this.approvedBy = currentUser; this.approvedBy = currentUser;
actualChanges.put("locale", command.locale()); actualChanges.put("locale", command.locale());
@@ -1942,6 +1968,18 @@ public class Loan extends AbstractPersistable<Long> {
throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage,
getApprovedOnDate(), submittalDate); getApprovedOnDate(), submittalDate);
} }
if(expecteddisbursementDate != null){
this.expectedDisbursementDate = expecteddisbursementDate.toDate();
actualChanges.put("expectedDisbursementDate", expectedDisbursementDate);
if(expecteddisbursementDate.isBefore(approvedOn)) {
final String errorMessage = "The expected disbursement date should be either on or after the approval date: "
+ approvedOn.toString();
throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage,
getApprovedOnDate(), expecteddisbursementDate);
}
}
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_APPROVED, approvedOn); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_APPROVED, approvedOn);
@@ -2442,7 +2480,6 @@ public class Loan extends AbstractPersistable<Long> {
if (this.loanProduct.isMultiDisburseLoan()) { if (this.loanProduct.isMultiDisburseLoan()) {
for (final LoanDisbursementDetails details : this.disbursementDetails) { for (final LoanDisbursementDetails details : this.disbursementDetails) {
details.updateActualDisbursementDate(null); details.updateActualDisbursementDate(null);
details.resetPrincipal();
} }
} }
boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
@@ -4074,6 +4111,9 @@ public class Loan extends AbstractPersistable<Long> {
return AccountType.fromInt(this.loanType).isGroupAccount(); return AccountType.fromInt(this.loanType).isGroupAccount();
} }
public boolean isJLGLoan(){
return AccountType.fromInt(this.loanType).isJLGAccount();
}
public void updateInterestRateFrequencyType() { public void updateInterestRateFrequencyType() {
this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType()); this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
} }
@@ -4828,7 +4868,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate, disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
loanDisbursementDetails.principal(), loanDisbursementDetails.approvedPrincipal())); loanDisbursementDetails.principal()));
} }
return disbursementData; return disbursementData;
@@ -39,9 +39,6 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
@Column(name = "principal", scale = 6, precision = 19, nullable = false) @Column(name = "principal", scale = 6, precision = 19, nullable = false)
private BigDecimal principal; private BigDecimal principal;
@Column(name = "approved_principal", scale = 6, precision = 19, nullable = false)
private BigDecimal approvedPrincipal;
protected LoanDisbursementDetails() { protected LoanDisbursementDetails() {
} }
@@ -50,8 +47,7 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
this.expectedDisbursementDate = expectedDisbursementDate; this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate; this.actualDisbursementDate = actualDisbursementDate;
this.principal = principal; this.principal = principal;
this.approvedPrincipal = principal; }
}
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
this.loan = loan; this.loan = loan;
@@ -61,14 +57,13 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj; final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj;
if (loanDisbursementDetails.principal.equals(this.principal) if (loanDisbursementDetails.principal.equals(this.principal)
&& loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate) && loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate))
&& loanDisbursementDetails.approvedPrincipal.equals(this.approvedPrincipal)) { return true; } { return true; }
return false; return false;
} }
public void copy(final LoanDisbursementDetails disbursementDetails) { public void copy(final LoanDisbursementDetails disbursementDetails) {
this.principal = disbursementDetails.principal; this.principal = disbursementDetails.principal;
this.approvedPrincipal = disbursementDetails.approvedPrincipal;
this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate; this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate;
this.actualDisbursementDate = disbursementDetails.actualDisbursementDate; this.actualDisbursementDate = disbursementDetails.actualDisbursementDate;
} }
@@ -93,10 +88,6 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
return this.principal; return this.principal;
} }
public BigDecimal approvedPrincipal() {
return this.approvedPrincipal;
}
public void updatePrincipal(BigDecimal principal) { public void updatePrincipal(BigDecimal principal) {
this.principal = principal; this.principal = principal;
} }
@@ -115,17 +106,13 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
if (this.actualDisbursementDate != null) { if (this.actualDisbursementDate != null) {
actualDisburseDate = new LocalDate(this.actualDisbursementDate); actualDisburseDate = new LocalDate(this.actualDisbursementDate);
} }
return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, this.approvedPrincipal); return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal);
} }
public void updateActualDisbursementDate(Date actualDisbursementDate) { public void updateActualDisbursementDate(Date actualDisbursementDate) {
this.actualDisbursementDate = actualDisbursementDate; this.actualDisbursementDate = actualDisbursementDate;
} }
public BigDecimal resetPrincipal() {
return this.principal = this.approvedPrincipal;
}
public void updateExpectedDisbursementDate(Date expectedDisbursementDate) { public void updateExpectedDisbursementDate(Date expectedDisbursementDate) {
this.expectedDisbursementDate = expectedDisbursementDate; this.expectedDisbursementDate = expectedDisbursementDate;
} }
@@ -462,11 +462,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
instalmentNumber++; instalmentNumber++;
} }
if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse) && diffAmt == null) {
final String errorMsg = "One of the Disbursement date is not falling on Loan Schedule";
throw new MultiDisbursementDisbursementDateException(errorMsg);
}
// 7. determine fees and penalties // 7. determine fees and penalties
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) {
@@ -285,7 +285,7 @@ public class LoanScheduleAssembler {
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
} }
disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null)); disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal));
i++; i++;
} while (i < disbursementDataArray.size()); } while (i < disbursementDataArray.size());
} }
@@ -303,7 +303,7 @@ public class LoanScheduleAssembler {
} }
} }
private void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) { public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) {
// disbursement date should fall on a meeting date // disbursement date should fall on a meeting date
if (!calendar.isValidRecurringDate(expectedDisbursementDate)) { if (!calendar.isValidRecurringDate(expectedDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date"; final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
@@ -727,7 +727,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance) baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
.ignoreIfNull().positiveAmount(); .ignoreIfNull().positiveAmount();
} }
validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
@@ -832,64 +831,115 @@ public final class LoanApplicationCommandFromApiJsonHelper {
"Validation errors exist.", dataValidationErrors); } "Validation errors exist.", dataValidationErrors); }
} }
private void validateLoanMultiDisbursementdate(final JsonElement element, final DataValidatorBuilder baseDataValidator, public void validateLoanMultiDisbursementdate(final JsonElement element,
LocalDate expectedDisbursement, BigDecimal totalPrincipal) { final DataValidatorBuilder baseDataValidator,
final JsonObject topLevelJsonElement = element.getAsJsonObject(); LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null final Locale locale = this.fromApiJsonHelper
&& totalPrincipal != null) { .extractLocaleParameter(topLevelJsonElement);
BigDecimal tatalDisbursement = BigDecimal.ZERO; final String dateFormat = this.fromApiJsonHelper
boolean isFirstinstallmentOnExpectedDisbursementDate = false; .extractDateFormatParameter(topLevelJsonElement);
final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, if (this.fromApiJsonHelper.parameterExists(
element); LoanApiConstants.disbursementDataParameterName, element)
List<LocalDate> expectedDisbursementDates = new ArrayList<>(); && expectedDisbursement != null && totalPrincipal != null) {
if (variationArray != null && variationArray.size() > 0) { BigDecimal tatalDisbursement = BigDecimal.ZERO;
int i = 0; boolean isFirstinstallmentOnExpectedDisbursementDate = false;
do { final JsonArray variationArray = this.fromApiJsonHelper
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject(); .extractJsonArrayNamed(
LoanApiConstants.disbursementDataParameterName,
element);
List<LocalDate> expectedDisbursementDates = new ArrayList<>();
if (variationArray != null && variationArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i)
.getAsJsonObject();
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(
LoanApiConstants.disbursementDateParameterName,
jsonObject, dateFormat, locale);
if (expectedDisbursementDates
.contains(expectedDisbursementDate)) {
baseDataValidator
.reset()
.parameter(
LoanApiConstants.disbursementDateParameterName)
.failWithCode(
LoanApiConstants.DISBURSEMENT_DATE_UNIQUE_ERROR);
}
expectedDisbursementDates.add(expectedDisbursementDate);
BigDecimal principal = this.fromApiJsonHelper
.extractBigDecimalNamed(
LoanApiConstants.disbursementPrincipalParameterName,
jsonObject, locale);
baseDataValidator
.reset()
.parameter(
LoanApiConstants.disbursementDataParameterName)
.parameterAtIndexArray(
LoanApiConstants.disbursementPrincipalParameterName,
i).value(principal).notBlank();
if (principal != null) {
tatalDisbursement = tatalDisbursement.add(principal);
}
BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed( baseDataValidator
LoanApiConstants.disbursementPrincipalParameterName, jsonObject, locale); .reset()
; .parameter(
LoanApiConstants.disbursementDataParameterName)
.parameterAtIndexArray(
LoanApiConstants.disbursementDateParameterName,
i).value(expectedDisbursementDate)
.notNull();
LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( if (expectedDisbursement.equals(expectedDisbursementDate)) {
LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale); isFirstinstallmentOnExpectedDisbursementDate = true;
if (expectedDisbursementDates.contains(expectedDisbursementDate)) { }
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) i++;
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_UNIQUE_ERROR); } while (i < variationArray.size());
} if (!isFirstinstallmentOnExpectedDisbursementDate) {
expectedDisbursementDates.add(expectedDisbursementDate); baseDataValidator
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) .reset()
.parameterAtIndexArray(LoanApiConstants.disbursementPrincipalParameterName, i).value(principal).notBlank(); .parameter(
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) LoanApiConstants.disbursementDateParameterName)
.parameterAtIndexArray(LoanApiConstants.disbursementDateParameterName, i).value(expectedDisbursementDate) .failWithCode(
.notNull(); LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR);
if (principal != null) { }
tatalDisbursement = tatalDisbursement.add(principal); /*if (tatalDisbursement.compareTo(totalPrincipal) != 0 && !(this.fromApiJsonHelper.parameterExists(
} LoanApiConstants.approvedLoanAmountParameterName, element))) {
if (expectedDisbursement.equals(expectedDisbursementDate)) { baseDataValidator
isFirstinstallmentOnExpectedDisbursementDate = true; .reset()
} .parameter(
i++; LoanApiConstants.disbursementPrincipalParameterName)
} while (i < variationArray.size()); .failWithCode(
if (!isFirstinstallmentOnExpectedDisbursementDate) { LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) }*/
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR); if (tatalDisbursement.compareTo(totalPrincipal) == 1) {
} baseDataValidator
if (tatalDisbursement.compareTo(totalPrincipal) != 0) { .reset()
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName) .parameter(
.failWithCode(LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME); LoanApiConstants.disbursementPrincipalParameterName)
} .failWithCode(
final String interestTypeParameterName = "interestType"; LoanApiConstants.APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES);
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element); }
baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).ignoreIfNull() final String interestTypeParameterName = "interestType";
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue()); final Integer interestType = this.fromApiJsonHelper
} .extractIntegerSansLocaleNamed(
interestTypeParameterName, element);
baseDataValidator
.reset()
.parameter(interestTypeParameterName)
.value(interestType)
.ignoreIfNull()
.integerSameAsNumber(
InterestMethod.DECLINING_BALANCE.getValue());
} }
} }
}
public void validateRecalcuationFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate, public void validateRecalcuationFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate,
final List<ApiParameterError> dataValidationErrors) { final List<ApiParameterError> dataValidationErrors) {
@@ -49,7 +49,7 @@ public final class LoanApplicationTransitionApiJsonValidator {
final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.approvedLoanAmountParameterName, final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.approvedLoanAmountParameterName,
LoanApiConstants.approvedOnDateParameterName, LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.approvedOnDateParameterName, LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName,
LoanApiConstants.dateFormatParameterName)); LoanApiConstants.dateFormatParameterName,LoanApiConstants.disbursementDataParameterName,LoanApiConstants.disbursementDateParameterName));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
@@ -68,10 +68,14 @@ public final class LoanApplicationTransitionApiJsonValidator {
.extractLocalDateNamed(LoanApiConstants.approvedOnDateParameterName, element); .extractLocalDateNamed(LoanApiConstants.approvedOnDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.approvedOnDateParameterName).value(approvedOnDate).notNull(); baseDataValidator.reset().parameter(LoanApiConstants.approvedOnDateParameterName).value(approvedOnDate).notNull();
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(expectedDisbursementDate).ignoreIfNull();
final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element); final String note = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000); baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateRejection(final String json) { public void validateRejection(final String json) {
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@@ -73,8 +74,10 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateExce
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified; import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
@@ -99,6 +102,7 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@Service @Service
@@ -134,6 +138,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final LoanScheduleAssembler loanScheduleAssembler;
@Autowired @Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -151,7 +157,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanReadPlatformService loanReadPlatformService, final LoanAccountDomainService loanAccountDomainService, final LoanReadPlatformService loanReadPlatformService, final LoanAccountDomainService loanAccountDomainService,
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService) { final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService,
final LoanScheduleAssembler loanScheduleAssembler) {
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -180,6 +187,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.accountNumberFormatRepository = accountNumberFormatRepository; this.accountNumberFormatRepository = accountNumberFormatRepository;
this.businessEventNotifierService = businessEventNotifierService; this.businessEventNotifierService = businessEventNotifierService;
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
this.loanScheduleAssembler = loanScheduleAssembler;
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -732,29 +740,66 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
.build(); .build();
} }
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){
final String json = command.json();
final JsonElement element = this.fromJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if(disbursementDataArray == null || disbursementDataArray.size() == 0){
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
final BigDecimal principal = this.fromJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
fromApiJsonDeserializer.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) { public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) {
final AppUser currentUser = getAppUserIfPresent(); final AppUser currentUser = getAppUserIfPresent();
LocalDate expectedDisbursementDate = null;
this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json()); this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json());
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
checkClientOrGroupActive(loan);
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, defaultLoanLifecycleStateMachine()); expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
if(expectedDisbursementDate == null){
expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
}
if(loan.loanProduct().isMultiDisburseLoan()){
this.validateMultiDisbursementData(command, expectedDisbursementDate);
}
checkClientOrGroupActive(loan);
// validate expected disbursement date against meeting date
if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final Calendar calendar = calendarInstance.getCalendar();
this.loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
}
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray, defaultLoanLifecycleStateMachine());
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
// If loan approved amount less than loan demanded amount, then need // If loan approved amount less than loan demanded amount, then need
// to recompute the schedule // to recompute the schedule
if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) { if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) {
ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
} }
this.loanRepository.save(loan); this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText); final Note note = Note.loanNote(loan, noteText);
@@ -298,6 +298,7 @@ public class LoanAssembler {
do { do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject(); final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
Date expectedDisbursementDate = null; Date expectedDisbursementDate = null;
Date actualDisbursementDate = null;
BigDecimal principal = null; BigDecimal principal = null;
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) { if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
@@ -312,7 +313,8 @@ public class LoanAssembler {
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
} }
disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, null, principal));
disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal));
i++; i++;
} while (i < disbursementDataArray.size()); } while (i < disbursementDataArray.size());
} }
@@ -907,7 +907,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate(); this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.outstandingLoanPrincipalBalance = this.disbursement.amount();
this.disbursementData = disbursementData; this.disbursementData = disbursementData;
this.excludePastUndisbursed = isInterestRecalculationEnabled; this.excludePastUndisbursed = isInterestRecalculationEnabled;
} }
@@ -1398,7 +1398,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private static final class LoanDisbursementDetailMapper implements RowMapper<DisbursementData> { private static final class LoanDisbursementDetailMapper implements RowMapper<DisbursementData> {
public String schema() { public String schema() {
return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate,dd.disbursedon_date as actualDisbursementdate,dd.principal as principal,dd.approved_principal as approvedPrincipal " return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate,dd.disbursedon_date as actualDisbursementdate,dd.principal as principal "
+ "from m_loan_disbursement_detail dd"; + "from m_loan_disbursement_detail dd";
} }
@@ -1408,10 +1408,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate"); final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate");
final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate"); final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate");
final BigDecimal principal = rs.getBigDecimal("principal"); final BigDecimal principal = rs.getBigDecimal("principal");
final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal");
final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal, final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal);
approvedPrincipal);
return disbursementData; return disbursementData;
} }
@@ -0,0 +1,2 @@
ALTER TABLE `m_loan_disbursement_detail`
DROP COLUMN approved_principal;