sorting out concurrency issues with loans -1

This commit is contained in:
Vishwas Babu A J
2015-03-05 20:32:38 +05:30
parent 221cb1c17d
commit eaa32e9465
4 changed files with 234 additions and 72 deletions
@@ -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);
}
}
}
@@ -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;
@@ -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());
} }
@@ -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);
@@ -1906,14 +1908,13 @@ public class Loan extends AbstractPersistable<Long> {
approvedOn = command.localDateValueOfParameterNamed("eventDate"); approvedOn = command.localDateValueOfParameterNamed("eventDate");
approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); approvedOnDateChange = command.stringValueOfParameterNamed("eventDate");
} }
LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate"); LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName); BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
if (approvedLoanAmount != null) { if (approvedLoanAmount != null) {
// Approved amount has to be less than or equal to principal // Approved amount has to be less than or equal to principal
// amount demanded // amount demanded
@@ -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());
@@ -1974,15 +1969,15 @@ public class Loan extends AbstractPersistable<Long> {
throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage,
getApprovedOnDate(), submittalDate); getApprovedOnDate(), submittalDate);
} }
if(expecteddisbursementDate != null){ 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,24 +4474,26 @@ 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);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);