creocore: change waive functionality to support waive of interest only.

This commit is contained in:
Keith Woodlock
2012-10-04 16:08:31 +01:00
parent cf865a28d9
commit 0a22ab3e41
18 changed files with 377 additions and 235 deletions
@@ -40,6 +40,7 @@ import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductLookup; import org.mifosng.platform.api.data.LoanProductLookup;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData; import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.StaffData; import org.mifosng.platform.api.data.StaffData;
import org.mifosng.platform.api.data.TransactionProcessingStrategyData; import org.mifosng.platform.api.data.TransactionProcessingStrategyData;
@@ -414,9 +415,9 @@ public class LoansApiResource {
EntityIdentifier identifier = this.loanWritePlatformService EntityIdentifier identifier = this.loanWritePlatformService
.makeLoanRepayment(command); .makeLoanRepayment(command);
response = Response.ok().entity(identifier).build(); response = Response.ok().entity(identifier).build();
} else if (is(commandParam, "waiver")) { } else if (is(commandParam, "waiveinterest")) {
EntityIdentifier identifier = this.loanWritePlatformService EntityIdentifier identifier = this.loanWritePlatformService
.waiveLoanAmount(command); .waiveInterestOnLoan(command);
response = Response.ok().entity(identifier).build(); response = Response.ok().entity(identifier).build();
} }
@@ -436,26 +437,34 @@ public class LoansApiResource {
@QueryParam("command") final String commandParam, @QueryParam("command") final String commandParam,
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
Set<String> typicalResponseParameters = new HashSet<String>(
Arrays.asList("id", "transactionType", "date", "principal", "interest", "total", "totalWaived", "overpaid")
);
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
LoanTransactionData transactionData = null; LoanTransactionData transactionData = null;
if (is(commandParam, "repayment")) { if (is(commandParam, "repayment")) {
Set<String> typicalResponseParameters = new HashSet<String>(
Arrays.asList("id", "transactionType", "date", "principal", "interest", "total", "totalWaived", "overpaid")
);
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
transactionData = this.loanReadPlatformService.retrieveNewLoanRepaymentDetails(loanId); transactionData = this.loanReadPlatformService.retrieveNewLoanRepaymentDetails(loanId);
} else if (is(commandParam, "waiver")) { return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, transactionData);
transactionData = this.loanReadPlatformService.retrieveNewLoanWaiverDetails(loanId); } else if (is(commandParam, "waiveinterest")) {
final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "type", "date", "currency", "amount"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewLoanWaiveInterestDetails(loanId);
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
} else { } else {
throw new UnrecognizedQueryParamException("command", commandParam); throw new UnrecognizedQueryParamException("command", commandParam);
} }
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, transactionData);
} }
@GET @GET
@@ -0,0 +1,37 @@
package org.mifosng.platform.api.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
/**
* Immutable data object representing a loan transaction.
*/
public class LoanTransactionNewData {
@SuppressWarnings("unused")
private Long id;
@SuppressWarnings("unused")
private EnumOptionData type;
@SuppressWarnings("unused")
private LocalDate date;
@SuppressWarnings("unused")
private CurrencyData currency;
@SuppressWarnings("unused")
private BigDecimal amount;
public LoanTransactionNewData(
final EnumOptionData transactionsType,
final CurrencyData currency,
final LocalDate date,
final BigDecimal amount) {
this.type = transactionsType;
this.currency = currency;
this.date = date;
this.amount = amount;
}
}
@@ -23,6 +23,7 @@ import org.mifosng.platform.api.data.LoanAccountData;
import org.mifosng.platform.api.data.LoanChargeData; import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanProductData; import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
import org.mifosng.platform.api.data.NoteData; import org.mifosng.platform.api.data.NoteData;
import org.mifosng.platform.api.data.OfficeData; import org.mifosng.platform.api.data.OfficeData;
import org.mifosng.platform.api.data.OfficeTransactionData; import org.mifosng.platform.api.data.OfficeTransactionData;
@@ -102,6 +103,8 @@ public interface ApiJsonSerializerService {
String serializeLoanAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanAccountData loanAccount); String serializeLoanAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanAccountData loanAccount);
String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData transaction); String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionData transaction);
String serializeLoanTransactionDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanTransactionNewData newTransactionData);
String serializeChargeDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ChargeData> charges); String serializeChargeDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<ChargeData> charges);
@@ -25,6 +25,7 @@ import org.mifosng.platform.api.data.LoanAccountData;
import org.mifosng.platform.api.data.LoanChargeData; import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanProductData; import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
import org.mifosng.platform.api.data.NoteData; import org.mifosng.platform.api.data.NoteData;
import org.mifosng.platform.api.data.OfficeData; import org.mifosng.platform.api.data.OfficeData;
import org.mifosng.platform.api.data.OfficeTransactionData; import org.mifosng.platform.api.data.OfficeTransactionData;
@@ -174,6 +175,9 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ
private static final Set<String> LOAN_TRANSACTION_DATA_PARAMETERS = new HashSet<String>( private static final Set<String> LOAN_TRANSACTION_DATA_PARAMETERS = new HashSet<String>(
Arrays.asList("id", "transactionType", "date", "principal", Arrays.asList("id", "transactionType", "date", "principal",
"interest", "total", "totalWaived", "overpaid")); "interest", "total", "totalWaived", "overpaid"));
private static final Set<String> LOAN_TRANSACTION_NEW_DATA_PARAMETERS = new HashSet<String>(
Arrays.asList("id", "type", "date", "currency", "amount"));
private static final Set<String> CHARGES_DATA_PARAMETERS = new HashSet<String>( private static final Set<String> CHARGES_DATA_PARAMETERS = new HashSet<String>(
Arrays.asList("id", "name", "amount", "currency", "active", Arrays.asList("id", "name", "amount", "currency", "active",
@@ -552,6 +556,17 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ
responseParameters); responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, transaction); return helper.serializedJsonFrom(gsonDeserializer, transaction);
} }
@Override
public String serializeLoanTransactionDataToJson(final boolean prettyPrint,
final Set<String> responseParameters,
final LoanTransactionNewData transaction) {
final Gson gsonDeserializer = helper
.createGsonBuilderWithParameterExclusionSerializationStrategy(
LOAN_TRANSACTION_NEW_DATA_PARAMETERS, prettyPrint,
responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, transaction);
}
@Override @Override
public String serializeChargeDataToJson(final boolean prettyPrint, public String serializeChargeDataToJson(final boolean prettyPrint,
@@ -55,7 +55,6 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
* For late repayments, pay off in the same way as on-time payments, * For late repayments, pay off in the same way as on-time payments,
* interest first then principal. * interest first then principal.
*/ */
@SuppressWarnings("unused")
@Override @Override
protected Money handleTransactionThatIsALateRepaymentOfInstallment( protected Money handleTransactionThatIsALateRepaymentOfInstallment(
final LoanRepaymentScheduleInstallment currentInstallment, final LoanRepaymentScheduleInstallment currentInstallment,
@@ -64,37 +63,47 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
// pay of overdue and current interest due given transaction date // pay of overdue and current interest due given transaction date
MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments); if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
final Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
final Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
} else {
for (LoanRepaymentScheduleInstallment installment : installments) { LoanRepaymentScheduleInstallment currentInstallmentBasedOnTransactionDate = nearestInstallment(loanTransaction.getTransactionDate(), installments);
if (
installment.isInterestDue(currency) && for (LoanRepaymentScheduleInstallment installment : installments) {
(installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(currentInstallmentBasedOnTransactionDate.getInstallmentNumber())) if (
) { installment.isInterestDue(currency) &&
Money interestPortion = installment.payInterestComponent(transactionAmountRemaining); (installment.isOverdueOn(loanTransaction.getTransactionDate()) || installment.getInstallmentNumber().equals(currentInstallmentBasedOnTransactionDate.getInstallmentNumber()))
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); ) {
Money interestPortion = installment.payInterestComponent(transactionAmountRemaining);
Money principalPortion = Money.zero(currency); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
Money interestWaivedPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); Money principalPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency()));
}
}
// With whatever is remaining, pay off principal components of installments
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
Money principalPortion = installment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
Money interestPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency()));
}
} }
} }
// With whatever is remaining, pay off principal components of installments
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.isPrincipalNotCompleted(currency) && transactionAmountRemaining.isGreaterThanZero()) {
Money principalPortion = installment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
Money interestPortion = Money.zero(currency);
Money interestWaivedPortion = Money.zero(currency);
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency()));
}
}
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -119,18 +128,26 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
final LoanRepaymentScheduleInstallment currentInstallment, final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining); Money transactionAmountRemaining = transactionAmountUnprocessed;
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
Money principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestPortion,interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -79,20 +79,24 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestPortion = currentInstallment
.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining
.minus(interestPortion);
Money principalPortion = currentInstallment
.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining
.minus(principalPortion);
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -52,7 +52,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
newState = stateOf(LoanStatus.APPROVED, allowedLoanStatuses); newState = stateOf(LoanStatus.APPROVED, allowedLoanStatuses);
} }
break; break;
case LOAN_REPAYMENT: case LOAN_REPAYMENT_OR_WAIVER:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED, LoanStatus.OVERPAID)) { if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED, LoanStatus.OVERPAID)) {
newState = stateOf(LoanStatus.ACTIVE, allowedLoanStatuses); newState = stateOf(LoanStatus.ACTIVE, allowedLoanStatuses);
} else { } else {
@@ -65,31 +65,38 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
final List<LoanRepaymentScheduleInstallment> installments, final List<LoanRepaymentScheduleInstallment> installments,
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final LocalDate transactionDate, final Money paymentInAdvance) { final LocalDate transactionDate, final Money paymentInAdvance) {
final MonetaryCurrency currency = paymentInAdvance.getCurrency();
Money transactionAmountRemaining = paymentInAdvance; Money transactionAmountRemaining = paymentInAdvance;
MonetaryCurrency currency = paymentInAdvance.getCurrency(); Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
// placeholders for tracking the breakdown of components relevant to if (currentInstallment.isPrincipalNotCompleted(currency)) {
// this transaction. principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
Money principalPortion = Money.zero(currency); if (currentInstallment.isPrincipalCompleted(currency)) {
Money interestPortion = Money.zero(currency); interestWaivedPortion = currentInstallment.waiveInterestComponent(currentInstallment.getInterest(currency));
Money interestWaivedPortion = Money.zero(currency); }
if (currentInstallment.isPrincipalNotCompleted(currency)) { loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
principalPortion = currentInstallment
.payPrincipalComponent(transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
if (currentInstallment.isPrincipalCompleted(currency)) {
interestWaivedPortion = currentInstallment
.waiveInterestComponent(currentInstallment
.getInterest(currency));
} }
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
transactionAmountRemaining = transactionAmountRemaining
.minus(principalPortion); principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
} }
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -101,18 +108,27 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
final LoanRepaymentScheduleInstallment currentInstallment, final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
Money principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -582,65 +582,42 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
} }
public void waive(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) { /**
*
* @param amount The amount of interest to be waived.
* @param transactionDate The date on which the interest waiver is to be processed against.
* @param defaultLoanLifecycleStateMachine
*
* @return {@link LoanTransaction} details of executed transaction
*/
public LoanTransaction waiveInterest(
final BigDecimal amount,
final LocalDate transactionDate,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT, LoanStatus.fromInt(this.loanStatus)); final Money amountToWaive = Money.of(loanCurrency(), amount);
this.loanStatus = statusEnum.getValue(); final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(this, amountToWaive, transactionDate);
loanTransaction.updateLoan(this); handleRepaymentOrWaiverTransaction(waiveInterestTransaction, loanLifecycleStateMachine, null);
this.loanTransactions.add(loanTransaction);
return waiveInterestTransaction;
LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
if (loanTransactionDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
+ getApprovedOnDate().toString();
throw new InvalidLoanStateTransitionException("waive",
"cannot.be.before.disbursement.date", errorMessage,
loanTransactionDate, this.getDisbursementDate());
}
if (loanTransactionDate.isAfter(new LocalDate())) {
final String errorMessage = "The transaction date cannot be in the future.";
throw new InvalidLoanStateTransitionException("waive",
"cannot.be.a.future.date", errorMessage, loanTransactionDate);
}
if (getTotalOutstanding().isGreaterThan(this.getInArrearsTolerance())) {
final String errorMessage = "Waiver is only allowed when the total outstanding amount left on loan ("
+ getTotalOutstanding()
+ ") is less than the in arrears tolerance setting of "
+ getInArrearsTolerance().getAmount();
throw new InvalidLoanStateTransitionException("waive",
"cannot.exceed.in.arrears.tolerance.setting", errorMessage,
getTotalOutstanding(), getInArrearsTolerance());
}
Money waived = Money.of(getCurrency(), loanTransaction.getAmount());
if (waived.isGreaterThan(this.getInArrearsTolerance())) {
final String errorMessage = "The amount being waived cannot exceed the in arrears tolerance setting of "
+ getInArrearsTolerance().getAmount();
throw new InvalidLoanStateTransitionException("waive",
"cannot.exceed.in.arrears.tolerance.setting", errorMessage,
waived, getInArrearsTolerance());
}
doPostLoanTransactionChecks(loanTransaction, loanLifecycleStateMachine);
} }
public void updateTotalChargesDueAtDisbursement(){ public void updateTotalChargesDueAtDisbursement() {
this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement();
} }
public void makeRepayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) { public void makeRepayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
handleRepaymentTransaction(loanTransaction, loanLifecycleStateMachine, false); handleRepaymentOrWaiverTransaction(loanTransaction, loanLifecycleStateMachine, null);
} }
private void handleRepaymentTransaction( private void handleRepaymentOrWaiverTransaction(
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final boolean adjusted) { final LoanLifecycleStateMachine loanLifecycleStateMachine,
final LoanTransaction adjustedTransaction) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT, LoanStatus.fromInt(this.loanStatus)); LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
loanTransaction.updateLoan(this); loanTransaction.updateLoan(this);
@@ -649,33 +626,44 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanTransactions.add(loanTransaction); this.loanTransactions.add(loanTransaction);
if (loanTransaction.isNotRepayment()) { if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver()) {
final String errorMessage = "A transaction of type repayment was expected but not received."; final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
throw new InvalidLoanTransactionTypeException("transaction", throw new InvalidLoanTransactionTypeException("transaction",
"is.not.a.repayment.transaction", errorMessage); "is.not.a.repayment.or.waiver.transaction", errorMessage);
} }
LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); LocalDate loanTransactionDate = loanTransaction.getTransactionDate();
if (loanTransactionDate.isBefore(this.getDisbursementDate())) { if (loanTransactionDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The transaction date cannot be before the loan disbursement date: " final String errorMessage = "The transaction date cannot be before the loan disbursement date: "
+ getApprovedOnDate().toString(); + getApprovedOnDate().toString();
throw new InvalidLoanStateTransitionException("repayment", throw new InvalidLoanStateTransitionException("transaction",
"cannot.be.before.disbursement.date", errorMessage, "cannot.be.before.disbursement.date", errorMessage,
loanTransactionDate, this.getDisbursementDate()); loanTransactionDate, this.getDisbursementDate());
} }
if (loanTransactionDate.isAfter(new LocalDate())) { if (loanTransactionDate.isAfter(new LocalDate())) {
final String errorMessage = "The transaction date cannot be in the future."; final String errorMessage = "The transaction date cannot be in the future.";
throw new InvalidLoanStateTransitionException("repayment", throw new InvalidLoanStateTransitionException("transaction",
"cannot.be.a.future.date", errorMessage, loanTransactionDate); "cannot.be.a.future.date", errorMessage, loanTransactionDate);
} }
List<LoanTransaction> repaymentsOrWaivers = retreiveListOfRepaymentOrWaiverTransactionsPostDisbursement(); if (loanTransaction.isWaiver()) {
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
if (adjustedTransaction != null) {
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount());
}
if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
throw new InvalidLoanStateTransitionException("waive.interest",
"amount.exceeds.total.outstanding.interest", errorMessage, loanTransaction.getAmount(), totalInterestOutstandingOnLoan.getAmount());
}
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && !adjusted) { if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments);
} else { } else {
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfRepaymentOrWaiverTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments); loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments);
} }
@@ -777,8 +765,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public Money possibleNextRepaymentAmount() { public Money possibleNextRepaymentAmount() {
MonetaryCurrency currency = this.loanRepaymentScheduleDetail MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getPrincipal().getCurrency();
.getPrincipal().getCurrency();
Money possibleNextRepaymentAmount = Money.zero(currency); Money possibleNextRepaymentAmount = Money.zero(currency);
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
@@ -790,26 +777,43 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return possibleNextRepaymentAmount; return possibleNextRepaymentAmount;
} }
public LoanTransaction deriveDefaultInterestWaiverTransaction() {
final Money totalInterestOutstanding = getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
LocalDate transactionDate = new LocalDate();
if (totalInterestOutstanding.isGreaterThanZero()) {
// find earliest known instance of overdue interest and default to that
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
if (scheduledRepayment.isOverdueOn(new LocalDate()) && scheduledRepayment.isNotFullyCompleted() && outstandingForPeriod.isGreaterThanZero()) {
transactionDate = scheduledRepayment.getDueDate();
possibleInterestToWaive = outstandingForPeriod;
break;
}
}
}
return LoanTransaction.waiver(this, possibleInterestToWaive, transactionDate);
}
public void adjustExistingTransaction( public void adjustExistingTransaction(
LoanTransaction transactionForAdjustment, final LoanTransaction transactionForAdjustment,
LoanTransaction newTransactionDetail, final LoanTransaction newTransactionDetail,
LoanLifecycleStateMachine loanLifecycleStateMachine) { final LoanLifecycleStateMachine loanLifecycleStateMachine) {
if (transactionForAdjustment.isNotRepayment() if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
&& transactionForAdjustment.isNotWaiver()) {
final String errorMessage = "A transaction of type repayment or waiver was expected but not received."; final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
throw new InvalidLoanTransactionTypeException("transaction", throw new InvalidLoanTransactionTypeException("transaction",
"is.not.a.repayment.or.waiver.transaction", errorMessage); "is.not.a.repayment.or.waiver.transaction", errorMessage);
} }
transactionForAdjustment.contra(); transactionForAdjustment.contra();
if (newTransactionDetail.isRepayment()) { if (newTransactionDetail.isRepayment() || newTransactionDetail.isWaiver()) {
handleRepaymentTransaction(newTransactionDetail, loanLifecycleStateMachine, true); handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment);
}
if (newTransactionDetail.isWaiver()) {
waive(newTransactionDetail, loanLifecycleStateMachine);
} }
} }
@@ -1240,8 +1244,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
private Money getTotalInterestOnLoan() { private Money getTotalInterestOnLoan() {
Money cumulativeInterest = Money.zero(this.loanRepaymentScheduleDetail Money cumulativeInterest = Money.zero(loanCurrency());
.getPrincipal().getCurrency());
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment cumulativeInterest = cumulativeInterest.plus(scheduledRepayment
@@ -1250,6 +1253,31 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return cumulativeInterest; return cumulativeInterest;
} }
private Money getTotalInterestOutstandingOnLoan() {
Money cumulativeInterest = Money.zero(loanCurrency());
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
}
return cumulativeInterest;
}
@SuppressWarnings("unused")
private Money getTotalInterestOverdueOnLoan() {
Money cumulativeInterestOverdue = Money.zero(this.loanRepaymentScheduleDetail.getPrincipal().getCurrency());
for (LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
if (scheduledRepayment.isOverdueOn(new LocalDate())) {
cumulativeInterestOverdue = cumulativeInterestOverdue.plus(interestOutstandingForPeriod);
}
}
return cumulativeInterestOverdue;
}
private Money getTotalPrincipalOnLoan() { private Money getTotalPrincipalOnLoan() {
Money cumulativePrincipal = Money.zero(this.loanRepaymentScheduleDetail Money cumulativePrincipal = Money.zero(this.loanRepaymentScheduleDetail
@@ -2,7 +2,7 @@ package org.mifosng.platform.loan.domain;
public enum LoanEvent { public enum LoanEvent {
LOAN_CREATED, LOAN_REJECTED, LOAN_WITHDRAWN, LOAN_APPROVED, LOAN_APPROVAL_UNDO, LOAN_DISBURSED, LOAN_DISBURSAL_UNDO, LOAN_REPAYMENT, LOAN_CREATED, LOAN_REJECTED, LOAN_WITHDRAWN, LOAN_APPROVED, LOAN_APPROVAL_UNDO, LOAN_DISBURSED, LOAN_DISBURSAL_UNDO, LOAN_REPAYMENT_OR_WAIVER,
// //
REPAID_IN_FULL, LOAN_WRITE_OFF, LOAN_RESCHEDULE, INTERST_REBATE_OWED, LOAN_OVERPAYMENT; REPAID_IN_FULL, LOAN_WRITE_OFF, LOAN_RESCHEDULE, INTERST_REBATE_OWED, LOAN_OVERPAYMENT;
} }
@@ -93,6 +93,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
return Money.of(currency, this.principalCompleted); return Money.of(currency, this.principalCompleted);
} }
public Money getPrincipalOutstanding(final MonetaryCurrency currency) {
return getPrincipal(currency).minus(getPrincipalCompleted(currency));
}
public Money getInterest(final MonetaryCurrency currency) { public Money getInterest(final MonetaryCurrency currency) {
return Money.of(currency, this.interest); return Money.of(currency, this.interest);
} }
@@ -104,15 +108,23 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
public Money getInterestWaived(final MonetaryCurrency currency) { public Money getInterestWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWaived); return Money.of(currency, this.interestWaived);
} }
public Money getInterestOutstanding(final MonetaryCurrency currency) {
return getInterest(currency).minus(getInterestCompleted(currency).plus(getInterestWaived(currency)));
}
public boolean isInterestDue(final MonetaryCurrency currency) { public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterest(currency).minus(getInterestCompleted(currency).plus(getInterestWaived(currency))).isGreaterThanZero(); return getInterest(currency).minus(getInterestCompleted(currency).plus(getInterestWaived(currency))).isGreaterThanZero();
} }
public Money getTotal(MonetaryCurrency currency) { public Money getTotal(final MonetaryCurrency currency) {
return getPrincipal(currency).plus(getInterest(currency)); return getPrincipal(currency).plus(getInterest(currency));
} }
public Money getTotalOutstanding(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency));
}
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
this.loan = loan; this.loan = loan;
} }
@@ -151,12 +163,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
public Money payInterestComponent(final Money transactionAmountRemaining) { public Money payInterestComponent(final Money transactionAmountRemaining) {
MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money interestPortionOfTransaction = Money.zero(currency); Money interestPortionOfTransaction = Money.zero(currency);
Money interestExpected = getInterest(currency); final Money interestDue = getInterestOutstanding(currency);
Money interestDue = interestExpected.minus(getInterestCompleted(currency));
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestCompleted = getInterestCompleted(currency).plus(interestDue).getAmount(); this.interestCompleted = getInterestCompleted(currency).plus(interestDue).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue); interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue);
@@ -172,11 +182,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
public Money payPrincipalComponent(final Money transactionAmountRemaining) { public Money payPrincipalComponent(final Money transactionAmountRemaining) {
MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money principalPortionOfTransaction = Money.zero(currency); Money principalPortionOfTransaction = Money.zero(currency);
Money principalExpected = getPrincipal(currency); final Money principalDue = getPrincipalOutstanding(currency);
Money principalDue = principalExpected.minus(getPrincipalCompleted(currency));
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount(); this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue); principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
@@ -194,8 +203,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedInterestPortionOfTransaction = Money.zero(currency); Money waivedInterestPortionOfTransaction = Money.zero(currency);
Money interestExpected = getInterest(currency); Money interestDue = getInterestOutstanding(currency);
Money interestDue = interestExpected.minus(getInterestCompleted(currency));
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount(); this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue); waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue);
@@ -69,23 +69,23 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
} }
public static LoanTransaction disbursement(final Money amount, final LocalDate disbursementDate) { public static LoanTransaction disbursement(final Money amount, final LocalDate disbursementDate) {
return new LoanTransaction(LoanTransactionType.DISBURSEMENT, amount.getAmount(), disbursementDate); return new LoanTransaction(null, LoanTransactionType.DISBURSEMENT, amount.getAmount(), disbursementDate);
} }
public static LoanTransaction repayment(final Money amount, final LocalDate paymentDate) { public static LoanTransaction repayment(final Money amount, final LocalDate paymentDate) {
return new LoanTransaction(LoanTransactionType.REPAYMENT, amount.getAmount(), paymentDate); return new LoanTransaction(null, LoanTransactionType.REPAYMENT, amount.getAmount(), paymentDate);
} }
public static LoanTransaction repaymentAtDisbursement(final Money amount, final LocalDate paymentDate) { public static LoanTransaction repaymentAtDisbursement(final Money amount, final LocalDate paymentDate) {
return new LoanTransaction(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, amount.getAmount(), paymentDate); return new LoanTransaction(null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, amount.getAmount(), paymentDate);
} }
public static LoanTransaction waiver(final Money waived, final LocalDate waiveDate) { public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) {
return new LoanTransaction(LoanTransactionType.WAIVED, waived.getAmount(), waiveDate); return new LoanTransaction(loan, LoanTransactionType.WAIVED, waived.getAmount(), waiveDate);
} }
private static LoanTransaction contra(LoanTransaction originalTransaction) { private static LoanTransaction contra(final LoanTransaction originalTransaction) {
LoanTransaction contra = new LoanTransaction(LoanTransactionType.REVERSAL, originalTransaction.getAmount().negate(), new LocalDate(originalTransaction.getDateOf())); LoanTransaction contra = new LoanTransaction(null, LoanTransactionType.REVERSAL, originalTransaction.getAmount().negate(), new LocalDate(originalTransaction.getDateOf()));
contra.updateContra(originalTransaction); contra.updateContra(originalTransaction);
return contra; return contra;
} }
@@ -94,7 +94,8 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
this.contra = transaction; this.contra = transaction;
} }
private LoanTransaction(final LoanTransactionType type, final BigDecimal amount, final LocalDate date) { private LoanTransaction(final Loan loan, final LoanTransactionType type, final BigDecimal amount, final LocalDate date) {
this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.amount = amount; this.amount = amount;
this.dateOf = date.toDateMidnight().toDate(); this.dateOf = date.toDateMidnight().toDate();
@@ -3,13 +3,28 @@ package org.mifosng.platform.loan.domain;
import java.util.Comparator; import java.util.Comparator;
/** /**
* * Sort loan transactions by transaction date and transaction type placing
*/ */
public class LoanTransactionComparator implements Comparator<LoanTransaction> { public class LoanTransactionComparator implements Comparator<LoanTransaction> {
@Override @Override
public int compare(final LoanTransaction o1, final LoanTransaction o2) { public int compare(final LoanTransaction o1, final LoanTransaction o2) {
return o1.getTransactionDate().compareTo(o2.getTransactionDate()); int compareResult = 0;
final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate());
if (comparsion == 0) {
// equal transaction dates
if (o1.isWaiver() && o2.isNotWaiver()) {
compareResult = -1;
} else if (o1.isNotWaiver() && o2.isWaiver()) {
compareResult = 1;
} else {
compareResult = 0;
}
} else {
compareResult = comparsion;
}
return compareResult;
} }
} }
@@ -68,22 +68,26 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
final LoanRepaymentScheduleInstallment currentInstallment, final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final LoanTransaction loanTransaction,
final Money transactionAmountUnprocessed) { final Money transactionAmountUnprocessed) {
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money interestPortion = currentInstallment
.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining
.minus(interestPortion);
Money principalPortion = currentInstallment
.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining
.minus(principalPortion);
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
loanTransaction.updateComponents(principalPortion, interestPortion,interestWaivedPortion, Money.zero(transactionAmountRemaining.getCurrency())); Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
}
loanTransaction.updateComponents(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@@ -10,6 +10,7 @@ import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanPermissionData; import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData; import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
public interface LoanReadPlatformService { public interface LoanReadPlatformService {
@@ -29,7 +30,7 @@ public interface LoanReadPlatformService {
LoanTransactionData retrieveNewLoanRepaymentDetails(Long loanId); LoanTransactionData retrieveNewLoanRepaymentDetails(Long loanId);
LoanTransactionData retrieveNewLoanWaiverDetails(Long loanId); LoanTransactionNewData retrieveNewLoanWaiveInterestDetails(Long loanId);
LoanTransactionData retrieveLoanTransactionDetails(Long loanId, Long transactionId); LoanTransactionData retrieveLoanTransactionDetails(Long loanId, Long transactionId);
} }
@@ -20,6 +20,7 @@ import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData; import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.client.service.ClientReadPlatformService; import org.mifosng.platform.client.service.ClientReadPlatformService;
import org.mifosng.platform.currency.domain.ApplicationCurrency; import org.mifosng.platform.currency.domain.ApplicationCurrency;
@@ -285,40 +286,30 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
@Override @Override
public LoanTransactionData retrieveNewLoanWaiverDetails(final Long loanId) { public LoanTransactionNewData retrieveNewLoanWaiveInterestDetails(final Long loanId) {
context.authenticatedUser(); context.authenticatedUser();
// TODO - OPTIMIZE - write simple sql query to fetch back date of // TODO - OPTIMIZE - write simple sql query to fetch back overdue interest that can be waived along with the date of repayment period interest is overdue.
// possible next transaction date.
Loan loan = this.loanRepository.findOne(loanId); Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { if (loan == null) {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
final String currencyCode = loan.repaymentScheduleDetail() final String currencyCode = loan.repaymentScheduleDetail().getPrincipal().getCurrencyCode();
.getPrincipal().getCurrencyCode(); ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode);
ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode);
if (currency == null) { if (currency == null) {
throw new CurrencyNotFoundException(currencyCode); throw new CurrencyNotFoundException(currencyCode);
} }
CurrencyData currencyData = new CurrencyData(currency.getCode(), CurrencyData currencyData = new CurrencyData(currency.getCode(),
currency.getName(), currency.getDecimalPlaces(), currency.getName(), currency.getDecimalPlaces(),
currency.getDisplaySymbol(), currency.getNameCode()); currency.getDisplaySymbol(), currency.getNameCode());
Money totalOutstanding = loan.getTotalOutstanding(); LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction();
MoneyData totalOutstandingData = MoneyData.of(currencyData,
totalOutstanding.getAmount());
LoanTransactionData newWaiverDetails = new LoanTransactionData(); EnumOptionData waiverEnum = LoanEnumerations.transactionType(LoanTransactionType.WAIVED);
newWaiverDetails.setTransactionType(LoanEnumerations
.transactionType(LoanTransactionType.WAIVED)); return new LoanTransactionNewData(waiverEnum, currencyData, waiveOfInterest.getTransactionDate(), waiveOfInterest.getAmount());
newWaiverDetails.setDate(new LocalDate());
newWaiverDetails.setTotal(totalOutstandingData);
return newWaiverDetails;
} }
@Override @Override
@@ -45,7 +45,7 @@ public interface LoanWritePlatformService {
EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command); EntityIdentifier adjustLoanTransaction(AdjustLoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')") @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
EntityIdentifier waiveLoanAmount(LoanTransactionCommand command); EntityIdentifier waiveInterestOnLoan(LoanTransactionCommand command);
@PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')") @PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')")
EntityIdentifier addLoanCharge(LoanChargeCommand command); EntityIdentifier addLoanCharge(LoanChargeCommand command);
@@ -495,7 +495,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LocalDate transactionDate = command.getTransactionDate(); LocalDate transactionDate = command.getTransactionDate();
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate); LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate);
if (transactionToAdjust.isWaiver()) { if (transactionToAdjust.isWaiver()) {
newTransactionDetail = LoanTransaction.waiver(transactionAmount, transactionDate); newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate);
} }
loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine()); loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine());
@@ -515,33 +515,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public EntityIdentifier waiveLoanAmount(LoanTransactionCommand command) { public EntityIdentifier waiveInterestOnLoan(final LoanTransactionCommand command) {
context.authenticatedUser(); context.authenticatedUser();
LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command); final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
validator.validate(); validator.validate();
Loan loan = this.loanRepository.findOne(command.getLoanId()); final Loan loan = this.loanRepository.findOne(command.getLoanId());
if (loan == null) { if (loan == null) {
throw new LoanNotFoundException(command.getLoanId()); throw new LoanNotFoundException(command.getLoanId());
} }
Money waived = Money.of(loan.repaymentScheduleDetail() final LoanTransaction waiveTransaction = loan.waiveInterest(command.getTransactionAmount(), command.getTransactionDate(), defaultLoanLifecycleStateMachine());
.getPrincipal().getCurrency(),
command.getTransactionAmount());
LoanTransaction waiver = LoanTransaction.waiver(waived, command.getTransactionDate());
loan.waive(waiver, defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(waiver);
this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
String noteText = command.getNote(); final String noteText = command.getNote();
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
Note note = Note.loanTransactionNote(loan, waiver, noteText); final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText);
this.noteRepository.save(note); this.noteRepository.save(note);
} }