mifosx-61: provide ability to close loans as reschedule or obligations met if loan is within defined tolerance.
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
alter table m_loan_transaction drop column interest_waived_derived;
|
||||||
@@ -187,7 +187,7 @@ public class LoansApiResource {
|
|||||||
}
|
}
|
||||||
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId);
|
final LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId);
|
||||||
|
|
||||||
int loanRepaymentsCount = 0;
|
int loanRepaymentsCount = 0;
|
||||||
Collection<LoanTransactionNewData> loanRepayments = null;
|
Collection<LoanTransactionNewData> loanRepayments = null;
|
||||||
@@ -223,7 +223,9 @@ public class LoansApiResource {
|
|||||||
MoneyData totalOutstandingMoney = MoneyData.of(loanBasicDetails.getCurrency(), repaymentSchedule.totalOutstanding());
|
MoneyData totalOutstandingMoney = MoneyData.of(loanBasicDetails.getCurrency(), repaymentSchedule.totalOutstanding());
|
||||||
boolean isWaiveAllowed = totalOutstandingMoney.isGreaterThanZero() && (tolerance.isGreaterThan(totalOutstandingMoney) || tolerance.isEqualTo(totalOutstandingMoney));
|
boolean isWaiveAllowed = totalOutstandingMoney.isGreaterThanZero() && (tolerance.isGreaterThan(totalOutstandingMoney) || tolerance.isEqualTo(totalOutstandingMoney));
|
||||||
|
|
||||||
permissions = this.loanReadPlatformService.retrieveLoanPermissions(loanBasicDetails, isWaiveAllowed, loanRepaymentsCount);
|
if (responseParameters.contains("permissions")) {
|
||||||
|
permissions = this.loanReadPlatformService.retrieveLoanPermissions(loanBasicDetails, isWaiveAllowed, loanRepaymentsCount);
|
||||||
|
}
|
||||||
convenienceDataRequired = true;
|
convenienceDataRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,8 +420,10 @@ public class LoansApiResource {
|
|||||||
identifier = this.loanWritePlatformService.waiveInterestOnLoan(command);
|
identifier = this.loanWritePlatformService.waiveInterestOnLoan(command);
|
||||||
} else if (is(commandParam, "writeoff")) {
|
} else if (is(commandParam, "writeoff")) {
|
||||||
identifier = this.loanWritePlatformService.writeOff(command);
|
identifier = this.loanWritePlatformService.writeOff(command);
|
||||||
} else if (is(commandParam, "closeasrescheduled")) {
|
} else if (is(commandParam, "close-rescheduled")) {
|
||||||
identifier = this.loanWritePlatformService.closeAsRescheduled(command);
|
identifier = this.loanWritePlatformService.closeAsRescheduled(command);
|
||||||
|
} else if (is(commandParam, "close")) {
|
||||||
|
identifier = this.loanWritePlatformService.closeLoan(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identifier == null) {
|
if (identifier == null) {
|
||||||
@@ -474,7 +478,7 @@ public class LoansApiResource {
|
|||||||
|
|
||||||
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
||||||
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
|
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
|
||||||
} else if (is(commandParam, "closeasrescheduled")) {
|
} else if (is(commandParam, "close-rescheduled")) {
|
||||||
final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "type", "date", "currency", "amount"));
|
final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "type", "date", "currency", "amount"));
|
||||||
|
|
||||||
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
@@ -485,7 +489,18 @@ public class LoansApiResource {
|
|||||||
|
|
||||||
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
||||||
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
|
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
|
||||||
} else {
|
} else if (is(commandParam, "close")) {
|
||||||
|
final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "type", "date", "currency", "amount"));
|
||||||
|
|
||||||
|
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
|
if (responseParameters.isEmpty()) {
|
||||||
|
responseParameters.addAll(typicalResponseParameters);
|
||||||
|
}
|
||||||
|
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
|
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
||||||
|
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
|
||||||
|
} else {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-48
@@ -1,7 +1,15 @@
|
|||||||
package org.mifosng.platform.api.data;
|
package org.mifosng.platform.api.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable data object containing information on permissions allowed on a given loan.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class LoanPermissionData {
|
public class LoanPermissionData {
|
||||||
|
|
||||||
|
// permission for actions on loan
|
||||||
|
private final boolean closeLoanAllowed;
|
||||||
|
private final boolean closeLoanAsRescheduledAllowed;
|
||||||
|
private final boolean addLoanChargeAllowed;
|
||||||
private final boolean waiveAllowed;
|
private final boolean waiveAllowed;
|
||||||
private final boolean makeRepaymentAllowed;
|
private final boolean makeRepaymentAllowed;
|
||||||
private final boolean rejectAllowed;
|
private final boolean rejectAllowed;
|
||||||
@@ -9,15 +17,30 @@ public class LoanPermissionData {
|
|||||||
private final boolean undoApprovalAllowed;
|
private final boolean undoApprovalAllowed;
|
||||||
private final boolean disbursalAllowed;
|
private final boolean disbursalAllowed;
|
||||||
private final boolean undoDisbursalAllowed;
|
private final boolean undoDisbursalAllowed;
|
||||||
|
|
||||||
private final boolean pendingApproval;
|
|
||||||
private final boolean waitingForDisbursal;
|
|
||||||
private final boolean anyActionOnLoanAllowed;
|
private final boolean anyActionOnLoanAllowed;
|
||||||
|
|
||||||
public LoanPermissionData(final boolean waiveAllowed,
|
// status of loan
|
||||||
final boolean makeRepaymentAllowed, final boolean rejectAllowed,
|
private final boolean pendingApproval;
|
||||||
final boolean withdrawnByApplicantAllowed, final boolean undoApprovalAllowed,
|
private final boolean waitingForDisbursal;
|
||||||
final boolean undoDisbursalAllowed, final boolean disbursalAllowed, final boolean pendingApproval, final boolean waitingForDisbursal) {
|
private final boolean closedObligationsMet;
|
||||||
|
|
||||||
|
public LoanPermissionData(
|
||||||
|
final boolean closeLoanAllowed,
|
||||||
|
final boolean closeLoanAsRescheduledAllowed,
|
||||||
|
final boolean addLoanChargeAllowed,
|
||||||
|
final boolean waiveAllowed,
|
||||||
|
final boolean makeRepaymentAllowed,
|
||||||
|
final boolean rejectAllowed,
|
||||||
|
final boolean withdrawnByApplicantAllowed,
|
||||||
|
final boolean undoApprovalAllowed,
|
||||||
|
final boolean undoDisbursalAllowed,
|
||||||
|
final boolean disbursalAllowed,
|
||||||
|
final boolean pendingApproval,
|
||||||
|
final boolean waitingForDisbursal,
|
||||||
|
final boolean closedObligationsMet) {
|
||||||
|
this.closeLoanAllowed = closeLoanAllowed;
|
||||||
|
this.closeLoanAsRescheduledAllowed = closeLoanAsRescheduledAllowed;
|
||||||
|
this.addLoanChargeAllowed = addLoanChargeAllowed;
|
||||||
this.waiveAllowed = waiveAllowed;
|
this.waiveAllowed = waiveAllowed;
|
||||||
this.makeRepaymentAllowed = makeRepaymentAllowed;
|
this.makeRepaymentAllowed = makeRepaymentAllowed;
|
||||||
this.rejectAllowed = rejectAllowed;
|
this.rejectAllowed = rejectAllowed;
|
||||||
@@ -27,46 +50,7 @@ public class LoanPermissionData {
|
|||||||
this.disbursalAllowed = disbursalAllowed;
|
this.disbursalAllowed = disbursalAllowed;
|
||||||
this.pendingApproval = pendingApproval;
|
this.pendingApproval = pendingApproval;
|
||||||
this.waitingForDisbursal = waitingForDisbursal;
|
this.waitingForDisbursal = waitingForDisbursal;
|
||||||
this.anyActionOnLoanAllowed = isRejectAllowed() || isWithdrawnByApplicantAllowed() || isPendingApproval() || isUndoDisbursalAllowed() || isMakeRepaymentAllowed();
|
this.closedObligationsMet = closedObligationsMet;
|
||||||
}
|
this.anyActionOnLoanAllowed = closeLoanAllowed || closeLoanAsRescheduledAllowed || rejectAllowed || withdrawnByApplicantAllowed || pendingApproval || undoDisbursalAllowed || makeRepaymentAllowed;
|
||||||
|
|
||||||
public boolean isWaiveAllowed() {
|
|
||||||
return waiveAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMakeRepaymentAllowed() {
|
|
||||||
return makeRepaymentAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRejectAllowed() {
|
|
||||||
return rejectAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWithdrawnByApplicantAllowed() {
|
|
||||||
return withdrawnByApplicantAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUndoApprovalAllowed() {
|
|
||||||
return undoApprovalAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDisbursalAllowed() {
|
|
||||||
return disbursalAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUndoDisbursalAllowed() {
|
|
||||||
return undoDisbursalAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPendingApproval() {
|
|
||||||
return pendingApproval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWaitingForDisbursal() {
|
|
||||||
return waitingForDisbursal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAnyActionOnLoanAllowed() {
|
|
||||||
return anyActionOnLoanAllowed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-1
@@ -11,7 +11,6 @@ import org.mifosng.platform.api.commands.DepositAccountWithdrawalCommand;
|
|||||||
import org.mifosng.platform.api.commands.DepositProductCommand;
|
import org.mifosng.platform.api.commands.DepositProductCommand;
|
||||||
import org.mifosng.platform.api.commands.DepositStateTransitionApprovalCommand;
|
import org.mifosng.platform.api.commands.DepositStateTransitionApprovalCommand;
|
||||||
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
|
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
|
||||||
import org.mifosng.platform.api.commands.DocumentCommand;
|
|
||||||
import org.mifosng.platform.api.commands.FundCommand;
|
import org.mifosng.platform.api.commands.FundCommand;
|
||||||
import org.mifosng.platform.api.commands.GroupCommand;
|
import org.mifosng.platform.api.commands.GroupCommand;
|
||||||
import org.mifosng.platform.api.commands.LoanApplicationCommand;
|
import org.mifosng.platform.api.commands.LoanApplicationCommand;
|
||||||
|
|||||||
+1
-2
@@ -88,7 +88,6 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
final MonetaryCurrency currency,
|
final MonetaryCurrency currency,
|
||||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
|
||||||
final Money interestWaivedPortion = Money.zero(currency);
|
|
||||||
Money principalPortion = Money.zero(currency);
|
Money principalPortion = Money.zero(currency);
|
||||||
Money interestPortion = Money.zero(currency);
|
Money interestPortion = Money.zero(currency);
|
||||||
Money chargesPortion = Money.zero(currency);
|
Money chargesPortion = Money.zero(currency);
|
||||||
@@ -102,7 +101,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, chargesPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// abstract interface
|
// abstract interface
|
||||||
|
|||||||
+6
-8
@@ -73,8 +73,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
||||||
|
|
||||||
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
final Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
loanTransaction.updateComponents(principalPortion, interestWaivedPortion, chargesPortion);
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments);
|
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments);
|
||||||
@@ -88,7 +87,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
|
|
||||||
Money principalPortion = Money.zero(currency);
|
Money principalPortion = Money.zero(currency);
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency()));
|
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(transactionAmountRemaining.getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
|
|
||||||
Money interestPortion = Money.zero(currency);
|
Money interestPortion = Money.zero(currency);
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency()));
|
loanTransaction.updateComponents(principalPortion, interestPortion, Money.zero(transactionAmountRemaining.getCurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,14 +129,13 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
|
|||||||
final Money transactionAmountUnprocessed) {
|
final Money transactionAmountUnprocessed) {
|
||||||
|
|
||||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
|
||||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
|
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||||
@@ -147,7 +145,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
return transactionAmountRemaining;
|
return transactionAmountRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -79,14 +79,13 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
|
|||||||
final Money transactionAmountUnprocessed) {
|
final Money transactionAmountUnprocessed) {
|
||||||
|
|
||||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
|
||||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
|
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||||
@@ -96,7 +95,7 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
return transactionAmountRemaining;
|
return transactionAmountRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -11,6 +11,7 @@ import org.mifosng.platform.currency.domain.Money;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class DailyEquivalentDecliningBalanceInterestRebateCalculator implements
|
public class DailyEquivalentDecliningBalanceInterestRebateCalculator implements
|
||||||
InterestRebateCalculator {
|
InterestRebateCalculator {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,8 +9,8 @@ import org.joda.time.LocalDate;
|
|||||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||||
import org.mifosng.platform.currency.domain.Money;
|
import org.mifosng.platform.currency.domain.Money;
|
||||||
|
|
||||||
// FIXME - KW - this might be deprecated now
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
@Deprecated
|
||||||
public class DailyEquivalentFlatInterestRebateCalculator implements
|
public class DailyEquivalentFlatInterestRebateCalculator implements
|
||||||
InterestRebateCalculator {
|
InterestRebateCalculator {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REPAID_IN_FULL:
|
case REPAID_IN_FULL:
|
||||||
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
|
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.OVERPAID)) {
|
||||||
newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, allowedLoanStatuses);
|
newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, allowedLoanStatuses);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
+9
-10
@@ -68,23 +68,23 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
|||||||
|
|
||||||
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
|
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
|
||||||
Money transactionAmountRemaining = paymentInAdvance;
|
Money transactionAmountRemaining = paymentInAdvance;
|
||||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
|
||||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
|
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (currentInstallment.isPrincipalNotCompleted(currency)) {
|
if (currentInstallment.isPrincipalNotCompleted(currency)) {
|
||||||
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
|
||||||
if (currentInstallment.isPrincipalCompleted(currency)) {
|
if (currentInstallment.isPrincipalCompleted(currency)) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(currentInstallment.getInterest(currency));
|
// FIXME - KW - if auto waiving interest need to create another transaction to handle this.
|
||||||
|
currentInstallment.waiveInterestComponent(currentInstallment.getInterest(currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
|
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
return transactionAmountRemaining;
|
return transactionAmountRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +111,13 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
|||||||
|
|
||||||
|
|
||||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
|
||||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
|
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||||
@@ -128,7 +127,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
return transactionAmountRemaining;
|
return transactionAmountRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -6,6 +6,7 @@ import java.util.List;
|
|||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.mifosng.platform.currency.domain.Money;
|
import org.mifosng.platform.currency.domain.Money;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public interface InterestRebateCalculator {
|
public interface InterestRebateCalculator {
|
||||||
|
|
||||||
Money calculate(
|
Money calculate(
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import org.apache.commons.lang.ObjectUtils;
|
|||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.hibernate.annotations.LazyCollection;
|
import org.hibernate.annotations.LazyCollection;
|
||||||
import org.hibernate.annotations.LazyCollectionOption;
|
import org.hibernate.annotations.LazyCollectionOption;
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.mifosng.platform.api.LoanScheduleData;
|
import org.mifosng.platform.api.LoanScheduleData;
|
||||||
import org.mifosng.platform.api.commands.LoanApplicationCommand;
|
import org.mifosng.platform.api.commands.LoanApplicationCommand;
|
||||||
@@ -533,7 +532,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
|
|
||||||
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn);
|
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn);
|
||||||
Money zero = Money.zero(getCurrency());
|
Money zero = Money.zero(getCurrency());
|
||||||
chargesPayment.updateComponents(zero, zero, zero, getTotalChargesDueAtDisbursement());
|
chargesPayment.updateComponents(zero, zero, getTotalChargesDueAtDisbursement());
|
||||||
chargesPayment.updateLoan(this);
|
chargesPayment.updateLoan(this);
|
||||||
this.loanTransactions.add(chargesPayment);
|
this.loanTransactions.add(chargesPayment);
|
||||||
|
|
||||||
@@ -871,10 +870,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOverPaid() {
|
private boolean isOverPaid() {
|
||||||
|
return getTotalOverpayment().isGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
Money totalPaidInRepayments = getTotalPaidInRepayments();
|
private Money getTotalOverpayment() {
|
||||||
|
|
||||||
MonetaryCurrency currency = loanCurrency();
|
final Money totalPaidInRepayments = getTotalPaidInRepayments();
|
||||||
|
|
||||||
|
final MonetaryCurrency currency = loanCurrency();
|
||||||
|
|
||||||
Money cumulativeOriginalExpectedTotal = Money.zero(currency);
|
Money cumulativeOriginalExpectedTotal = Money.zero(currency);
|
||||||
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
||||||
@@ -886,7 +889,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
|
cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalPaidInRepayments.isGreaterThan(cumulativeTotalPaidOnInstallments);
|
return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MonetaryCurrency loanCurrency() {
|
private MonetaryCurrency loanCurrency() {
|
||||||
@@ -933,12 +936,75 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return loanTransaction;
|
return loanTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - KW - finish off reschedule/restructure capability for loans.
|
public LoanTransaction close(
|
||||||
public void reschedule(
|
final LocalDate closureDate,
|
||||||
final DateTime rescheduledOn,
|
|
||||||
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
|
|
||||||
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus));
|
if (closureDate.isBefore(this.getDisbursementDate())) {
|
||||||
|
final String errorMessage = "The date on which a loan is closed cannot be before the loan disbursement date: " + getDisbursementDate().toString();
|
||||||
|
throw new InvalidLoanStateTransitionException("close","cannot.be.before.submittal.date", errorMessage, closureDate, getDisbursementDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closureDate.isAfter(new LocalDate())) {
|
||||||
|
final String errorMessage = "The date on which a loan is closed cannot be in the future.";
|
||||||
|
throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoanTransaction loanTransaction = null;
|
||||||
|
if (isOpen()) {
|
||||||
|
// 1. check total outstanding
|
||||||
|
final Money outstanding = getTotalOutstanding();
|
||||||
|
if (outstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(outstanding)) {
|
||||||
|
|
||||||
|
updateLoanForClosure(closureDate, loanLifecycleStateMachine);
|
||||||
|
|
||||||
|
loanTransaction = LoanTransaction.writeoff(this, closureDate);
|
||||||
|
boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions);
|
||||||
|
if (!isLastTransaction) {
|
||||||
|
final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
|
||||||
|
throw new InvalidLoanStateTransitionException("close.loan","must.occur.on.or.after.latest.transaction.date", errorMessage, closureDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loanTransactions.add(loanTransaction);
|
||||||
|
|
||||||
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
|
||||||
|
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
|
||||||
|
|
||||||
|
} else if (outstanding.isGreaterThanZero()) {
|
||||||
|
final String errorMessage = "A loan with money outstanding cannot be closed";
|
||||||
|
throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage, outstanding.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOverPaid()) {
|
||||||
|
final Money totalLoanOverpayment = getTotalOverpayment();
|
||||||
|
// FIXME - kw - use overpaymentTolerance setting when in place on loan product settings.
|
||||||
|
if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
|
||||||
|
// TODO - technically should set somewhere that this loan has 'overpaid' amount
|
||||||
|
updateLoanForClosure(closureDate, loanLifecycleStateMachine);
|
||||||
|
} else if (totalLoanOverpayment.isGreaterThanZero()) {
|
||||||
|
final String errorMessage = "The loan is marked as 'Overpaid' and cannot be moved to 'Closed (obligations met).";
|
||||||
|
throw new InvalidLoanStateTransitionException("close", "loan.is.overpaid", errorMessage, totalLoanOverpayment.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loanTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLoanForClosure(final LocalDate closureDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
|
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, LoanStatus.fromInt(this.loanStatus));
|
||||||
|
this.loanStatus = statusEnum.getValue();
|
||||||
|
this.closedOnDate = closureDate.toDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behaviour added to comply with capability of previous mifos product to support easier transition to mifosx platform.
|
||||||
|
*/
|
||||||
|
public void closeAsMarkedForReschedule(
|
||||||
|
final LocalDate rescheduledOn,
|
||||||
|
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
|
|
||||||
|
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus));
|
||||||
this.loanStatus = statusEnum.getValue();
|
this.loanStatus = statusEnum.getValue();
|
||||||
|
|
||||||
this.closedOnDate = rescheduledOn.toDate();
|
this.closedOnDate = rescheduledOn.toDate();
|
||||||
@@ -948,15 +1014,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
if (rescheduledOnLocalDate.isBefore(this.getDisbursementDate())) {
|
if (rescheduledOnLocalDate.isBefore(this.getDisbursementDate())) {
|
||||||
final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: "
|
final String errorMessage = "The date on which a loan is rescheduled cannot be before the loan disbursement date: "
|
||||||
+ getDisbursementDate().toString();
|
+ getDisbursementDate().toString();
|
||||||
throw new InvalidLoanStateTransitionException("writeoff",
|
throw new InvalidLoanStateTransitionException("close.reschedule","cannot.be.before.submittal.date", errorMessage, rescheduledOnLocalDate, getDisbursementDate());
|
||||||
"cannot.be.before.submittal.date", errorMessage,
|
|
||||||
rescheduledOnLocalDate, getDisbursementDate());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rescheduledOnLocalDate.isAfter(new LocalDate())) {
|
if (rescheduledOnLocalDate.isAfter(new LocalDate())) {
|
||||||
final String errorMessage = "The date on which a loan is rescheduled cannot be in the future.";
|
final String errorMessage = "The date on which a loan is rescheduled cannot be in the future.";
|
||||||
throw new InvalidLoanStateTransitionException("writeoff",
|
throw new InvalidLoanStateTransitionException("close.reschedule", "cannot.be.a.future.date", errorMessage, rescheduledOnLocalDate);
|
||||||
"cannot.be.a.future.date", errorMessage,
|
|
||||||
rescheduledOnLocalDate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,6 +1282,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return regenerationRequired;
|
return regenerationRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public LoanPayoffSummary getPayoffSummaryOn(
|
public LoanPayoffSummary getPayoffSummaryOn(
|
||||||
final LocalDate projectedPayoffDate) {
|
final LocalDate projectedPayoffDate) {
|
||||||
|
|
||||||
@@ -1246,6 +1310,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
rebateGivenOnProjectedPayoffDate);
|
rebateGivenOnProjectedPayoffDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME - KW - change what outstanding balance on loan can be accessed. This total outstanding method is wrong and
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public Money getTotalOutstanding() {
|
public Money getTotalOutstanding() {
|
||||||
return getTotalPrincipalOnLoan().plus(getTotalInterestOnLoan()).minus(getTotalPaidInRepayments());
|
return getTotalPrincipalOnLoan().plus(getTotalInterestOnLoan()).minus(getTotalPaidInRepayments());
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1330,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return cumulativePaid;
|
return cumulativePaid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Money calculateRebateWhenPaidInFullOn(final LocalDate paidInFullDate) {
|
public Money calculateRebateWhenPaidInFullOn(final LocalDate paidInFullDate) {
|
||||||
|
|
||||||
Money loanPrincipal = this.loanRepaymentScheduleDetail.getPrincipal();
|
Money loanPrincipal = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||||
@@ -1290,8 +1359,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
Money cumulativeInterest = Money.zero(loanCurrency());
|
Money cumulativeInterest = Money.zero(loanCurrency());
|
||||||
|
|
||||||
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
|
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
|
||||||
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment
|
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterest(loanCurrency()));
|
||||||
.getInterest(loanCurrency()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cumulativeInterest;
|
return cumulativeInterest;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.joda.time.Days;
|
|||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.mifosng.platform.currency.domain.Money;
|
import org.mifosng.platform.currency.domain.Money;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class LoanPayoffSummary {
|
public class LoanPayoffSummary {
|
||||||
|
|
||||||
private final Long reference;
|
private final Long reference;
|
||||||
|
|||||||
+3
-15
@@ -43,9 +43,6 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = false)
|
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = false)
|
||||||
private BigDecimal interestPortion = BigDecimal.ZERO;
|
private BigDecimal interestPortion = BigDecimal.ZERO;
|
||||||
|
|
||||||
@Column(name = "interest_waived_derived", scale = 6, precision = 19)
|
|
||||||
private BigDecimal interestWaivedPortion = BigDecimal.ZERO;
|
|
||||||
|
|
||||||
@Column(name = "charges_portion_derived", scale = 6, precision = 19, nullable = false)
|
@Column(name = "charges_portion_derived", scale = 6, precision = 19, nullable = false)
|
||||||
private BigDecimal chargesPortion = BigDecimal.ZERO;
|
private BigDecimal chargesPortion = BigDecimal.ZERO;
|
||||||
|
|
||||||
@@ -187,16 +184,15 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
*
|
*
|
||||||
* This accumulates the values passed to the already existent values for each of the portions.
|
* This accumulates the values passed to the already existent values for each of the portions.
|
||||||
*/
|
*/
|
||||||
public void updateComponents(final Money principal, final Money interest, final Money interestWaivedPortion, final Money charges) {
|
public void updateComponents(final Money principal, final Money interest, final Money charges) {
|
||||||
MonetaryCurrency currency = principal.getCurrency();
|
MonetaryCurrency currency = principal.getCurrency();
|
||||||
this.principalPortion = getPrincipalPortion(currency).plus(principal).getAmount();
|
this.principalPortion = getPrincipalPortion(currency).plus(principal).getAmount();
|
||||||
this.interestPortion = getInterestPortion(currency).plus(interest).getAmount();
|
this.interestPortion = getInterestPortion(currency).plus(interest).getAmount();
|
||||||
this.interestWaivedPortion = getInterestWaivedPortion(currency).plus(interestWaivedPortion).getAmount();
|
|
||||||
this.chargesPortion = getChargesPortion(currency).plus(charges).getAmount();
|
this.chargesPortion = getChargesPortion(currency).plus(charges).getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money interestWaivedPortion, final Money charges) {
|
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money charges) {
|
||||||
updateComponents(principal, interest, interestWaivedPortion, charges);
|
updateComponents(principal, interest, charges);
|
||||||
|
|
||||||
final MonetaryCurrency currency = principal.getCurrency();
|
final MonetaryCurrency currency = principal.getCurrency();
|
||||||
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(this.chargesPortion).getAmount();
|
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(this.chargesPortion).getAmount();
|
||||||
@@ -210,13 +206,6 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return Money.of(currency, interestPortion);
|
return Money.of(currency, interestPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getInterestWaivedPortion(final MonetaryCurrency currency) {
|
|
||||||
if (this.interestWaivedPortion == null) {
|
|
||||||
this.interestWaivedPortion = BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
return Money.of(currency, interestWaivedPortion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Money getChargesPortion(final MonetaryCurrency currency) {
|
public Money getChargesPortion(final MonetaryCurrency currency) {
|
||||||
if (this.chargesPortion == null) {
|
if (this.chargesPortion == null) {
|
||||||
this.chargesPortion = BigDecimal.ZERO;
|
this.chargesPortion = BigDecimal.ZERO;
|
||||||
@@ -227,7 +216,6 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
public void resetDerivedComponents() {
|
public void resetDerivedComponents() {
|
||||||
this.principalPortion = null;
|
this.principalPortion = null;
|
||||||
this.interestPortion = null;
|
this.interestPortion = null;
|
||||||
this.interestWaivedPortion = null;
|
|
||||||
this.chargesPortion = null;
|
this.chargesPortion = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-4
@@ -70,14 +70,13 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
|
|||||||
final Money transactionAmountUnprocessed) {
|
final Money transactionAmountUnprocessed) {
|
||||||
|
|
||||||
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
Money transactionAmountRemaining = transactionAmountUnprocessed;
|
||||||
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
|
||||||
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||||
|
|
||||||
if (loanTransaction.isInterestWaiver()) {
|
if (loanTransaction.isInterestWaiver()) {
|
||||||
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
|
||||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
|
||||||
@@ -87,7 +86,7 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
|
|||||||
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
|
loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion);
|
||||||
return transactionAmountRemaining;
|
return transactionAmountRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -77,6 +77,10 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
|
|||||||
command.getInterestChargedFromDate(), loanCharges);
|
command.getInterestChargedFromDate(), loanCharges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated functionality from early prototype work with creocore
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public LoanPayoffReadModel calculatePayoffOn(final Long loanId, final LocalDate payoffDate) {
|
public LoanPayoffReadModel calculatePayoffOn(final Long loanId, final LocalDate payoffDate) {
|
||||||
|
|
||||||
|
|||||||
+18
-12
@@ -185,28 +185,34 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
|||||||
final boolean isWaiverAllowed,
|
final boolean isWaiverAllowed,
|
||||||
final int repaymentAndWaiveCount) {
|
final int repaymentAndWaiveCount) {
|
||||||
|
|
||||||
boolean pendingApproval = (loanBasicDetails.getStatus().getId().equals(100L));
|
final boolean pendingApproval = (loanBasicDetails.getStatus().getId().equals(100L));
|
||||||
boolean waitingForDisbursal = (loanBasicDetails.getStatus().getId().equals(200L));
|
final boolean waitingForDisbursal = (loanBasicDetails.getStatus().getId().equals(200L));
|
||||||
boolean isActive = (loanBasicDetails.getStatus().getId().equals(300L));
|
final boolean isActive = (loanBasicDetails.getStatus().getId().equals(300L));
|
||||||
|
final boolean closedObligationsMet = (loanBasicDetails.getStatus().getId().equals(600L));
|
||||||
|
final boolean isOverpaid = (loanBasicDetails.getStatus().getId().equals(700L));
|
||||||
|
final boolean addLoanChargeAllowed = !closedObligationsMet || !isOverpaid;
|
||||||
|
|
||||||
boolean waiveAllowed = isWaiverAllowed && isActive;
|
final boolean waiveAllowed = isWaiverAllowed && isActive;
|
||||||
boolean makeRepaymentAllowed = isActive;
|
final boolean makeRepaymentAllowed = isActive;
|
||||||
|
final boolean closeLoanAllowed = !closedObligationsMet;
|
||||||
|
final boolean closeLoanAsRescheduledAllowed = !closedObligationsMet && !isOverpaid;
|
||||||
|
|
||||||
boolean rejectAllowed = pendingApproval;
|
final boolean rejectAllowed = pendingApproval;
|
||||||
boolean withdrawnByApplicantAllowed = waitingForDisbursal
|
final boolean withdrawnByApplicantAllowed = waitingForDisbursal
|
||||||
|| pendingApproval;
|
|| pendingApproval;
|
||||||
|
|
||||||
boolean undoApprovalAllowed = waitingForDisbursal;
|
final boolean undoApprovalAllowed = waitingForDisbursal;
|
||||||
|
|
||||||
boolean undoDisbursalAllowed = isActive
|
final boolean undoDisbursalAllowed = isActive
|
||||||
&& (repaymentAndWaiveCount == 0);
|
&& (repaymentAndWaiveCount == 0);
|
||||||
|
|
||||||
boolean disbursalAllowed = waitingForDisbursal;
|
final boolean disbursalAllowed = waitingForDisbursal;
|
||||||
|
|
||||||
return new LoanPermissionData(waiveAllowed, makeRepaymentAllowed,
|
return new LoanPermissionData(closeLoanAllowed, closeLoanAsRescheduledAllowed,
|
||||||
|
addLoanChargeAllowed, waiveAllowed, makeRepaymentAllowed,
|
||||||
rejectAllowed, withdrawnByApplicantAllowed,
|
rejectAllowed, withdrawnByApplicantAllowed,
|
||||||
undoApprovalAllowed, undoDisbursalAllowed, disbursalAllowed,
|
undoApprovalAllowed, undoDisbursalAllowed, disbursalAllowed,
|
||||||
pendingApproval, waitingForDisbursal);
|
pendingApproval, waitingForDisbursal, closedObligationsMet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+3
@@ -50,6 +50,9 @@ public interface LoanWritePlatformService {
|
|||||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
|
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
|
||||||
EntityIdentifier writeOff(LoanTransactionCommand command);
|
EntityIdentifier writeOff(LoanTransactionCommand command);
|
||||||
|
|
||||||
|
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
|
||||||
|
EntityIdentifier closeLoan(LoanTransactionCommand command);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
|
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
|
||||||
EntityIdentifier closeAsRescheduled(LoanTransactionCommand command);
|
EntityIdentifier closeAsRescheduled(LoanTransactionCommand command);
|
||||||
|
|
||||||
|
|||||||
+31
-4
@@ -568,6 +568,35 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
return new EntityIdentifier(loan.getId());
|
return new EntityIdentifier(loan.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public EntityIdentifier closeLoan(final LoanTransactionCommand command) {
|
||||||
|
|
||||||
|
context.authenticatedUser();
|
||||||
|
|
||||||
|
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
|
||||||
|
validator.validateNonMonetaryTransaction();
|
||||||
|
|
||||||
|
final Loan loan = this.loanRepository.findOne(command.getLoanId());
|
||||||
|
if (loan == null) {
|
||||||
|
throw new LoanNotFoundException(command.getLoanId());
|
||||||
|
}
|
||||||
|
|
||||||
|
final LoanTransaction possibleClosingTransaction = loan.close(command.getTransactionDate(), defaultLoanLifecycleStateMachine());
|
||||||
|
if (possibleClosingTransaction != null) {
|
||||||
|
this.loanTransactionRepository.save(possibleClosingTransaction);
|
||||||
|
}
|
||||||
|
this.loanRepository.save(loan);
|
||||||
|
|
||||||
|
final String noteText = command.getNote();
|
||||||
|
if (StringUtils.isNotBlank(noteText)) {
|
||||||
|
final Note note = Note.loanNote(loan, noteText);
|
||||||
|
this.noteRepository.save(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EntityIdentifier(loan.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) {
|
public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) {
|
||||||
@@ -581,15 +610,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
throw new LoanNotFoundException(command.getLoanId());
|
throw new LoanNotFoundException(command.getLoanId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - kw - HANDLE closed and rescheduled case.
|
loan.closeAsMarkedForReschedule(command.getTransactionDate(), defaultLoanLifecycleStateMachine());
|
||||||
final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine());
|
|
||||||
|
|
||||||
this.loanTransactionRepository.save(writeoff);
|
|
||||||
this.loanRepository.save(loan);
|
this.loanRepository.save(loan);
|
||||||
|
|
||||||
final String noteText = command.getNote();
|
final String noteText = command.getNote();
|
||||||
if (StringUtils.isNotBlank(noteText)) {
|
if (StringUtils.isNotBlank(noteText)) {
|
||||||
final Note note = Note.loanTransactionNote(loan, writeoff, noteText);
|
final Note note = Note.loanNote(loan, noteText);
|
||||||
this.noteRepository.save(note);
|
this.noteRepository.save(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-7
@@ -49,7 +49,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
|
LoanRepaymentScheduleInstallment firstInstallment = this.installments.get(0);
|
||||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
|
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), firstInstallment, usDollars);
|
||||||
|
|
||||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), interestWaivedOf("0.00"), onTimePartialRepayment, usDollars);
|
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("100.00"), onTimePartialRepayment, usDollars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +77,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
||||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
||||||
|
|
||||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("300.00"), interestWaivedOf("0.00"), lateRepayment, usDollars);
|
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("0.00"), interestOf("300.00"), lateRepayment, usDollars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,13 +105,12 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
||||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("0.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
||||||
|
|
||||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("500.00"), interestOf("400.00"), interestWaivedOf("0.00"), lateRepayment, usDollars);
|
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("500.00"), interestOf("400.00"), lateRepayment, usDollars);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatLoanTransactionHasDerivedFieldsOf(
|
private void assertThatLoanTransactionHasDerivedFieldsOf(
|
||||||
final String principal,
|
final String principal,
|
||||||
final String interest,
|
final String interest,
|
||||||
final String interestWaived,
|
|
||||||
final LoanTransaction transaction, final MonetaryCurrency currency) {
|
final LoanTransaction transaction, final MonetaryCurrency currency) {
|
||||||
|
|
||||||
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
|
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
|
||||||
@@ -119,9 +118,6 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
|
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
|
||||||
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
|
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
|
||||||
|
|
||||||
Money expectedInterestWaivedPortion = new MoneyBuilder().with(currency).with(interestWaived).build();
|
|
||||||
assertThat("Interest waived portion of transaction not as expected: ",transaction.getInterestWaivedPortion(currency).toString(), is(expectedInterestWaivedPortion.toString()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatLoanInstallmentHasDerivedFieldsOf(
|
private void assertThatLoanInstallmentHasDerivedFieldsOf(
|
||||||
|
|||||||
+2
-6
@@ -278,7 +278,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
||||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("800.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("800.00"), interestOf("0.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
||||||
|
|
||||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("2800.00"), interestOf("200.00"), interestWaivedOf("200.00"), onTimePartialPayment, usDollars);
|
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("2800.00"), interestOf("200.00"), onTimePartialPayment, usDollars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -353,7 +353,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
LoanRepaymentScheduleInstallment thirdInstallment = this.installments.get(2);
|
||||||
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
assertThatLoanInstallmentHasDerivedFieldsOf(principalOf("1000.00"), interestOf("200.00"), interestWaivedOf("0.00"), thirdInstallment, usDollars);
|
||||||
|
|
||||||
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"), interestOf("600.00"), interestWaivedOf("0.00"), lateRepaymentTransaction, usDollars);
|
assertThatLoanTransactionHasDerivedFieldsOf(principalOf("3000.00"), interestOf("600.00"), lateRepaymentTransaction, usDollars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -379,7 +379,6 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
private void assertThatLoanTransactionHasDerivedFieldsOf(
|
private void assertThatLoanTransactionHasDerivedFieldsOf(
|
||||||
final String principal,
|
final String principal,
|
||||||
final String interest,
|
final String interest,
|
||||||
final String interestWaived,
|
|
||||||
final LoanTransaction transaction, final MonetaryCurrency currency) {
|
final LoanTransaction transaction, final MonetaryCurrency currency) {
|
||||||
|
|
||||||
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
|
Money expectedPrincipalPortion = new MoneyBuilder().with(currency).with(principal).build();
|
||||||
@@ -387,9 +386,6 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
|
Money expectedInterestPortion = new MoneyBuilder().with(currency).with(interest).build();
|
||||||
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
|
assertThat("Interest portion of transaction not as expected: ", transaction.getInterestPortion(currency).toString(), is(expectedInterestPortion.toString()));
|
||||||
|
|
||||||
Money expectedInterestWaivedPortion = new MoneyBuilder().with(currency).with(interestWaived).build();
|
|
||||||
assertThat("Interest waived portion of transaction not as expected: ",transaction.getInterestWaivedPortion(currency).toString(), is(expectedInterestWaivedPortion.toString()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatLoanInstallmentHasDerivedFieldsOf(
|
private void assertThatLoanInstallmentHasDerivedFieldsOf(
|
||||||
|
|||||||
Reference in New Issue
Block a user