Merge pull request #1583 from sachinkulkarni12/CC-32New
CC-32: undo last tranche disbursal feature
This commit is contained in:
+2
-2
@@ -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()
|
||||
|
||||
+145
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+15
@@ -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);
|
||||
|
||||
+9
@@ -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";
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+3
@@ -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")) {
|
||||
|
||||
+96
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
@@ -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, //
|
||||
|
||||
+4
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
@@ -91,4 +91,6 @@ public interface LoanWritePlatformService {
|
||||
|
||||
void recalculateInterest(long loanId);
|
||||
|
||||
CommandProcessingResult undoLastLoanDisbursal(Long loanId, JsonCommand command);
|
||||
|
||||
}
|
||||
+102
-1
@@ -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();
|
||||
|
||||
+3
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user