Merge pull request #1583 from sachinkulkarni12/CC-32New

CC-32: undo last tranche disbursal feature
This commit is contained in:
pramod
2016-01-06 16:44:38 +05:30
15 changed files with 439 additions and 5 deletions
@@ -101,7 +101,7 @@ public class LoanApplicationApprovalTest {
}
private HashMap createTrancheDetail(final String date, final String amount) {
public HashMap createTrancheDetail(final String date, final String amount) {
HashMap<String, Object> detail = new HashMap<>();
detail.put("expectedDisbursementDate", date);
detail.put("principal", amount);
@@ -242,7 +242,7 @@ public class LoanApplicationApprovalTest {
return this.loanTransactionHelper.getLoanId(loanApplication);
}
private Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
public Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
List<HashMap> tranches) {
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder()
@@ -0,0 +1,145 @@
/**
* 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.integrationtests;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.junit.Assert;
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.loans.LoanApplicationTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanStatusChecker;
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;
import org.mifosplatform.integrationtests.LoanApplicationApprovalTest;
@SuppressWarnings("rawtypes")
public class LoanApplicationUndoLastTrancheTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper;
private LoanApplicationApprovalTest loanApplicationApprovalTest;
@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);
this.loanApplicationApprovalTest = new LoanApplicationApprovalTest();
}
@Test
public void LoanApplicationUndoLastTranche(){
final String proposedAmount = "5000";
final String approvalAmount = "2000";
final String approveDate = "1 March 2014";
final String expectedDisbursementDate = "1 March 2014";
final String disbursalDate = "1 March 2014";
// CREATE CLIENT
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+ clientID);
// CREATE LOAN MULTIDISBURSAL PRODUCT
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
.withInterestTypeAsDecliningBalance().withTranches(true).build(null));
System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+ loanProductID);
// CREATE TRANCHES
List<HashMap> createTranches = new ArrayList<>();
createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("1 March 2014", "1000"));
createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("23 June 2014", "4000"));
// APPROVE TRANCHES
List<HashMap> approveTranches = new ArrayList<>();
approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("1 March 2014", "1000"));
approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("23 June 2014", "1000"));
// APPLY FOR LOAN WITH TRANCHES
final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+ loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
// VALIDATE THE LOAN STATUS
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount,
loanID, approveTranches);
// VALIDATE THE LOAN IS APPROVED
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
// DISBURSE A LOAN
this.loanTransactionHelper.disburseLoan(disbursalDate, loanID);
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
// VALIDATE THE LOAN IS ACTIVE STATUS
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
System.out.println("-------------Make repayment 1-----------");
this.loanTransactionHelper.makeRepayment("01 April 2014", Float.valueOf("420"), loanID);
System.out.println("-------------Make repayment 2-----------");
this.loanTransactionHelper.makeRepayment("01 May 2014", Float.valueOf("412"), loanID);
System.out.println("-------------Make repayment 3-----------");
this.loanTransactionHelper.makeRepayment("01 June 2014", Float.valueOf("204"), loanID);
// DISBURSE A SECOND TRANCHE
this.loanTransactionHelper.disburseLoan("23 June 2014", loanID);
// UNDO LAST TRANCHE
Float disbursedAmount = this.loanTransactionHelper.undoLastDisbursal(loanID);
validateDisbursedAmount(disbursedAmount);
}
private void validateDisbursedAmount(Float disbursedAmount) {
Assert.assertEquals(Float.valueOf("1000.0"), disbursedAmount);
}
public Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
List<HashMap> tranches) {
System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder()
//
.withPrincipal(principal)
//
.withLoanTermFrequency("5")
//
.withLoanTermFrequencyAsMonths()
//
.withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
.withInterestRatePerPeriod("2") //
.withExpectedDisbursementDate("1 March 2014") //
.withTranches(tranches) //
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate("1 March 2014") //
.build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
}
@@ -32,6 +32,7 @@ public class LoanTransactionHelper {
private static final String DISBURSE_LOAN_COMMAND = "disburse";
private static final String DISBURSE_LOAN_TO_SAVINGS_COMMAND = "disburseToSavings";
private static final String UNDO_DISBURSE_LOAN_COMMAND = "undoDisbursal";
private static final String UNDO_LAST_DISBURSE_LOAN_COMMAND = "undolastdisbursal";
private static final String WRITE_OFF_LOAN_COMMAND = "writeoff";
private static final String WAIVE_INTEREST_COMMAND = "waiveinterest";
private static final String MAKE_REPAYMENT_COMMAND = "repayment";
@@ -167,6 +168,13 @@ public class LoanTransactionHelper {
System.out.println("IN DISBURSE LOAN URL " + url);
return performLoanTransaction(createLoanOperationURL(UNDO_DISBURSE_LOAN_COMMAND, loanID), undoDisburseJson);
}
public Float undoLastDisbursal(final Integer loanID) {
final String undoLastDisburseJson = "{'note' : 'UNDO LAST DISBURSAL'}";
final String url = createLoanOperationURL(UNDO_LAST_DISBURSE_LOAN_COMMAND, loanID);
System.out.println("IN UNDO LAST DISBURSE LOAN URL " + url);
return performUndoLastLoanDisbursementTransaction(createLoanOperationURL(UNDO_LAST_DISBURSE_LOAN_COMMAND, loanID), undoLastDisburseJson);
}
public void recoverFromGuarantor(final Integer loanID) {
performLoanTransaction(createLoanOperationURL(RECOVER_FROM_GUARANTORS_COMMAND, loanID), "", "");
@@ -424,6 +432,13 @@ public class LoanTransactionHelper {
"changes");
return (HashMap) response.get("status");
}
private Float performUndoLastLoanDisbursementTransaction(final String postURLForLoanTransaction, final String jsonToBeSent) {
final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent,
"changes");
return (Float) response.get("disbursedAmount");
}
private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, final String responseAttribute) {
return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute);
@@ -817,6 +817,15 @@ public class CommandWrapperBuilder {
this.href = "/loans/" + loanId;
return this;
}
public CommandWrapperBuilder undoLastDisbursalLoanApplication(final Long loanId) {
this.actionName = "DISBURSALLASTUNDO";
this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId;
return this;
}
public CommandWrapperBuilder assignLoanOfficer(final Long loanId) {
this.actionName = "UPDATELOANOFFICER";
@@ -11,7 +11,7 @@ import java.util.Set;
public class BusinessEventNotificationConstants {
public static enum BUSINESS_EVENTS {
LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_TRANSACTION(
LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL("loan_undo_lastdisbursal"),LOAN_UNDO_TRANSACTION(
"loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT(
"loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF("loan_undo_writtenoff"), LOAN_DISBURSAL(
"loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE("loan_close"), LOAN_CLOSE_AS_RESCHEDULE(
@@ -661,6 +661,9 @@ public class LoansApiResource {
} else if (is(commandParam, "undodisbursal")) {
final CommandWrapper commandRequest = builder.undoLoanApplicationDisbursal(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if (is(commandParam, "undolastdisbursal")) {
final CommandWrapper commandRequest = builder.undoLastDisbursalLoanApplication(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
if (is(commandParam, "assignloanofficer")) {
@@ -98,6 +98,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDa
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
@@ -105,6 +106,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleG
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
@@ -4517,6 +4519,10 @@ public class Loan extends AbstractPersistable<Long> {
action = "refund";
postfix = "cannot.be.made.before.client.transfer.date";
break;
case LOAN_DISBURSAL_UNDO_LAST:
errorMessage = "Cannot undo a last disbursal in another branch";
action = "disbursal";
postfix = "cannot.be.undone.before.client.transfer.date";
default:
break;
}
@@ -4567,6 +4573,18 @@ public class Loan extends AbstractPersistable<Long> {
}
return currentTransactionDate;
}
public LocalDate getLastRepaymentDate() {
LocalDate currentTransactionDate = getDisbursementDate();
for (final LoanTransaction previousTransaction : this.loanTransactions) {
if (previousTransaction.isRepayment()) {
if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
currentTransactionDate = previousTransaction.getTransactionDate();
}
}
}
return currentTransactionDate;
}
public LocalDate getLastUserTransactionForChargeCalc() {
LocalDate lastTransaction = getDisbursementDate();
@@ -4738,6 +4756,13 @@ public class Loan extends AbstractPersistable<Long> {
dataValidationErrors.add(error);
}
break;
case LOAN_DISBURSAL_UNDO_LAST:
if (!isOpen()) {
final String defaultUserMessage = "Loan Undo last disbursal is not allowed. Loan Account is not active.";
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.undo.last.disbursal.account.is.not.active",
defaultUserMessage);
dataValidationErrors.add(error);
}
default:
break;
}
@@ -5550,6 +5575,76 @@ public class Loan extends AbstractPersistable<Long> {
trancheCharges.add(new LoanTrancheCharge(charge, this));
}
}
public Map<String, Object> undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List<Long> existingTransactionIds,
List<Long> existingReversedTransactionIds, AppUser currentUser, Loan loan) {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST);
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Map<String, Object> actualChanges = new LinkedHashMap<>();
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate());
LocalDate actualDisbursementDate = null;
LocalDate lastTransactionDate = getDisbursementDate();
List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals();
Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) {
if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
if(previousTransaction.isRepayment() || previousTransaction.isWaiver() ||
previousTransaction.isChargePayment()){
throw new UndoLastTrancheDisbursementException(previousTransaction.getId()); }
}
if(previousTransaction.isDisbursement()){
lastTransactionDate = previousTransaction.getTransactionDate();
break;
}
}
actualDisbursementDate = lastTransactionDate;
updateLoanToLastDisbursalState(actualDisbursementDate);
reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate);
loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO,
existingTransactionIds, existingReversedTransactionIds, currentUser);
actualChanges.put("undolastdisbursal","true");
actualChanges.put("disbursedAmount",this.getDisbursedAmount());
updateLoanSummaryDerivedFields();
return actualChanges;
}
/**
* Reverse only disbursement, accruals, and repayments at disbursal transactions
* @param actualDisbursementDate
* @return
*/
public List<LoanTransaction> reverseExistingTransactionsTillLastDisbursal(LocalDate actualDisbursementDate) {
final List<LoanTransaction> reversedTransactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if((actualDisbursementDate.equals(transaction.getTransactionDate()) || actualDisbursementDate.isBefore(transaction.getTransactionDate()))
&& transaction.isAllowTypeTransactionAtTheTimeOfLastUndo()){
reversedTransactions.add(transaction);
transaction.reverse();
}
}
return reversedTransactions;
}
private void updateLoanToLastDisbursalState(LocalDate actualDisbursementDate) {
for (final LoanCharge charge : charges()) {
if (charge.isOverdueInstallmentCharge()) {
charge.setActive(false);
} else if (charge.isTrancheDisbursementCharge() && actualDisbursementDate.equals(new LocalDate
(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate()))) {
charge.resetToOriginal(loanCurrency());
}
}
for (final LoanDisbursementDetails details : this.disbursementDetails) {
if (actualDisbursementDate.equals(new LocalDate(details.actualDisbursementDate()))) {
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(details.principal()));
details.updateActualDisbursementDate(null);
}
}
updateLoanSummaryDerivedFields();
}
public Boolean getIsFloatingInterestRate() {
return this.isFloatingInterestRate;
@@ -5590,5 +5685,5 @@ public class Loan extends AbstractPersistable<Long> {
public void setExpectedFirstRepaymentOnDate(Date expectedFirstRepaymentOnDate) {
this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
}
}
@@ -961,4 +961,8 @@ public class LoanCharge extends AbstractPersistable<Long> {
public Loan getLoan() {
return this.loan;
}
public boolean isTrancheDisbursementCharge() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
}
@@ -18,6 +18,7 @@ public enum LoanEvent {
LOAN_RECOVERY_PAYMENT, //
LOAN_DISBURSED, //
LOAN_DISBURSAL_UNDO, //
LOAN_DISBURSAL_UNDO_LAST, //
LOAN_REPAYMENT_OR_WAIVER, //
REPAID_IN_FULL, //
WRITE_OFF_OUTSTANDING, //
@@ -713,5 +713,9 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
public Set<LoanTransactionToRepaymentScheduleMapping> getLoanTransactionToRepaymentScheduleMappings() {
return this.loanTransactionToRepaymentScheduleMappings;
}
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo(){
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement();
}
}
@@ -0,0 +1,18 @@
/**
* 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.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class UndoLastTrancheDisbursementException extends AbstractPlatformDomainRuleException{
public UndoLastTrancheDisbursementException(final Object... defaultUserMessageArgs) {
super("error.msg.cannot.undo.last.disbursal.after.repayments or waivers"," Cannot undo last disbursement after repayments or waivers.",
defaultUserMessageArgs);
}
}
@@ -0,0 +1,34 @@
/**
* 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.annotation.CommandType;
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
@CommandType(entity = "LOAN", action = "DISBURSALLASTUNDO")
public class UndoLastDisbursalLoanCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public UndoLastDisbursalLoanCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.undoLastLoanDisbursal(command.entityId(), command);
}
}
@@ -91,4 +91,6 @@ public interface LoanWritePlatformService {
void recalculateInterest(long loanId);
CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
}
@@ -3105,8 +3105,109 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
map.put(entityEvent, entity);
return map;
}
@Override
@Transactional
public CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command) {
final AppUser currentUser = getAppUserIfPresent();
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final LocalDate recalculateFromDate = loan.getLastRepaymentDate();
validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(loan);
checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_LASTDISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
ScheduleGeneratorDTO scheduleGeneratorDTO = getScheduleGeneratorDTO(loan, recalculateFromDate, applicationCurrency);
final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds,
existingReversedTransactionIds, currentUser, loan);
if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
String noteText = null;
if (command.hasParameter("note")) {
noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
}
boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_UNDO_LASTDISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loan.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
}
private ScheduleGeneratorDTO getScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFromDate,
final ApplicationCurrency applicationCurrency) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final LocalDate actualDisbursementDate = loan.getDisbursementDate();
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
actualDisbursementDate, loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
actualDisbursementDate.toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
}
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Long overduePenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
recalculateFromDate, overduePenaltyWaitPeriod, floatingRateDTO);
return scheduleGeneratorDTO;
}
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
if(!loan.isMultiDisburmentLoan()){
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
throw new LoanMultiDisbursementException(errorMessage);
}
Integer trancheDisbursedCount = 0;
for(LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()){
if(disbursementDetails.actualDisbursementDate() != null){
trancheDisbursedCount++;
}
}
if(trancheDisbursedCount <=1){
final String errorMessage = "tranches.should.be.disbursed.more.than.one.to.undo.last.disbursal";
throw new LoanMultiDisbursementException(errorMessage);
}
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
@@ -0,0 +1,3 @@
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSALLASTUNDO_LOAN', 'LOAN', 'DISBURSALLASTUNDO', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('transaction_loan', 'DISBURSALLASTUNDO_LOAN_CHECKER', 'LOAN', 'DISBURSALLASTUNDO_CHECKER', 0);