From f6f04d7ae310e2c65f5cd891368ffd846e16cb3f Mon Sep 17 00:00:00 2001 From: Chandrika Date: Fri, 30 Jan 2015 11:04:31 -0500 Subject: [PATCH] MIFOSX-1824 API changes and test case for loan approval in case of multi-disburse loans. --- api-docs/apiLive.htm | 8 +- .../LoanApplicationApprovalTest.java | 188 +++++++++++++++++- .../common/loans/LoanProductTestBuilder.java | 2 +- .../common/loans/LoanStatusChecker.java | 2 +- .../common/loans/LoanTransactionHelper.java | 29 ++- .../loanaccount/api/LoanApiConstants.java | 1 + .../loanaccount/data/DisbursementData.java | 7 +- .../data/LoanApplicationTimelineData.java | 2 +- .../RepaymentScheduleRelatedLoanData.java | 7 +- .../portfolio/loanaccount/domain/Loan.java | 56 +++++- .../domain/LoanDisbursementDetails.java | 21 +- .../domain/AbstractLoanScheduleGenerator.java | 5 - .../service/LoanScheduleAssembler.java | 4 +- ...anApplicationCommandFromApiJsonHelper.java | 160 ++++++++++----- ...ApplicationTransitionApiJsonValidator.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 57 +++++- .../loanaccount/service/LoanAssembler.java | 4 +- .../service/LoanReadPlatformServiceImpl.java | 8 +- .../V240__alter_loan_disbursement_details.sql | 2 + 19 files changed, 448 insertions(+), 123 deletions(-) create mode 100644 mifosng-provider/src/main/resources/sql/migrations/core_db/V240__alter_loan_disbursement_details.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 63d9e8b47..11e6ee558 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -8545,6 +8545,9 @@ Request Body: approvedLoanAmount + + expectedDisbursementDate +
@@ -8559,7 +8562,10 @@ Request Body: "locale": "en", "dateFormat": "dd MMMM yyyy", "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"}] } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/LoanApplicationApprovalTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/LoanApplicationApprovalTest.java index a2f78f53c..ac0fe9e7d 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/LoanApplicationApprovalTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/LoanApplicationApprovalTest.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.integrationtests; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,6 +33,7 @@ public class LoanApplicationApprovalTest { private ResponseSpecification responseSpec; private ResponseSpecification responseSpecForStatusCode403; + private ResponseSpecification responseSpecForStatusCode400; private RequestSpecification requestSpec; private LoanTransactionHelper loanTransactionHelper; @@ -42,6 +44,7 @@ public class LoanApplicationApprovalTest { this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build(); + this.responseSpecForStatusCode400 = new ResponseSpecBuilder().expectStatusCode(400).build(); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); } @@ -63,7 +66,9 @@ public class LoanApplicationApprovalTest { HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); - loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, approvalAmount, loanID); + final String expectedDisbursementDate = null; + List approveTranches = null; + loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate,expectedDisbursementDate, approvalAmount, loanID, approveTranches); LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); } @@ -97,6 +102,161 @@ public class LoanApplicationApprovalTest { } + private HashMap createTrancheDetail(final String date, final String amount) { + HashMap detail = new HashMap<>(); + detail.put("expectedDisbursementDate", date); + detail.put("principal", amount); + + return detail; + } + + @Test + public void loanApplicationApprovalAndValidationForMultiDisburseLoans() { + + List 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 createTranches) { + final String proposedAmount = "5000"; + final String approvalAmount = "2000"; + final String approveDate = "1 March 2014"; + final String expectedDisbursementDate = "1 March 2014"; + + List 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 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 approveTranche1 = new ArrayList<>(); + approveTranche1.add(createTrancheDetail("1 March 2014", "5000")); + approveTranche1.add(createTrancheDetail("23 March 2014", "5000")); + + List 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 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 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 approveTranche5 = new ArrayList<>(); + + 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-----------------------------------------------------------"); + this.loanTransactionHelper = new LoanTransactionHelper( + this.requestSpec, this.responseSpecForStatusCode400); + + /* Tranches with same expected disbursement date */ + List> 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) { final String loanApplication = new LoanApplicationTestBuilder().withPrincipal(proposedAmount).withLoanTermFrequency("5") .withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("1") @@ -104,4 +264,30 @@ public class LoanApplicationApprovalTest { .withSubmittedOnDate("02 April 2012").build(clientID.toString(), loanProductID.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplication); } + + private Integer applyForLoanApplicationWithTranches(final Integer clientID, + final Integer loanProductID, String principal, + List 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); + } + } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java index d383a841f..b1d095303 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java @@ -77,7 +77,7 @@ public class LoanProductTestBuilder { private Boolean multiDisburseLoan = false; private final String outstandingLoanBalance = "35000"; - private final String maxTrancheCount = "35000"; + private final String maxTrancheCount = "3"; private Boolean isInterestRecalculationEnabled = false; private String daysInYearType = "1"; diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanStatusChecker.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanStatusChecker.java index 0c7bc5ac7..f59fbd98b 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanStatusChecker.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanStatusChecker.java @@ -46,7 +46,7 @@ public class LoanStatusChecker { assertTrue(getStatus(loanStatusHashMap, "overpaid")); } - public static HashMap getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + public static HashMap getStatusOfLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer loanID) { final String url = "/mifosng-provider/api/v1/loans/" + loanID + "?" + Utils.TENANT_IDENTIFIER; return Utils.performServerGet(requestSpec, responseSpec, url, "status"); diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java index e52b0349e..26cd4fab5 100755 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import org.joda.time.LocalDate; import org.mifosplatform.integrationtests.common.Utils; @@ -107,15 +108,22 @@ public class LoanTransactionHelper { 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 tranches) { return performLoanTransaction(createLoanOperationURL(APPROVE_LOAN_COMMAND, loanID), - getApproveLoanAsJSON(approvalDate, approvalAmount)); + getApproveLoanAsJSON(approvalDate, expectedDisbursementDate, approvalAmount, tranches)); + } + + public List> approveLoanForTranches(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount, final Integer loanID, + List tranches, final String responseAttribute) { + return (List>) 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) { 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); } @@ -225,17 +233,24 @@ public class LoanTransactionHelper { } 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) { - final HashMap map = new HashMap<>(); + private String getApproveLoanAsJSON(final String approvalDate, final String expectedDisbursementDate, final String approvalAmount, + List tranches) { + final HashMap map = new HashMap<>(); map.put("locale", "en"); map.put("dateFormat", "dd MMMM yyyy"); if (approvalAmount != null) { map.put("approvedLoanAmount", approvalAmount); } 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"); return new Gson().toJson(map); } @@ -392,7 +407,7 @@ public class LoanTransactionHelper { private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, final String responseAttribute) { return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute); } - + public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date, final String transactionAmount, final String responseAttribute) { return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java index a6b17f7de..1f90ae70f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java @@ -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 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 APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal"; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java index 04872b543..7ed6a6bd8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java @@ -19,16 +19,13 @@ public class DisbursementData implements Comparable { private final LocalDate expectedDisbursementDate; private final LocalDate actualDisbursementDate; private final BigDecimal principal; - @SuppressWarnings("unused") - private final BigDecimal approvedPrincipal; public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate, - final BigDecimal principalDisbursed, final BigDecimal approvedPrincipal) { + final BigDecimal principalDisbursed) { this.id = id; this.expectedDisbursementDate = expectedDisbursementDate; this.actualDisbursementDate = actualDisbursementDate; this.principal = principalDisbursed; - this.approvedPrincipal = approvedPrincipal; } public LocalDate disbursementDate() { @@ -42,7 +39,7 @@ public class DisbursementData implements Comparable { public BigDecimal amount() { return this.principal; } - + public boolean isDisbursed() { return this.actualDisbursementDate != null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java index ab8209602..17f331f8d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java @@ -133,6 +133,6 @@ public class LoanApplicationTimelineData { public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal, final BigDecimal approvedPrincipal, final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) { return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal, - approvedPrincipal, inArrearsTolerance, totalFeeChargesAtDisbursement); + inArrearsTolerance, totalFeeChargesAtDisbursement); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java index 6c241eb5c..f70fc3c53 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java @@ -19,18 +19,16 @@ public class RepaymentScheduleRelatedLoanData { private final LocalDate actualDisbursementDate; private final CurrencyData currency; private final BigDecimal principal; - private final BigDecimal approvedPrincipal; private final BigDecimal inArrearsTolerance; private final BigDecimal totalFeeChargesAtDisbursement; 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) { this.expectedDisbursementDate = expectedDisbursementDate; this.actualDisbursementDate = actualDisbursementDate; this.currency = currency; this.principal = principal; - this.approvedPrincipal = approvedPrincipal; this.inArrearsTolerance = inArrearsTolerance; this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement; } @@ -64,7 +62,6 @@ public class RepaymentScheduleRelatedLoanData { } public DisbursementData disbursementData() { - return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, - this.approvedPrincipal); + return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 2d8dc1e19..a5873d6d1 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1588,7 +1588,8 @@ public class Loan extends AbstractPersistable { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { 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) { disbursementDetails.updateLoan(this); this.disbursementDetails.add(disbursementDetails); @@ -1611,7 +1612,6 @@ public class Loan extends AbstractPersistable { } } } - } public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) { @@ -1865,6 +1865,7 @@ public class Loan extends AbstractPersistable { } public Map loanApplicationApproval(final AppUser currentUser, final JsonCommand command, + final JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) { validateAccountStatus(LoanEvent.LOAN_APPROVED); @@ -1899,11 +1900,14 @@ public class Loan extends AbstractPersistable { approvedOn = command.localDateValueOfParameterNamed("eventDate"); approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); } + + LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate"); BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName); + if (approvedLoanAmount != null) { - + // Approved amount has to be less than or equal to principal // amount demanded @@ -1921,14 +1925,36 @@ public class Loan extends AbstractPersistable { actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, 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."; throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", 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.approvedBy = currentUser; actualChanges.put("locale", command.locale()); @@ -1942,6 +1968,18 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, 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); @@ -2442,7 +2480,6 @@ public class Loan extends AbstractPersistable { if (this.loanProduct.isMultiDisburseLoan()) { for (final LoanDisbursementDetails details : this.disbursementDetails) { details.updateActualDisbursementDate(null); - details.resetPrincipal(); } } boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; @@ -4074,6 +4111,9 @@ public class Loan extends AbstractPersistable { return AccountType.fromInt(this.loanType).isGroupAccount(); } + public boolean isJLGLoan(){ + return AccountType.fromInt(this.loanType).isJLGAccount(); + } public void updateInterestRateFrequencyType() { this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType()); } @@ -4828,7 +4868,7 @@ public class Loan extends AbstractPersistable { } disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate, - loanDisbursementDetails.principal(), loanDisbursementDetails.approvedPrincipal())); + loanDisbursementDetails.principal())); } return disbursementData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java index 697dea93b..bfdc39837 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java @@ -39,9 +39,6 @@ public class LoanDisbursementDetails extends AbstractPersistable { @Column(name = "principal", scale = 6, precision = 19, nullable = false) private BigDecimal principal; - @Column(name = "approved_principal", scale = 6, precision = 19, nullable = false) - private BigDecimal approvedPrincipal; - protected LoanDisbursementDetails() { } @@ -50,8 +47,7 @@ public class LoanDisbursementDetails extends AbstractPersistable { this.expectedDisbursementDate = expectedDisbursementDate; this.actualDisbursementDate = actualDisbursementDate; this.principal = principal; - this.approvedPrincipal = principal; - } + } public void updateLoan(final Loan loan) { this.loan = loan; @@ -61,14 +57,13 @@ public class LoanDisbursementDetails extends AbstractPersistable { public boolean equals(final Object obj) { final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj; if (loanDisbursementDetails.principal.equals(this.principal) - && loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate) - && loanDisbursementDetails.approvedPrincipal.equals(this.approvedPrincipal)) { return true; } + && loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate)) + { return true; } return false; } public void copy(final LoanDisbursementDetails disbursementDetails) { this.principal = disbursementDetails.principal; - this.approvedPrincipal = disbursementDetails.approvedPrincipal; this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate; this.actualDisbursementDate = disbursementDetails.actualDisbursementDate; } @@ -93,10 +88,6 @@ public class LoanDisbursementDetails extends AbstractPersistable { return this.principal; } - public BigDecimal approvedPrincipal() { - return this.approvedPrincipal; - } - public void updatePrincipal(BigDecimal principal) { this.principal = principal; } @@ -115,17 +106,13 @@ public class LoanDisbursementDetails extends AbstractPersistable { if (this.actualDisbursementDate != null) { 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) { this.actualDisbursementDate = actualDisbursementDate; } - public BigDecimal resetPrincipal() { - return this.principal = this.approvedPrincipal; - } - public void updateExpectedDisbursementDate(Date expectedDisbursementDate) { this.expectedDisbursementDate = expectedDisbursementDate; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 02673ec6b..9f3fa0d48 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -462,11 +462,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener 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 for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 1aa6bd6e9..d36b7f9ef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -285,7 +285,7 @@ public class LoanScheduleAssembler { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); } - disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null)); + disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal)); i++; } 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 if (!calendar.isValidRecurringDate(expectedDisbursementDate)) { final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 72fae1c21..183868f0d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -727,7 +727,6 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance) .ignoreIfNull().positiveAmount(); } - validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", @@ -832,64 +831,115 @@ public final class LoanApplicationCommandFromApiJsonHelper { "Validation errors exist.", dataValidationErrors); } } - private void validateLoanMultiDisbursementdate(final JsonElement element, final DataValidatorBuilder baseDataValidator, - LocalDate expectedDisbursement, BigDecimal totalPrincipal) { - final JsonObject topLevelJsonElement = element.getAsJsonObject(); - final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); - final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); - if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null - && totalPrincipal != null) { - BigDecimal tatalDisbursement = BigDecimal.ZERO; - boolean isFirstinstallmentOnExpectedDisbursementDate = false; - final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, - element); - List expectedDisbursementDates = new ArrayList<>(); - if (variationArray != null && variationArray.size() > 0) { - int i = 0; - do { - final JsonObject jsonObject = variationArray.get(i).getAsJsonObject(); + public void validateLoanMultiDisbursementdate(final JsonElement element, + final DataValidatorBuilder baseDataValidator, + LocalDate expectedDisbursement, BigDecimal totalPrincipal) { + + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper + .extractLocaleParameter(topLevelJsonElement); + final String dateFormat = this.fromApiJsonHelper + .extractDateFormatParameter(topLevelJsonElement); + if (this.fromApiJsonHelper.parameterExists( + LoanApiConstants.disbursementDataParameterName, element) + && expectedDisbursement != null && totalPrincipal != null) { + BigDecimal tatalDisbursement = BigDecimal.ZERO; + boolean isFirstinstallmentOnExpectedDisbursementDate = false; + final JsonArray variationArray = this.fromApiJsonHelper + .extractJsonArrayNamed( + LoanApiConstants.disbursementDataParameterName, + element); + List 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( - LoanApiConstants.disbursementPrincipalParameterName, jsonObject, locale); - ; + baseDataValidator + .reset() + .parameter( + LoanApiConstants.disbursementDataParameterName) + .parameterAtIndexArray( + LoanApiConstants.disbursementDateParameterName, + i).value(expectedDisbursementDate) + .notNull(); - 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); - baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) - .parameterAtIndexArray(LoanApiConstants.disbursementPrincipalParameterName, i).value(principal).notBlank(); - baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) - .parameterAtIndexArray(LoanApiConstants.disbursementDateParameterName, i).value(expectedDisbursementDate) - .notNull(); - if (principal != null) { - tatalDisbursement = tatalDisbursement.add(principal); - } - if (expectedDisbursement.equals(expectedDisbursementDate)) { - isFirstinstallmentOnExpectedDisbursementDate = true; - } - i++; - } while (i < variationArray.size()); - if (!isFirstinstallmentOnExpectedDisbursementDate) { - baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) - .failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR); - } - if (tatalDisbursement.compareTo(totalPrincipal) != 0) { - baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName) - .failWithCode(LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME); - } - final String interestTypeParameterName = "interestType"; - final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element); - baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).ignoreIfNull() - .integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue()); - } + if (expectedDisbursement.equals(expectedDisbursementDate)) { + isFirstinstallmentOnExpectedDisbursementDate = true; + } + i++; + } while (i < variationArray.size()); + if (!isFirstinstallmentOnExpectedDisbursementDate) { + baseDataValidator + .reset() + .parameter( + LoanApiConstants.disbursementDateParameterName) + .failWithCode( + LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR); + } + /*if (tatalDisbursement.compareTo(totalPrincipal) != 0 && !(this.fromApiJsonHelper.parameterExists( + LoanApiConstants.approvedLoanAmountParameterName, element))) { + baseDataValidator + .reset() + .parameter( + LoanApiConstants.disbursementPrincipalParameterName) + .failWithCode( + LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME); + }*/ + if (tatalDisbursement.compareTo(totalPrincipal) == 1) { + baseDataValidator + .reset() + .parameter( + LoanApiConstants.disbursementPrincipalParameterName) + .failWithCode( + LoanApiConstants.APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES); + } + final String interestTypeParameterName = "interestType"; + 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, final List dataValidationErrors) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java index e293d57f5..b4eb9c70a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationTransitionApiJsonValidator.java @@ -49,7 +49,7 @@ public final class LoanApplicationTransitionApiJsonValidator { final Set disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.approvedLoanAmountParameterName, LoanApiConstants.approvedOnDateParameterName, LoanApiConstants.noteParameterName, LoanApiConstants.localeParameterName, - LoanApiConstants.dateFormatParameterName)); + LoanApiConstants.dateFormatParameterName,LoanApiConstants.disbursementDataParameterName,LoanApiConstants.disbursementDateParameterName)); final Type typeOfMap = new TypeToken>() {}.getType(); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); @@ -68,10 +68,14 @@ public final class LoanApplicationTransitionApiJsonValidator { .extractLocalDateNamed(LoanApiConstants.approvedOnDateParameterName, element); 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); baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000); - throwExceptionIfValidationWarningsExist(dataValidationErrors); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } public void validateRejection(final String json) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 277572497..9707ff12a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.loanaccount.service; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; 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.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified; 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.LoanScheduleModel; +import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator; @@ -99,6 +102,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; @Service @@ -134,6 +138,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final BusinessEventNotifierService businessEventNotifierService; private final ConfigurationDomainService configurationDomainService; + private final LoanScheduleAssembler loanScheduleAssembler; + @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, @@ -151,7 +157,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanReadPlatformService loanReadPlatformService, final LoanAccountDomainService loanAccountDomainService, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, - final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService) { + final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService, + final LoanScheduleAssembler loanScheduleAssembler) { this.context = context; this.fromJsonHelper = fromJsonHelper; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; @@ -180,6 +187,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.accountNumberFormatRepository = accountNumberFormatRepository; this.businessEventNotifierService = businessEventNotifierService; this.configurationDomainService = configurationDomainService; + this.loanScheduleAssembler = loanScheduleAssembler; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -732,29 +740,66 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa .build(); } + private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){ + final String json = command.json(); + final JsonElement element = this.fromJsonHelper.parse(json); + + final List 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 @Override public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) { final AppUser currentUser = getAppUserIfPresent(); + LocalDate expectedDisbursementDate = null; this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json()); - + final Loan loan = retrieveLoanBy(loanId); - checkClientOrGroupActive(loan); + + final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); - final Map 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 changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray, defaultLoanLifecycleStateMachine()); + + if (!changes.isEmpty()) { // If loan approved amount less than loan demanded amount, then need // to recompute the schedule if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) { ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan); - loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } this.loanRepository.save(loan); - + final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { final Note note = Note.loanNote(loan, noteText); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index f0e8a1c70..72e7cc966 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -298,6 +298,7 @@ public class LoanAssembler { do { final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject(); Date expectedDisbursementDate = null; + Date actualDisbursementDate = null; BigDecimal principal = null; if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) { @@ -312,7 +313,8 @@ public class LoanAssembler { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); } - disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, null, principal)); + + disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal)); i++; } while (i < disbursementDataArray.size()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index a263d01b2..5487829c4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -907,7 +907,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); this.lastDueDate = this.disbursement.disbursementDate(); - this.outstandingLoanPrincipalBalance = this.disbursement.amount(); + this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.disbursementData = disbursementData; this.excludePastUndisbursed = isInterestRecalculationEnabled; } @@ -1398,7 +1398,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private static final class LoanDisbursementDetailMapper implements RowMapper { 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"; } @@ -1408,10 +1408,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate"); final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate"); final BigDecimal principal = rs.getBigDecimal("principal"); - final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal"); - final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal, - approvedPrincipal); + final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal); return disbursementData; } diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V240__alter_loan_disbursement_details.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V240__alter_loan_disbursement_details.sql new file mode 100644 index 000000000..a0cdc4026 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V240__alter_loan_disbursement_details.sql @@ -0,0 +1,2 @@ +ALTER TABLE `m_loan_disbursement_detail` + DROP COLUMN approved_principal; \ No newline at end of file