From eaa32e9465c78b29d36068615c65c3e650772fe5 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Thu, 5 Mar 2015 20:32:38 +0530 Subject: [PATCH] sorting out concurrency issues with loans -1 --- .../ConcurrencyIntegrationTest.java | 142 ++++++++++++++++++ ...CommandSourceWritePlatformServiceImpl.java | 28 +++- .../SynchronousCommandProcessingService.java | 4 +- .../portfolio/loanaccount/domain/Loan.java | 132 ++++++++-------- 4 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ConcurrencyIntegrationTest.java diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ConcurrencyIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ConcurrencyIntegrationTest.java new file mode 100644 index 000000000..4d44c925f --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ConcurrencyIntegrationTest.java @@ -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); + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index e458e041e..0cfea7b07 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.commands.service; +import java.util.Random; + import org.joda.time.DateTime; import org.mifosplatform.commands.domain.CommandSource; 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.useradministration.domain.AppUser; 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.transaction.annotation.Transactional; @@ -64,16 +68,34 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio final String json = wrapper.getJson(); CommandProcessingResult result = null; + JsonCommand command = null; + Integer numberOfRetries = 1; + Integer maxNumberOfRetries = 10; try { final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json); - final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), - wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), - wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId()); + command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(), + wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), + wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId()); result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker); } catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { 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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index abbcdadb4..a891ed8bd 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -445,7 +445,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer if (wrapper.isUpdateDisbursementDate()) { handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class); } - if(wrapper.addAndDeleteDisbursementDetails()){ + if (wrapper.addAndDeleteDisbursementDetails()) { handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class); } } else if (wrapper.isGLAccountResource()) { @@ -923,7 +923,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer } else { throw new UnsupportedCommandException(wrapper.commandName()); } - }else { + } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 5add0092a..09ba88801 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -34,6 +34,7 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; +import javax.persistence.Version; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; @@ -119,8 +120,8 @@ import com.google.gson.JsonPrimitive; public class Loan extends AbstractPersistable { /** Disable optimistic locking till batch jobs failures can be fixed **/ - // @Version - // int version; + @Version + int version; @Column(name = "account_no", length = 20, unique = true, nullable = false) private String accountNumber; @@ -1407,7 +1408,7 @@ public class Loan extends AbstractPersistable { this.disbursementDetails.clear(); } - if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount ()) { + if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) { if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) { this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount); @@ -1590,27 +1591,29 @@ public class Loan extends AbstractPersistable { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); } - if(id != null){ + if (id != null) { LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id); list.remove(id); - if(loanDisbursementDetail.actualDisbursementDate() == null){ - Date actualDisbursementDate = null; - LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal); + if (loanDisbursementDetail.actualDisbursementDate() == null) { + Date actualDisbursementDate = null; + LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, + actualDisbursementDate, principal); if (!loanDisbursementDetail.equals(disbursementDetails)) { loanDisbursementDetail.copy(disbursementDetails); actualChanges.put("disbursementDetailId", id); 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{ - 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++; + i++; } while (i < disbursementDataArray.size()); for (Long id : list) { this.disbursementDetails.remove(fetchLoanDisbursementsById(id)); @@ -1871,8 +1874,7 @@ public class Loan extends AbstractPersistable { } public Map loanApplicationApproval(final AppUser currentUser, final JsonCommand command, - final JsonArray disbursementDataArray, - final LoanLifecycleStateMachine loanLifecycleStateMachine) { + final JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) { validateAccountStatus(LoanEvent.LOAN_APPROVED); @@ -1906,14 +1908,13 @@ public class Loan extends AbstractPersistable { approvedOn = command.localDateValueOfParameterNamed("eventDate"); approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); } - + LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate"); BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName); - if (approvedLoanAmount != null) { - + // Approved amount has to be less than or equal to principal // amount demanded @@ -1931,36 +1932,30 @@ public class Loan extends AbstractPersistable { actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount); actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount); - } else if(approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) { + } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) { final String errorMessage = "Loan approved amount can't be greater than loan amount demanded."; throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", errorMessage, this.proposedPrincipal, approvedLoanAmount); } - + /* Update disbursement details */ - if (disbursementDataArray != null) { - updateDisbursementDetails(command, actualChanges); - } - } - if (loanProduct.isMultiDisburseLoan()) { + if (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.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()); - } - } + if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) { + final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount(); + throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, + loanProduct.maxTrancheCount(), disbursementDetails.size()); + } + } this.approvedOnDate = approvedOn.toDate(); this.approvedBy = currentUser; actualChanges.put("locale", command.locale()); @@ -1974,15 +1969,15 @@ public class Loan extends AbstractPersistable { throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, getApprovedOnDate(), submittalDate); } - - if(expecteddisbursementDate != null){ - this.expectedDisbursementDate = expecteddisbursementDate.toDate(); + + 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: " + 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, + throw new InvalidLoanStateTransitionException("expecteddisbursal", "should.be.on.or.after.approval.date", errorMessage, getApprovedOnDate(), expecteddisbursementDate); } } @@ -2115,7 +2110,7 @@ public class Loan extends AbstractPersistable { public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule, final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser) { 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) { if (this.loanProduct.isMultiDisburseLoan()) { LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(), @@ -2182,19 +2177,19 @@ public class Loan extends AbstractPersistable { disbursementDetails.updatePrincipal(principalDisbursed); } } - if(this.loanProduct().isMultiDisburseLoan()){ + if (this.loanProduct().isMultiDisburseLoan()) { Collection loanDisburseDetails = this.getDisbursementDetails(); - BigDecimal setPrincipalAmount = BigDecimal.ZERO; + BigDecimal setPrincipalAmount = BigDecimal.ZERO; for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) { - setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal()); + 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 "; + 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{ + } else { this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount()); } if (!(this.loanProduct().isMultiDisburseLoan()) && diff.compareTo(BigDecimal.ZERO) == -1) { @@ -2437,8 +2432,8 @@ public class Loan extends AbstractPersistable { getApprovedOnDate()); } - if (getExpectedFirstRepaymentOnDate() != null && disbursedOn.isAfter(getExpectedFirstRepaymentOnDate()) && - disbursedOn.toDate().equals(this.actualDisbursementDate)) { + if (getExpectedFirstRepaymentOnDate() != null && disbursedOn.isAfter(getExpectedFirstRepaymentOnDate()) + && disbursedOn.toDate().equals(this.actualDisbursementDate)) { final String errorMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate: " + getExpectedFirstRepaymentOnDate().toString(); throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage, @@ -4130,9 +4125,10 @@ public class Loan extends AbstractPersistable { return AccountType.fromInt(this.loanType).isGroupAccount(); } - public boolean isJLGLoan(){ - return AccountType.fromInt(this.loanType).isJLGAccount(); + public boolean isJLGLoan() { + return AccountType.fromInt(this.loanType).isJLGAccount(); } + public void updateInterestRateFrequencyType() { this.loanRepaymentScheduleDetail.updatenterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType()); } @@ -4478,24 +4474,26 @@ public class Loan extends AbstractPersistable { final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { final Locale locale = command.extractLocale(); validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); - final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, locale); - final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName); + final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, + locale); + final LocalDate expectedDisbursementDate = command + .localDateValueOfParameterNamed(LoanApiConstants.updatedDisbursementDateParameterName); disbursementDetails.updateExpectedDisbursementDateAndAmount(expectedDisbursementDate.toDate(), principal); actualChanges.put(LoanApiConstants.disbursementDateParameterName, command.stringValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName)); actualChanges.put(LoanApiConstants.disbursementIdParameterName, command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName)); - actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, - command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale)); + actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, + command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale)); existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); Collection loanDisburseDetails = this.getDisbursementDetails(); - BigDecimal setPrincipalAmount = BigDecimal.ZERO; + BigDecimal setPrincipalAmount = BigDecimal.ZERO; for (LoanDisbursementDetails details : loanDisburseDetails) { - setPrincipalAmount = setPrincipalAmount.add(details.principal()); + setPrincipalAmount = setPrincipalAmount.add(details.principal()); } - + this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);