sorting out concurrency issues with loans -1
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
|||||||
|
package org.mifosplatform.integrationtests;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mifosplatform.integrationtests.common.ClientHelper;
|
||||||
|
import org.mifosplatform.integrationtests.common.Utils;
|
||||||
|
import org.mifosplatform.integrationtests.common.accounting.Account;
|
||||||
|
import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||||
|
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
|
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
|
|
||||||
|
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||||
|
import com.jayway.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import com.jayway.restassured.http.ContentType;
|
||||||
|
import com.jayway.restassured.specification.RequestSpecification;
|
||||||
|
import com.jayway.restassured.specification.ResponseSpecification;
|
||||||
|
|
||||||
|
public class ConcurrencyIntegrationTest {
|
||||||
|
|
||||||
|
private ResponseSpecification responseSpec;
|
||||||
|
private RequestSpecification requestSpec;
|
||||||
|
private LoanTransactionHelper loanTransactionHelper;
|
||||||
|
|
||||||
|
private static final String NO_ACCOUNTING = "1";
|
||||||
|
|
||||||
|
final int MYTHREADS = 30;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyConcurrentLoanRepayments() {
|
||||||
|
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||||
|
final Integer loanProductID = createLoanProduct(false, NO_ACCOUNTING);
|
||||||
|
final Integer loanID = applyForLoanApplication(clientID, loanProductID, "12,000.00");
|
||||||
|
this.loanTransactionHelper.approveLoan("20 September 2011", loanID);
|
||||||
|
this.loanTransactionHelper.disburseLoan("20 September 2011", loanID, "12,000.00");
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(MYTHREADS);
|
||||||
|
Calendar date = Calendar.getInstance();
|
||||||
|
date.set(2011, 9, 20);
|
||||||
|
Float repaymentAmount = 100.0f;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
System.out.println("Starting concurrent transaction number " + i);
|
||||||
|
date.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
repaymentAmount = repaymentAmount + 100;
|
||||||
|
Runnable worker = new LoanRepaymentExecutor(loanTransactionHelper, loanID, repaymentAmount, date);
|
||||||
|
executor.execute(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
// Wait until all threads are finish
|
||||||
|
while (!executor.isTerminated()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
System.out.println("\nFinished all threads");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer createLoanProduct(final boolean multiDisburseLoan, final String accountingRule, final Account... accounts) {
|
||||||
|
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
final String loanProductJSON = new LoanProductTestBuilder() //
|
||||||
|
.withPrincipal("12,000.00") //
|
||||||
|
.withNumberOfRepayments("4") //
|
||||||
|
.withRepaymentAfterEvery("1") //
|
||||||
|
.withRepaymentTypeAsMonth() //
|
||||||
|
.withinterestRatePerPeriod("1") //
|
||||||
|
.withInterestRateFrequencyTypeAsMonths() //
|
||||||
|
.withAmortizationTypeAsEqualInstallments() //
|
||||||
|
.withInterestTypeAsDecliningBalance() //
|
||||||
|
.withTranches(multiDisburseLoan) //
|
||||||
|
.withAccounting(accountingRule, accounts).build(null);
|
||||||
|
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal) {
|
||||||
|
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||||
|
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
|
||||||
|
.withPrincipal(principal) //
|
||||||
|
.withLoanTermFrequency("4") //
|
||||||
|
.withLoanTermFrequencyAsMonths() //
|
||||||
|
.withNumberOfRepayments("4") //
|
||||||
|
.withRepaymentEveryAfter("1") //
|
||||||
|
.withRepaymentFrequencyTypeAsMonths() //
|
||||||
|
.withInterestRatePerPeriod("2") //
|
||||||
|
.withAmortizationTypeAsEqualInstallments() //
|
||||||
|
.withInterestTypeAsDecliningBalance() //
|
||||||
|
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
|
||||||
|
.withExpectedDisbursementDate("20 September 2011") //
|
||||||
|
.withSubmittedOnDate("20 September 2011") //
|
||||||
|
.build(clientID.toString(), loanProductID.toString(), null);
|
||||||
|
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoanRepaymentExecutor implements Runnable {
|
||||||
|
|
||||||
|
private final Integer loanId;
|
||||||
|
private final Float repaymentAmount;
|
||||||
|
private final String repaymentDate;
|
||||||
|
private final LoanTransactionHelper loanTransactionHelper;
|
||||||
|
|
||||||
|
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
|
||||||
|
|
||||||
|
LoanRepaymentExecutor(LoanTransactionHelper loanTransactionHelper, Integer loanId, Float repaymentAmount, Calendar repaymentDate) {
|
||||||
|
this.loanId = loanId;
|
||||||
|
this.repaymentAmount = repaymentAmount;
|
||||||
|
this.repaymentDate = dateFormat.format(repaymentDate.getTime());
|
||||||
|
this.loanTransactionHelper = loanTransactionHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
this.loanTransactionHelper.makeRepayment(repaymentDate, repaymentAmount, loanId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Found an exception" + e.getMessage());
|
||||||
|
System.out.println("Details of failed concurrent transaction (date, amount, loanId) are " + repaymentDate + ","
|
||||||
|
+ repaymentAmount + "," + loanId);
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
System.out.println("Details of passed concurrent transaction, details (date, amount, loanId) are " + repaymentDate + ","
|
||||||
|
+ repaymentAmount + "," + loanId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+25
-3
@@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.mifosplatform.commands.service;
|
package org.mifosplatform.commands.service;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.mifosplatform.commands.domain.CommandSource;
|
import org.mifosplatform.commands.domain.CommandSource;
|
||||||
import org.mifosplatform.commands.domain.CommandSourceRepository;
|
import org.mifosplatform.commands.domain.CommandSourceRepository;
|
||||||
@@ -19,6 +21,8 @@ import org.mifosplatform.infrastructure.jobs.service.SchedulerJobRunnerReadServi
|
|||||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.mifosplatform.useradministration.domain.AppUser;
|
import org.mifosplatform.useradministration.domain.AppUser;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.CannotAcquireLockException;
|
||||||
|
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -64,16 +68,34 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
|
|
||||||
final String json = wrapper.getJson();
|
final String json = wrapper.getJson();
|
||||||
CommandProcessingResult result = null;
|
CommandProcessingResult result = null;
|
||||||
|
JsonCommand command = null;
|
||||||
|
Integer numberOfRetries = 1;
|
||||||
|
Integer maxNumberOfRetries = 10;
|
||||||
try {
|
try {
|
||||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
|
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
|
||||||
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
|
command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(),
|
||||||
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
|
wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(),
|
||||||
wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId());
|
wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId());
|
||||||
|
|
||||||
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
||||||
} catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
|
} catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
|
||||||
|
|
||||||
result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
|
result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
|
||||||
|
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
|
||||||
|
System.out.println("Number of times this transaction has been retried " + numberOfRetries);
|
||||||
|
if (numberOfRetries >= maxNumberOfRetries) {
|
||||||
|
System.out.println("This transaction is done for");
|
||||||
|
throw (exception);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Random random = new Random();
|
||||||
|
int randomNum = random.nextInt(10) + 1;
|
||||||
|
Thread.sleep(1000 + (randomNum * 1000));
|
||||||
|
numberOfRetries = numberOfRetries + 1;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw (exception);
|
||||||
|
}
|
||||||
|
this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+2
-2
@@ -445,7 +445,7 @@ 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()){
|
if (wrapper.addAndDeleteDisbursementDetails()) {
|
||||||
handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
|
handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class);
|
||||||
}
|
}
|
||||||
} else if (wrapper.isGLAccountResource()) {
|
} else if (wrapper.isGLAccountResource()) {
|
||||||
@@ -923,7 +923,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
} else {
|
} else {
|
||||||
throw new UnsupportedCommandException(wrapper.commandName());
|
throw new UnsupportedCommandException(wrapper.commandName());
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
|
|
||||||
throw new UnsupportedCommandException(wrapper.commandName());
|
throw new UnsupportedCommandException(wrapper.commandName());
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-61
@@ -34,6 +34,7 @@ import javax.persistence.Temporal;
|
|||||||
import javax.persistence.TemporalType;
|
import javax.persistence.TemporalType;
|
||||||
import javax.persistence.Transient;
|
import javax.persistence.Transient;
|
||||||
import javax.persistence.UniqueConstraint;
|
import javax.persistence.UniqueConstraint;
|
||||||
|
import javax.persistence.Version;
|
||||||
|
|
||||||
import org.apache.commons.lang.ObjectUtils;
|
import org.apache.commons.lang.ObjectUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -119,8 +120,8 @@ import com.google.gson.JsonPrimitive;
|
|||||||
public class Loan extends AbstractPersistable<Long> {
|
public class Loan extends AbstractPersistable<Long> {
|
||||||
|
|
||||||
/** Disable optimistic locking till batch jobs failures can be fixed **/
|
/** Disable optimistic locking till batch jobs failures can be fixed **/
|
||||||
// @Version
|
@Version
|
||||||
// int version;
|
int version;
|
||||||
|
|
||||||
@Column(name = "account_no", length = 20, unique = true, nullable = false)
|
@Column(name = "account_no", length = 20, unique = true, nullable = false)
|
||||||
private String accountNumber;
|
private String accountNumber;
|
||||||
@@ -1407,7 +1408,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
this.disbursementDetails.clear();
|
this.disbursementDetails.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount ()) {
|
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) {
|
||||||
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
|
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
|
||||||
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
||||||
actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount);
|
actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount);
|
||||||
@@ -1590,27 +1591,29 @@ 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();
|
||||||
}
|
}
|
||||||
if(id != null){
|
if (id != null) {
|
||||||
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id);
|
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id);
|
||||||
list.remove(id);
|
list.remove(id);
|
||||||
if(loanDisbursementDetail.actualDisbursementDate() == null){
|
if (loanDisbursementDetail.actualDisbursementDate() == null) {
|
||||||
Date actualDisbursementDate = null;
|
Date actualDisbursementDate = null;
|
||||||
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal);
|
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate,
|
||||||
|
actualDisbursementDate, principal);
|
||||||
if (!loanDisbursementDetail.equals(disbursementDetails)) {
|
if (!loanDisbursementDetail.equals(disbursementDetails)) {
|
||||||
loanDisbursementDetail.copy(disbursementDetails);
|
loanDisbursementDetail.copy(disbursementDetails);
|
||||||
actualChanges.put("disbursementDetailId", id);
|
actualChanges.put("disbursementDetailId", id);
|
||||||
actualChanges.put("recalculateLoanSchedule", true);
|
actualChanges.put("recalculateLoanSchedule", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} 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);
|
||||||
}
|
}
|
||||||
else{
|
i++;
|
||||||
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));
|
||||||
@@ -1871,8 +1874,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 JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
|
||||||
|
|
||||||
validateAccountStatus(LoanEvent.LOAN_APPROVED);
|
validateAccountStatus(LoanEvent.LOAN_APPROVED);
|
||||||
|
|
||||||
@@ -1911,7 +1913,6 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
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
|
||||||
@@ -1931,36 +1932,30 @@ 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 */
|
/* Update disbursement details */
|
||||||
if (disbursementDataArray != null) {
|
if (disbursementDataArray != null) {
|
||||||
updateDisbursementDetails(command, actualChanges);
|
updateDisbursementDetails(command, actualChanges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loanProduct.isMultiDisburseLoan()) {
|
if (loanProduct.isMultiDisburseLoan()) {
|
||||||
|
|
||||||
if (this.disbursementDetails.isEmpty()) {
|
if (this.disbursementDetails.isEmpty()) {
|
||||||
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(
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
LoanApiConstants.disbursementDataParameterName,
|
}
|
||||||
errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.disbursementDetails.size() > loanProduct
|
if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) {
|
||||||
.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 "
|
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
|
||||||
+ loanProduct.maxTrancheCount();
|
loanProduct.maxTrancheCount(), disbursementDetails.size());
|
||||||
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());
|
||||||
@@ -1975,14 +1970,14 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
getApprovedOnDate(), submittalDate);
|
getApprovedOnDate(), submittalDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(expecteddisbursementDate != null){
|
if (expecteddisbursementDate != null) {
|
||||||
this.expectedDisbursementDate = expecteddisbursementDate.toDate();
|
this.expectedDisbursementDate = expecteddisbursementDate.toDate();
|
||||||
actualChanges.put("expectedDisbursementDate", expectedDisbursementDate);
|
actualChanges.put("expectedDisbursementDate", expectedDisbursementDate);
|
||||||
|
|
||||||
if(expecteddisbursementDate.isBefore(approvedOn)) {
|
if (expecteddisbursementDate.isBefore(approvedOn)) {
|
||||||
final String errorMessage = "The expected disbursement date should be either on or after the approval date: "
|
final String errorMessage = "The expected disbursement date should be either on or after the approval date: "
|
||||||
+ approvedOn.toString();
|
+ approvedOn.toString();
|
||||||
throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage,
|
throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage,
|
||||||
getApprovedOnDate(), expecteddisbursementDate);
|
getApprovedOnDate(), expecteddisbursementDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2115,7 +2110,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
|
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
|
||||||
final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser) {
|
final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser) {
|
||||||
boolean isEmiAmountChanged = false;
|
boolean isEmiAmountChanged = false;
|
||||||
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount ()) && emiAmount != null
|
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount()) && emiAmount != null
|
||||||
&& emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
&& emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||||
@@ -2182,19 +2177,19 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
disbursementDetails.updatePrincipal(principalDisbursed);
|
disbursementDetails.updatePrincipal(principalDisbursed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(this.loanProduct().isMultiDisburseLoan()){
|
if (this.loanProduct().isMultiDisburseLoan()) {
|
||||||
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
|
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
|
||||||
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
||||||
for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
|
for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
|
||||||
setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
|
setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
|
||||||
}
|
}
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
||||||
if(setPrincipalAmount.compareTo(this.approvedPrincipal) == 1){
|
if (setPrincipalAmount.compareTo(this.approvedPrincipal) == 1) {
|
||||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
|
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,
|
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", principalDisbursed,
|
||||||
this.approvedPrincipal);
|
this.approvedPrincipal);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
||||||
}
|
}
|
||||||
if (!(this.loanProduct().isMultiDisburseLoan()) && diff.compareTo(BigDecimal.ZERO) == -1) {
|
if (!(this.loanProduct().isMultiDisburseLoan()) && diff.compareTo(BigDecimal.ZERO) == -1) {
|
||||||
@@ -2437,8 +2432,8 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
getApprovedOnDate());
|
getApprovedOnDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getExpectedFirstRepaymentOnDate() != null && disbursedOn.isAfter(getExpectedFirstRepaymentOnDate()) &&
|
if (getExpectedFirstRepaymentOnDate() != null && disbursedOn.isAfter(getExpectedFirstRepaymentOnDate())
|
||||||
disbursedOn.toDate().equals(this.actualDisbursementDate)) {
|
&& disbursedOn.toDate().equals(this.actualDisbursementDate)) {
|
||||||
final String errorMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate: "
|
final String errorMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate: "
|
||||||
+ getExpectedFirstRepaymentOnDate().toString();
|
+ getExpectedFirstRepaymentOnDate().toString();
|
||||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage,
|
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage,
|
||||||
@@ -4130,9 +4125,10 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
return AccountType.fromInt(this.loanType).isGroupAccount();
|
return AccountType.fromInt(this.loanType).isGroupAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isJLGLoan(){
|
public boolean isJLGLoan() {
|
||||||
return AccountType.fromInt(this.loanType).isJLGAccount();
|
return AccountType.fromInt(this.loanType).isJLGAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateInterestRateFrequencyType() {
|
public void updateInterestRateFrequencyType() {
|
||||||
this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
|
this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
|
||||||
}
|
}
|
||||||
@@ -4478,22 +4474,24 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
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();
|
final Locale locale = command.extractLocale();
|
||||||
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
|
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
|
||||||
final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, locale);
|
final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName,
|
||||||
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName);
|
locale);
|
||||||
|
final LocalDate expectedDisbursementDate = command
|
||||||
|
.localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName);
|
||||||
disbursementDetails.updateExpectedDisbursementDateAndAmount(expectedDisbursementDate.toDate(), principal);
|
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,
|
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName,
|
||||||
command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale));
|
command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale));
|
||||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||||
|
|
||||||
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
|
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
|
||||||
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
||||||
for (LoanDisbursementDetails details : loanDisburseDetails) {
|
for (LoanDisbursementDetails details : loanDisburseDetails) {
|
||||||
setPrincipalAmount = setPrincipalAmount.add(details.principal());
|
setPrincipalAmount = setPrincipalAmount.add(details.principal());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
||||||
|
|||||||
Reference in New Issue
Block a user