Merge branch 'chandrikamohith-TrancheDisbursal' into develop

This commit is contained in:
Vishwas Babu A J
2015-03-03 08:43:26 +05:30
29 changed files with 736 additions and 171 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);
} }
@@ -488,7 +488,11 @@ public class CommandWrapper {
} }
public boolean isUpdateDisbursementDate() { public boolean isUpdateDisbursementDate() {
return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL"); return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL") && this.entityId != null;
}
public boolean addAndDeleteDisbursementDetails() {
return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL") && this.entityId == null;
} }
public boolean isStandingInstruction() { public boolean isStandingInstruction() {
@@ -740,6 +740,15 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder addAndDeleteDisbursementDetails(final Long loanId) {
this.actionName = "UPDATE";
this.entityName = "DISBURSEMENTDETAIL";
this.entityId = null;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/editdisbursementdetails/";
return this;
}
public CommandWrapperBuilder deleteLoanApplication(final Long loanId) { public CommandWrapperBuilder deleteLoanApplication(final Long loanId) {
this.actionName = "DELETE"; this.actionName = "DELETE";
this.entityName = "LOAN"; this.entityName = "LOAN";
@@ -445,6 +445,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
if (wrapper.isUpdateDisbursementDate()) { if (wrapper.isUpdateDisbursementDate()) {
handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class);
} }
if(wrapper.addAndDeleteDisbursementDetails()){
handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
}
} else if (wrapper.isGLAccountResource()) { } else if (wrapper.isGLAccountResource()) {
if (wrapper.isCreate()) { if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createGLAccountCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("createGLAccountCommandHandler", NewCommandSourceHandler.class);
@@ -12,6 +12,8 @@ public interface LoanApiConstants {
public static final String disbursementDataParameterName = "disbursementData"; public static final String disbursementDataParameterName = "disbursementData";
public static final String disbursementDateParameterName = "expectedDisbursementDate"; public static final String disbursementDateParameterName = "expectedDisbursementDate";
public static final String disbursementPrincipalParameterName = "principal"; public static final String disbursementPrincipalParameterName = "principal";
public static final String updatedDisbursementDateParameterName = "updatedExpectedDisbursementDate";
public static final String updatedDisbursementPrincipalParameterName = "updatedPrincipal";
public static final String disbursementIdParameterName = "id"; public static final String disbursementIdParameterName = "id";
public static final String principalDisbursedParameterName = "transactionAmount"; public static final String principalDisbursedParameterName = "transactionAmount";
@@ -37,5 +39,9 @@ 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";
public static final String DISBURSEMENT_DATES_NOT_IN_ORDER = "disbursements.should.be.ordered.based.on.their.disbursement.dates";
public static final String DISBURSEMENT_DATE_BEFORE_ERROR = "disbursement.date.of.tranche.cannot.be.before.expected.disbursement.date";
} }
@@ -11,10 +11,12 @@ import java.util.Set;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
@@ -67,8 +69,22 @@ public class LoanDisbursementDetailApiResource {
public String updateDisbursementDate(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId, public String updateDisbursementDate(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId,
final String apiRequestBodyAsJson) { final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId, disbursementId) final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId, disbursementId)
.withJson(apiRequestBodyAsJson).build(); .withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@PUT
@Path("editDisbursements")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String addAndDeleteDisbursementDetail(@PathParam("loanId") final Long loanId, final String apiRequestBodyAsJson) {
CommandWrapper commandRequest = new CommandWrapperBuilder().addAndDeleteDisbursementDetails(loanId)
.withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -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() {
@@ -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);
} }
} }
@@ -1392,11 +1392,13 @@ public class Loan extends AbstractPersistable<Long> {
this.maxOutstandingLoanBalance = command this.maxOutstandingLoanBalance = command
.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName); .bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName);
} }
if (this.disbursementDetails.isEmpty()) { 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"; final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
} }
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) { if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount(); final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size()); loanProduct.maxTrancheCount(), disbursementDetails.size());
@@ -1531,7 +1533,7 @@ public class Loan extends AbstractPersistable<Long> {
return amount; return amount;
} }
private void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) { public void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) {
List<Long> list = fetchDisbursementIds(); List<Long> list = fetchDisbursementIds();
@@ -1588,22 +1590,27 @@ 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); if(id != null){
if (id == null) {
disbursementDetails.updateLoan(this);
this.disbursementDetails.add(disbursementDetails);
actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
actualChanges.put("recalculateLoanSchedule", true);
} else {
list.remove(id);
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id); LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id);
if (!loanDisbursementDetail.equals(disbursementDetails)) { list.remove(id);
loanDisbursementDetail.copy(disbursementDetails); if(loanDisbursementDetail.actualDisbursementDate() == null){
actualChanges.put("disbursementDetailId", id); Date actualDisbursementDate = null;
actualChanges.put("recalculateLoanSchedule", true); LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal);
if (!loanDisbursementDetail.equals(disbursementDetails)) {
loanDisbursementDetail.copy(disbursementDetails);
actualChanges.put("disbursementDetailId", id);
actualChanges.put("recalculateLoanSchedule", true);
}
} }
} }
i++; else{
Date actualDisbursementDate = null;
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal);
disbursementDetails.updateLoan(this);
this.disbursementDetails.add(disbursementDetails);
actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
actualChanges.put("recalculateLoanSchedule", true);
}i++;
} while (i < disbursementDataArray.size()); } while (i < disbursementDataArray.size());
for (Long id : list) { for (Long id : list) {
this.disbursementDetails.remove(fetchLoanDisbursementsById(id)); this.disbursementDetails.remove(fetchLoanDisbursementsById(id));
@@ -1611,7 +1618,6 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
} }
} }
public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) { public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) {
@@ -1865,6 +1871,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);
@@ -1900,8 +1907,11 @@ public class Loan extends AbstractPersistable<Long> {
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
@@ -1921,14 +1931,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());
@@ -1943,6 +1975,18 @@ public class Loan extends AbstractPersistable<Long> {
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);
if (approvedOn.isAfter(DateUtils.getLocalDateOfTenant())) { if (approvedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
@@ -2135,12 +2179,25 @@ public class Loan extends AbstractPersistable<Long> {
} else { } else {
for (LoanDisbursementDetails disbursementDetails : details) { for (LoanDisbursementDetails disbursementDetails : details) {
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate()); disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
diff = diff.add(disbursementDetails.principal().subtract(principalDisbursed));
disbursementDetails.updatePrincipal(principalDisbursed); disbursementDetails.updatePrincipal(principalDisbursed);
} }
} }
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount()); if(this.loanProduct().isMultiDisburseLoan()){
if (diff.compareTo(BigDecimal.ZERO) == -1) { Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
}
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
if(setPrincipalAmount.compareTo(this.approvedPrincipal) == 1){
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", principalDisbursed,
this.approvedPrincipal);
}
}else{
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
}
if (!(this.loanProduct().isMultiDisburseLoan()) && diff.compareTo(BigDecimal.ZERO) == -1) {
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount "; final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed, throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
this.loanRepaymentScheduleDetail.getPrincipal().getAmount()); this.loanRepaymentScheduleDetail.getPrincipal().getAmount());
@@ -2442,7 +2499,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;
@@ -3488,7 +3544,7 @@ public class Loan extends AbstractPersistable<Long> {
boolean isRegenerationRequired = false; boolean isRegenerationRequired = false;
if (this.loanProduct.isMultiDisburseLoan()) { if (this.loanProduct.isMultiDisburseLoan()) {
LoanDisbursementDetails details = fetchLastDisburseDetail(); LoanDisbursementDetails details = fetchLastDisburseDetail();
if (!details.expectedDisbursementDate().equals(details.actualDisbursementDate())) { if (details != null && !(details.expectedDisbursementDate().equals(details.actualDisbursementDate()))) {
isRegenerationRequired = true; isRegenerationRequired = true;
} }
} }
@@ -4074,6 +4130,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());
} }
@@ -4414,19 +4473,30 @@ public class Loan extends AbstractPersistable<Long> {
return this.disbursementDetails; return this.disbursementDetails;
} }
public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails, public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails,
final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
final Locale locale = command.extractLocale();
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, locale);
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName);
disbursementDetails.updateExpectedDisbursementDateAndAmount(expectedDisbursementDate.toDate(), principal);
actualChanges.put(LoanApiConstants.disbursementDateParameterName, actualChanges.put(LoanApiConstants.disbursementDateParameterName,
command.stringValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName)); command.stringValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName));
actualChanges.put(LoanApiConstants.disbursementIdParameterName, actualChanges.put(LoanApiConstants.disbursementIdParameterName,
command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName)); command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName));
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName,
command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale));
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
for (LoanDisbursementDetails details : loanDisburseDetails) {
setPrincipalAmount = setPrincipalAmount.add(details.principal());
}
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
} else { } else {
@@ -4560,7 +4630,7 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail; return changedTransactionDetail;
} }
private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) {
LocalDate lastTransactionDate = getLastUserTransactionDate(); LocalDate lastTransactionDate = getLastUserTransactionDate();
generatorDTO.setLastTransactionDate(lastTransactionDate); generatorDTO.setLastTransactionDate(lastTransactionDate);
@@ -4828,7 +4898,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,19 +106,16 @@ 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() { public void updateExpectedDisbursementDateAndAmount(Date expectedDisbursementDate, BigDecimal principal) {
return this.principal = this.approvedPrincipal;
}
public void updateExpectedDisbursementDate(Date expectedDisbursementDate) {
this.expectedDisbursementDate = expectedDisbursementDate; this.expectedDisbursementDate = expectedDisbursementDate;
this.principal = principal;
} }
} }
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AddAndDeleteLoanDisburseDetailsCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public AddAndDeleteLoanDisburseDetailsCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.addAndDeleteLoanDisburseDetails(command.getLoanId(), command);
}
}
@@ -27,6 +27,6 @@ public class UpdateLoanDisbuseDateCommandHandler implements NewCommandSourceHand
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateDisbursementDateForTranche(command.getLoanId(), command.entityId(), command); return this.writePlatformService.updateDisbursementDateAndAmountForTranche(command.getLoanId(), command.entityId(), command);
} }
} }
@@ -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";
@@ -17,6 +17,7 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
@@ -727,7 +728,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 +832,154 @@ public final class LoanApplicationCommandFromApiJsonHelper {
"Validation errors exist.", dataValidationErrors); } "Validation errors exist.", dataValidationErrors); }
} }
private void validateLoanMultiDisbursementdate(final JsonElement element, final DataValidatorBuilder baseDataValidator, private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final DataValidatorBuilder baseDataValidator) {
LocalDate expectedDisbursement, BigDecimal totalPrincipal) { final JsonObject topLevelJsonElement = element.getAsJsonObject();
final JsonObject topLevelJsonElement = element.getAsJsonObject(); final Locale locale = this.fromApiJsonHelper
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); .extractLocaleParameter(topLevelJsonElement);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); final String dateFormat = this.fromApiJsonHelper
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null .extractDateFormatParameter(topLevelJsonElement);
&& totalPrincipal != null) { final JsonArray variationArray = this.fromApiJsonHelper
BigDecimal tatalDisbursement = BigDecimal.ZERO; .extractJsonArrayNamed(
boolean isFirstinstallmentOnExpectedDisbursementDate = false; LoanApiConstants.disbursementDataParameterName,
final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, element);
element); for(int i=0 ; i< variationArray.size();i++){
List<LocalDate> expectedDisbursementDates = new ArrayList<>(); final JsonObject jsonObject1 = variationArray.get(i)
if (variationArray != null && variationArray.size() > 0) { .getAsJsonObject();
int i = 0; LocalDate date1 = this.fromApiJsonHelper
do { .extractLocalDateNamed(
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject(); LoanApiConstants.disbursementDateParameterName,
jsonObject1, dateFormat, locale);
BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed( for(int j=i+1;j<variationArray.size();j++){
LoanApiConstants.disbursementPrincipalParameterName, jsonObject, locale); final JsonObject jsonObject2 = variationArray.get(j)
; .getAsJsonObject();
LocalDate date2 = this.fromApiJsonHelper
.extractLocalDateNamed(
LoanApiConstants.disbursementDateParameterName,
jsonObject2, dateFormat, locale);
if(date1.isAfter(date2)){
baseDataValidator
.reset()
.parameter(
LoanApiConstants.disbursementDataParameterName)
.failWithCode(
LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER);
}
}
}
}
LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( public void validateLoanMultiDisbursementdate(final JsonElement element,
LoanApiConstants.disbursementDateParameterName, jsonObject, dateFormat, locale); final DataValidatorBuilder baseDataValidator,
if (expectedDisbursementDates.contains(expectedDisbursementDate)) { LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
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());
}
} this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator);
} 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<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);
}
if(expectedDisbursementDate.isBefore(expectedDisbursement)){
baseDataValidator
.reset()
.parameter(
LoanApiConstants.disbursementDataParameterName)
.failWithCode(
LoanApiConstants.DISBURSEMENT_DATE_BEFORE_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);
}
baseDataValidator
.reset()
.parameter(
LoanApiConstants.disbursementDataParameterName)
.parameterAtIndexArray(
LoanApiConstants.disbursementDateParameterName,
i).value(expectedDisbursementDate)
.notNull();
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) == 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, 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) {
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -30,17 +31,23 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
@Component @Component
public final class LoanEventApiJsonValidator { public final class LoanEventApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
@Autowired @Autowired
public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper) { public LoanEventApiJsonValidator(final FromJsonHelper fromApiJsonHelper,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -363,12 +370,13 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateUpdateDisbursementDate(final String json, LoanDisbursementDetails loanDisbursementDetails) { public void validateUpdateDisbursementDateAndAmount(final String json, LoanDisbursementDetails loanDisbursementDetails) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.disbursementDateParameterName, final Set<String> disbursementParameters = new HashSet<>(Arrays.asList("locale", "dateFormat", LoanApiConstants.disbursementDataParameterName,
"locale", "dateFormat")); LoanApiConstants.approvedLoanAmountParameterName, LoanApiConstants.updatedDisbursementDateParameterName, LoanApiConstants.updatedDisbursementPrincipalParameterName,
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);
@@ -381,11 +389,21 @@ public final class LoanEventApiJsonValidator {
LoanApiConstants.disbursementDateParameterName, element); LoanApiConstants.disbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(actualDisbursementDate).notNull(); baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(actualDisbursementDate).notNull();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName,
element,locale);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName).value(principal).notNull();
final BigDecimal approvedPrincipal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.approvedLoanAmountParameterName,
element,locale);
if (loanDisbursementDetails.actualDisbursementDate() != null) { if (loanDisbursementDetails.actualDisbursementDate() != null) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
.failWithCode(LoanApiConstants.ALREADY_DISBURSED); .failWithCode(LoanApiConstants.ALREADY_DISBURSED);
} }
fromApiJsonDeserializer.validateLoanMultiDisbursementdate(element, baseDataValidator, actualDisbursementDate, approvedPrincipal);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -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,18 +740,55 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
.build(); .build();
} }
public 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);
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
if(expectedDisbursementDate == null){
expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
}
if(loan.loanProduct().isMultiDisburseLoan()){
this.validateMultiDisbursementData(command, expectedDisbursementDate);
}
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, defaultLoanLifecycleStateMachine()); // 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
@@ -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());
} }
@@ -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;
} }
@@ -78,7 +78,7 @@ public interface LoanWritePlatformService {
CommandProcessingResult undoWriteOff(Long loanId); CommandProcessingResult undoWriteOff(Long loanId);
CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command); CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command);
void recalculateInterest(); void recalculateInterest();
@@ -88,4 +88,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command); CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command);
CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command);
} }
@@ -123,11 +123,13 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException; import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.guarantor.service.GuarantorDomainService; import org.mifosplatform.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
@@ -159,6 +161,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@Service @Service
@@ -2541,17 +2544,59 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
@Override private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){
@Transactional final String json = command.json();
public CommandProcessingResult updateDisbursementDateForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) { final JsonElement element = this.fromApiJsonHelper.parse(json);
AppUser currentUser = getAppUserIfPresent(); 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.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@Override
@Transactional
public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); final Map<String, Object> actualChanges = new LinkedHashMap<>();
this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(), loanDisbursementDetails); LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), validateMultiDisbursementData(command, expectedDisbursementDate);
loan.updateDisbursementDetails(command, actualChanges);
if (loan.getDisbursementDetails().isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(
LoanApiConstants.disbursementDataParameterName,
errorMessage);
}
if (loan.getDisbursementDetails().size() > loan.loanProduct()
.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than "
+ loan.loanProduct().maxTrancheCount();
throw new ExceedingTrancheCountException(
LoanApiConstants.disbursementDataParameterName,
errorMessage, loan.loanProduct().maxTrancheCount(),
loan.getDisbursementDetails().size());
}
LoanDisbursementDetails updateDetails = null;
return processLoanDisbursementDetail(loan, loanId, command, updateDetails);
}
private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command,
LoanDisbursementDetails loanDisbursementDetails){
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
@@ -2580,12 +2625,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate); lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, ChangedTransactionDetail changedTransactionDetail = null;
existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); AppUser currentUser = getAppUserIfPresent();
if(command.entityId() != null){
changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command,
existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser);
}else{
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
BigDecimal setAmount = BigDecimal.ZERO;
for (LoanDisbursementDetails details : loanDisburseDetails) {
setAmount = setAmount.add(details.principal());
}
loan.repaymentScheduleDetail().setPrincipal(setAmount);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
} else {
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
}
}
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (command.entityId() != null && changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
@@ -2601,6 +2666,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withGroupId(loan.getGroupId()) // .withGroupId(loan.getGroupId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
}
@Override
@Transactional
public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
} }
@@ -0,0 +1,2 @@
ALTER TABLE `m_loan_disbursement_detail`
DROP COLUMN approved_principal;
@@ -0,0 +1,2 @@
ALTER TABLE `m_loan_disbursement_detail`
DROP COLUMN approved_principal;