diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index c13f419ff..dc56b8933 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -488,9 +488,13 @@ public class CommandWrapper { } 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() { return this.entityName.equalsIgnoreCase("STANDINGINSTRUCTION"); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 6fafeaf2e..d1e9ff115 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -740,6 +740,15 @@ public class CommandWrapperBuilder { 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) { this.actionName = "DELETE"; this.entityName = "LOAN"; 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 58d182c13..8ff2c2ff1 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,6 +445,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer if (wrapper.isUpdateDisbursementDate()) { handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class); } + if(wrapper.addAndDeleteDisbursementDetails()){ + handler = this.applicationContext.getBean("addAndDeleteLoanDisburseDetailsCommandHandler", NewCommandSourceHandler.class); + } } else if (wrapper.isGLAccountResource()) { if (wrapper.isCreate()) { handler = this.applicationContext.getBean("createGLAccountCommandHandler", NewCommandSourceHandler.class); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java index 1f90ae70f..b5b8fcf33 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java @@ -12,6 +12,8 @@ public interface LoanApiConstants { public static final String disbursementDataParameterName = "disbursementData"; public static final String disbursementDateParameterName = "expectedDisbursementDate"; 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 principalDisbursedParameterName = "transactionAmount"; @@ -38,5 +40,8 @@ public interface LoanApiConstants { public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches"; public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date"; public static final String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal"; + 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"; + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java index fbf30f885..95fb1fd81 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java @@ -11,10 +11,12 @@ import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; @@ -66,15 +68,29 @@ public class LoanDisbursementDetailApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String updateDisbursementDate(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId, final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId, disbursementId) - .withJson(apiRequestBodyAsJson).build(); + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId, disbursementId) + .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); + + return this.toApiJsonSerializer.serialize(result); + } + @GET @Path("{disbursementId}") @Consumes({ MediaType.APPLICATION_JSON }) 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 a5873d6d1..5add0092a 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 @@ -1392,11 +1392,13 @@ public class Loan extends AbstractPersistable { this.maxOutstandingLoanBalance = command .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"; 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(); throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, loanProduct.maxTrancheCount(), disbursementDetails.size()); @@ -1531,7 +1533,7 @@ public class Loan extends AbstractPersistable { return amount; } - private void updateDisbursementDetails(final JsonCommand command, final Map actualChanges) { + public void updateDisbursementDetails(final JsonCommand command, final Map actualChanges) { List list = fetchDisbursementIds(); @@ -1588,23 +1590,27 @@ public class Loan extends AbstractPersistable { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); } - Date actualDisbursementDate = null; - LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal); - if (id == null) { - disbursementDetails.updateLoan(this); - this.disbursementDetails.add(disbursementDetails); - actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal); - actualChanges.put("recalculateLoanSchedule", true); - } else { - list.remove(id); + if(id != null){ LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id); - if (!loanDisbursementDetail.equals(disbursementDetails)) { - loanDisbursementDetail.copy(disbursementDetails); - actualChanges.put("disbursementDetailId", id); - actualChanges.put("recalculateLoanSchedule", true); + list.remove(id); + 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); + } } } - 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()); for (Long id : list) { this.disbursementDetails.remove(fetchLoanDisbursementsById(id)); @@ -2173,12 +2179,25 @@ public class Loan extends AbstractPersistable { } else { for (LoanDisbursementDetails disbursementDetails : details) { disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate()); - diff = diff.add(disbursementDetails.principal().subtract(principalDisbursed)); disbursementDetails.updatePrincipal(principalDisbursed); } } - this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount()); - if (diff.compareTo(BigDecimal.ZERO) == -1) { + if(this.loanProduct().isMultiDisburseLoan()){ + Collection 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 "; throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed, this.loanRepaymentScheduleDetail.getPrincipal().getAmount()); @@ -3525,7 +3544,7 @@ public class Loan extends AbstractPersistable { boolean isRegenerationRequired = false; if (this.loanProduct.isMultiDisburseLoan()) { LoanDisbursementDetails details = fetchLastDisburseDetail(); - if (!details.expectedDisbursementDate().equals(details.actualDisbursementDate())) { + if (details != null && !(details.expectedDisbursementDate().equals(details.actualDisbursementDate()))) { isRegenerationRequired = true; } } @@ -4454,19 +4473,30 @@ public class Loan extends AbstractPersistable { return this.disbursementDetails; } - public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails, + public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails, final JsonCommand command, final List existingTransactionIds, final List existingReversedTransactionIds, final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { + final Locale locale = command.extractLocale(); validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); - final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); - disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); + 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)); existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + Collection loanDisburseDetails = this.getDisbursementDetails(); + BigDecimal setPrincipalAmount = BigDecimal.ZERO; + for (LoanDisbursementDetails details : loanDisburseDetails) { + setPrincipalAmount = setPrincipalAmount.add(details.principal()); + } + + this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } else { @@ -4600,7 +4630,7 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } - private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { + public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { LocalDate lastTransactionDate = getLastUserTransactionDate(); generatorDTO.setLastTransactionDate(lastTransactionDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java index bfdc39837..29052206e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java @@ -113,8 +113,9 @@ public class LoanDisbursementDetails extends AbstractPersistable { this.actualDisbursementDate = actualDisbursementDate; } - public void updateExpectedDisbursementDate(Date expectedDisbursementDate) { + public void updateExpectedDisbursementDateAndAmount(Date expectedDisbursementDate, BigDecimal principal) { this.expectedDisbursementDate = expectedDisbursementDate; + this.principal = principal; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java new file mode 100644 index 000000000..07c89a57f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/AddAndDeleteLoanDisburseDetailsCommandHandler.java @@ -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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java index 767de6383..85cd204ac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java @@ -26,7 +26,7 @@ public class UpdateLoanDisbuseDateCommandHandler implements NewCommandSourceHand @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.writePlatformService.updateDisbursementDateForTranche(command.getLoanId(), command.entityId(), command); + + return this.writePlatformService.updateDisbursementDateAndAmountForTranche(command.getLoanId(), command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 183868f0d..ca13948d1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -17,6 +17,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; 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.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -831,10 +832,49 @@ public final class LoanApplicationCommandFromApiJsonHelper { "Validation errors exist.", dataValidationErrors); } } + private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final DataValidatorBuilder baseDataValidator) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper + .extractLocaleParameter(topLevelJsonElement); + final String dateFormat = this.fromApiJsonHelper + .extractDateFormatParameter(topLevelJsonElement); + final JsonArray variationArray = this.fromApiJsonHelper + .extractJsonArrayNamed( + LoanApiConstants.disbursementDataParameterName, + element); + for(int i=0 ; i< variationArray.size();i++){ + final JsonObject jsonObject1 = variationArray.get(i) + .getAsJsonObject(); + LocalDate date1 = this.fromApiJsonHelper + .extractLocalDateNamed( + LoanApiConstants.disbursementDateParameterName, + jsonObject1, dateFormat, locale); + + for(int j=i+1;j dataValidationErrors) { @@ -363,12 +370,13 @@ public final class LoanEventApiJsonValidator { 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(); } - final Set disbursementParameters = new HashSet<>(Arrays.asList(LoanApiConstants.disbursementDateParameterName, - "locale", "dateFormat")); + final Set disbursementParameters = new HashSet<>(Arrays.asList("locale", "dateFormat", LoanApiConstants.disbursementDataParameterName, + LoanApiConstants.approvedLoanAmountParameterName, LoanApiConstants.updatedDisbursementDateParameterName, LoanApiConstants.updatedDisbursementPrincipalParameterName, + LoanApiConstants.disbursementDateParameterName)); final Type typeOfMap = new TypeToken>() {}.getType(); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); @@ -380,12 +388,22 @@ public final class LoanEventApiJsonValidator { final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( LoanApiConstants.disbursementDateParameterName, element); 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) { baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) .failWithCode(LoanApiConstants.ALREADY_DISBURSED); } + + fromApiJsonDeserializer.validateLoanMultiDisbursementdate(element, baseDataValidator, actualDisbursementDate, approvedPrincipal); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 9707ff12a..e36d6b8bd 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -740,7 +740,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa .build(); } - private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){ + public void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){ final String json = command.json(); final JsonElement element = this.fromJsonHelper.parse(json); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index 850c43afa..bd95e534b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -78,7 +78,7 @@ public interface LoanWritePlatformService { CommandProcessingResult undoWriteOff(Long loanId); - CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command); + CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command); void recalculateInterest(); @@ -88,4 +88,6 @@ public interface LoanWritePlatformService { CommandProcessingResult makeLoanRefund(Long loanId, JsonCommand command); + CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 9fe8ebdca..4a4a234a1 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -123,11 +123,13 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; 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.LoanDisbursalException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; 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.loanschedule.data.OverdueLoanScheduleData; 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.util.CollectionUtils; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; @Service @@ -2541,17 +2544,59 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - @Override + private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){ + final String json = command.json(); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); + if(disbursementDataArray == null || disbursementDataArray.size() == 0){ + final String errorMessage = "For this loan product, disbursement details must be provided"; + throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); + } + final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element); + + loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + @Override @Transactional - public CommandProcessingResult updateDisbursementDateForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) { - - AppUser currentUser = getAppUserIfPresent(); - + public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) { + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); - LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); - this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(), loanDisbursementDetails); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + final Map actualChanges = new LinkedHashMap<>(); + LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate(); + 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()); final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( loan.getDisbursementDate(), loan, calendarInstance); @@ -2580,12 +2625,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, lastTransactionDate); - final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, - existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); + ChangedTransactionDetail changedTransactionDetail = null; + AppUser currentUser = getAppUserIfPresent(); + + if(command.entityId() != null){ + + changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, + existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); + }else{ + Collection 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); - if (changedTransactionDetail != null) { + if (command.entityId() != null && changedTransactionDetail != null) { for (Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } @@ -2600,8 +2665,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .withClientId(loan.getClientId()) // .withGroupId(loan.getGroupId()) // .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); + + } public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command, diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V241__alter_loan_disbursement_details.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V241__alter_loan_disbursement_details.sql new file mode 100644 index 000000000..a0cdc4026 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V241__alter_loan_disbursement_details.sql @@ -0,0 +1,2 @@ +ALTER TABLE `m_loan_disbursement_detail` + DROP COLUMN approved_principal; \ No newline at end of file