calc loans arrears for details page

This commit is contained in:
John Woodlock
2012-07-20 19:33:58 +10:00
parent 7630b913ec
commit dc10b56388
11 changed files with 76 additions and 1242 deletions
@@ -27,10 +27,10 @@ import org.mifosng.platform.api.data.LoanAccountData;
import org.mifosng.platform.api.data.LoanAccountSummaryData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanRepaymentPeriodDatajpw;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionDatajpw;
import org.mifosng.platform.api.data.LoanTransactionDataDTO;
import org.mifosng.platform.api.data.NewLoanData;
import org.mifosng.platform.api.infrastructure.ApiDataConversionService;
import org.mifosng.platform.api.infrastructure.ApiJSONFormattingService;
@@ -99,9 +99,9 @@ public class LoansApiResource {
LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService
.retrieveLoanAccountDetails(loanId);
Collection<LoanTransactionDatajpw> loanRepayments = this.loanReadPlatformService
Collection<LoanTransactionDataDTO> loanRepayments = this.loanReadPlatformService
.retrieveLoanPayments(loanId);
Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule = this.loanReadPlatformService
Collection<LoanRepaymentPeriodData> repaymentSchedule = this.loanReadPlatformService
.retrieveRepaymentSchedule(loanId);
LoanAccountSummaryData summary = this.loanReadPlatformService
@@ -1,44 +0,0 @@
package org.mifosng.platform.api.data;
import java.util.List;
public class DerivedLoanData {
private LoanRepaymentScheduleData repaymentSchedule;
private LoanAccountSummaryData summary;
private List<LoanTransactionData> loanRepayments;
public DerivedLoanData() {
//
}
public DerivedLoanData(LoanRepaymentScheduleData repaymentScheduleData, LoanAccountSummaryData summaryData, List<LoanTransactionData> loanRepayments) {
this.repaymentSchedule = repaymentScheduleData;
this.summary = summaryData;
this.loanRepayments = loanRepayments;
}
public LoanRepaymentScheduleData getRepaymentSchedule() {
return repaymentSchedule;
}
public void setRepaymentSchedule(LoanRepaymentScheduleData repaymentSchedule) {
this.repaymentSchedule = repaymentSchedule;
}
public LoanAccountSummaryData getSummary() {
return summary;
}
public void setSummary(LoanAccountSummaryData summary) {
this.summary = summary;
}
public List<LoanTransactionData> getLoanRepayments() {
return loanRepayments;
}
public void setLoanRepayments(List<LoanTransactionData> loanRepayments) {
this.loanRepayments = loanRepayments;
}
}
@@ -44,8 +44,8 @@ public class LoanAccountData {
private LocalDate lifeCycleStatusDate;
private LoanAccountSummaryData summary;
private Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule;
private Collection<LoanTransactionDatajpw> loanRepayments;
private Collection<LoanRepaymentPeriodData> repaymentSchedule;
private Collection<LoanTransactionDataDTO> loanRepayments;
private LoanPermissionData permissions;
@@ -57,8 +57,8 @@ public class LoanAccountData {
public LoanAccountData(LoanBasicDetailsData basicDetails,
LoanAccountSummaryData summary,
Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule,
Collection<LoanTransactionDatajpw> loanRepayments,
Collection<LoanRepaymentPeriodData> repaymentSchedule,
Collection<LoanTransactionDataDTO> loanRepayments,
LoanPermissionData permissions) {
this.summary = summary;
this.repaymentSchedule = repaymentSchedule;
@@ -141,21 +141,21 @@ public class LoanAccountData {
this.summary = summary;
}
public Collection<LoanRepaymentPeriodDatajpw> getRepaymentSchedule() {
public Collection<LoanRepaymentPeriodData> getRepaymentSchedule() {
return repaymentSchedule;
}
public void setRepaymentSchedule(
Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule) {
Collection<LoanRepaymentPeriodData> repaymentSchedule) {
this.repaymentSchedule = repaymentSchedule;
}
public Collection<LoanTransactionDatajpw> getLoanRepayments() {
public Collection<LoanTransactionDataDTO> getLoanRepayments() {
return loanRepayments;
}
public void setLoanRepayments(
Collection<LoanTransactionDatajpw> loanRepayments) {
Collection<LoanTransactionDataDTO> loanRepayments) {
this.loanRepayments = loanRepayments;
}
@@ -4,9 +4,9 @@ import org.joda.time.LocalDate;
public class LoanRepaymentPeriodData {
private Long loanId;
private Integer period;
private LocalDate date;
private MoneyData disbursed;
private MoneyData principal;
private MoneyData principalPaid;
private MoneyData principalOutstanding;
@@ -16,70 +16,36 @@ public class LoanRepaymentPeriodData {
private MoneyData total;
private MoneyData totalPaid;
private MoneyData totalOutstanding;
private MoneyData totalWaived;
private MoneyData totalArrears;
private LocalDate arrearsFrom;
private LocalDate arrearsTo;
private LocalDate paidInFullOn;
private LocalDate lastAffectingPaymentOn;
private static CurrencyData currencyData(MoneyData moneyData) {
String code = moneyData.getCurrencyCode();
String name = moneyData.getDefaultName();
int decimalPlaces = moneyData.getDigitsAfterDecimal();
String displaySymbol = moneyData.getDisplaySymbol();
String nameCode = moneyData.getNameCode();
return new CurrencyData(code, name, decimalPlaces, displaySymbol, nameCode);
}
public LoanRepaymentPeriodData() {
//
}
public LoanRepaymentPeriodData(Integer period, LocalDate date,
MoneyData disbursed, MoneyData total) {
this.period = period;
this.date = date;
this.disbursed = disbursed;
this.principal = null;
this.principalPaid = null;
this.principalOutstanding = null;
this.interest = null;
this.interestPaid = null;
this.interestOutstanding = null;
this.total = total;
this.totalPaid = null;
this.totalOutstanding = total;
}
public LoanRepaymentPeriodData(Integer period, LocalDate date,
MoneyData disbursed, MoneyData principal,
MoneyData interest, MoneyData total,
MoneyData totalOutstanding) {
public LoanRepaymentPeriodData(Long loanId, Integer period,
LocalDate date, MoneyData principal, MoneyData principalPaid,
MoneyData principalOutstanding, MoneyData interest,
MoneyData interestPaid, MoneyData interestOutstanding,
MoneyData total, MoneyData totalPaid, MoneyData totalOutstanding) {
this.loanId = loanId;
this.period = period;
this.date = date;
this.disbursed = disbursed;
this.principal = principal;
this.principalPaid = MoneyData.zero(currencyData(principal));
this.principalOutstanding = principal;
this.principalPaid = principalPaid;
this.principalOutstanding = principalOutstanding;
this.interest = interest;
this.interestPaid = MoneyData.zero(currencyData(principal));
this.interestOutstanding = interest;
this.interestPaid = interestPaid;
this.interestOutstanding = interestOutstanding;
this.total = total;
this.totalPaid = MoneyData.zero(currencyData(principal));
this.totalPaid = totalPaid;
this.totalOutstanding = totalOutstanding;
}
public boolean isInArrearsWithToleranceOf(MoneyData arrearsTolerance, LocalDate asOf) {
boolean inArrears = false;
if (arrearsTolerance.isGreaterThanZero()) {
inArrears = this.totalOutstanding.isGreaterThan(arrearsTolerance);
} else {
inArrears = this.totalOutstanding.isGreaterThanZero();
}
return inArrears && this.date.isBefore(asOf);
public Long getLoanId() {
return loanId;
}
public void setLoanId(Long loanId) {
this.loanId = loanId;
}
public Integer getPeriod() {
@@ -98,14 +64,6 @@ public class LoanRepaymentPeriodData {
this.date = date;
}
public MoneyData getDisbursed() {
return disbursed;
}
public void setDisbursed(MoneyData disbursed) {
this.disbursed = disbursed;
}
public MoneyData getPrincipal() {
return principal;
}
@@ -114,22 +72,6 @@ public class LoanRepaymentPeriodData {
this.principal = principal;
}
public MoneyData getInterest() {
return interest;
}
public void setInterest(MoneyData interest) {
this.interest = interest;
}
public MoneyData getTotal() {
return total;
}
public void setTotal(MoneyData total) {
this.total = total;
}
public MoneyData getPrincipalPaid() {
return principalPaid;
}
@@ -146,6 +88,14 @@ public class LoanRepaymentPeriodData {
this.principalOutstanding = principalOutstanding;
}
public MoneyData getInterest() {
return interest;
}
public void setInterest(MoneyData interest) {
this.interest = interest;
}
public MoneyData getInterestPaid() {
return interestPaid;
}
@@ -162,6 +112,14 @@ public class LoanRepaymentPeriodData {
this.interestOutstanding = interestOutstanding;
}
public MoneyData getTotal() {
return total;
}
public void setTotal(MoneyData total) {
this.total = total;
}
public MoneyData getTotalPaid() {
return totalPaid;
}
@@ -178,55 +136,4 @@ public class LoanRepaymentPeriodData {
this.totalOutstanding = totalOutstanding;
}
public boolean isFullyPaid() {
return this.totalOutstanding.isZero();
}
public MoneyData getTotalArrears() {
return totalArrears;
}
public void setTotalArrears(MoneyData totalArrears) {
this.totalArrears = totalArrears;
}
public LocalDate getArrearsFrom() {
return arrearsFrom;
}
public void setArrearsFrom(LocalDate arrearsFrom) {
this.arrearsFrom = arrearsFrom;
}
public LocalDate getArrearsTo() {
return arrearsTo;
}
public void setArrearsTo(LocalDate arrearsTo) {
this.arrearsTo = arrearsTo;
}
public LocalDate getPaidInFullOn() {
return paidInFullOn;
}
public void setPaidInFullOn(LocalDate paidInFullOn) {
this.paidInFullOn = paidInFullOn;
}
public LocalDate getLastAffectingPaymentOn() {
return lastAffectingPaymentOn;
}
public void setLastAffectingPaymentOn(LocalDate lastAffectingPaymentOn) {
this.lastAffectingPaymentOn = lastAffectingPaymentOn;
}
public MoneyData getTotalWaived() {
return totalWaived;
}
public void setTotalWaived(MoneyData totalWaived) {
this.totalWaived = totalWaived;
}
}
@@ -1,139 +0,0 @@
package org.mifosng.platform.api.data;
import org.joda.time.LocalDate;
public class LoanRepaymentPeriodDatajpw {
private Long loanId;
private Integer period;
private LocalDate date;
private MoneyData principal;
private MoneyData principalPaid;
private MoneyData principalOutstanding;
private MoneyData interest;
private MoneyData interestPaid;
private MoneyData interestOutstanding;
private MoneyData total;
private MoneyData totalPaid;
private MoneyData totalOutstanding;
public LoanRepaymentPeriodDatajpw() {
//
}
public LoanRepaymentPeriodDatajpw(Long loanId, Integer period,
LocalDate date, MoneyData principal, MoneyData principalPaid,
MoneyData principalOutstanding, MoneyData interest,
MoneyData interestPaid, MoneyData interestOutstanding,
MoneyData total, MoneyData totalPaid, MoneyData totalOutstanding) {
this.loanId = loanId;
this.period = period;
this.date = date;
this.principal = principal;
this.principalPaid = principalPaid;
this.principalOutstanding = principalOutstanding;
this.interest = interest;
this.interestPaid = interestPaid;
this.interestOutstanding = interestOutstanding;
this.total = total;
this.totalPaid = totalPaid;
this.totalOutstanding = totalOutstanding;
}
public Long getLoanId() {
return loanId;
}
public void setLoanId(Long loanId) {
this.loanId = loanId;
}
public Integer getPeriod() {
return period;
}
public void setPeriod(Integer period) {
this.period = period;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public MoneyData getPrincipal() {
return principal;
}
public void setPrincipal(MoneyData principal) {
this.principal = principal;
}
public MoneyData getPrincipalPaid() {
return principalPaid;
}
public void setPrincipalPaid(MoneyData principalPaid) {
this.principalPaid = principalPaid;
}
public MoneyData getPrincipalOutstanding() {
return principalOutstanding;
}
public void setPrincipalOutstanding(MoneyData principalOutstanding) {
this.principalOutstanding = principalOutstanding;
}
public MoneyData getInterest() {
return interest;
}
public void setInterest(MoneyData interest) {
this.interest = interest;
}
public MoneyData getInterestPaid() {
return interestPaid;
}
public void setInterestPaid(MoneyData interestPaid) {
this.interestPaid = interestPaid;
}
public MoneyData getInterestOutstanding() {
return interestOutstanding;
}
public void setInterestOutstanding(MoneyData interestOutstanding) {
this.interestOutstanding = interestOutstanding;
}
public MoneyData getTotal() {
return total;
}
public void setTotal(MoneyData total) {
this.total = total;
}
public MoneyData getTotalPaid() {
return totalPaid;
}
public void setTotalPaid(MoneyData totalPaid) {
this.totalPaid = totalPaid;
}
public MoneyData getTotalOutstanding() {
return totalOutstanding;
}
public void setTotalOutstanding(MoneyData totalOutstanding) {
this.totalOutstanding = totalOutstanding;
}
}
@@ -1,26 +0,0 @@
package org.mifosng.platform.api.data;
import java.util.ArrayList;
import java.util.List;
public class LoanRepaymentScheduleData {
private List<LoanRepaymentPeriodData> periods = new ArrayList<LoanRepaymentPeriodData>();
public LoanRepaymentScheduleData() {
//
}
public LoanRepaymentScheduleData(
final List<LoanRepaymentPeriodData> periods) {
this.periods = periods;
}
public List<LoanRepaymentPeriodData> getPeriods() {
return periods;
}
public void setPeriods(List<LoanRepaymentPeriodData> periods) {
this.periods = periods;
}
}
@@ -2,18 +2,18 @@ package org.mifosng.platform.api.data;
import org.joda.time.LocalDate;
public class LoanTransactionDatajpw {
public class LoanTransactionDataDTO {
private Long id;
private EnumOptionData transactionType;
private LocalDate date;
private MoneyData total;
public LoanTransactionDatajpw() {
public LoanTransactionDataDTO() {
//
}
public LoanTransactionDatajpw(Long id, EnumOptionData transactionType,
public LoanTransactionDataDTO(Long id, EnumOptionData transactionType,
final LocalDate date, final MoneyData total) {
this.id = id;
this.transactionType = transactionType;
@@ -1,491 +0,0 @@
package org.mifosng.platform.loan.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DerivedLoanData;
import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.data.LoanAccountSummaryData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
import org.mifosng.platform.api.data.LoanRepaymentScheduleData;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loanproduct.service.LoanEnumerations;
// FIXME - The derived details of the loan schedule are now updated on the fly when someone does a transaction against a loan. So this is not fully needed.
// - we still use this to figure out how much of a given transactions amount was principal versus interest but might not need this and we should probably
// - derive and update that on the database on the fly
// FIXME - JPW - the above fixme was done and I can't see anything else using this method or whole class
public class DerivedLoanDataProcessor {
public DerivedLoanData process(List<LoanRepaymentScheduleInstallment> repaymentSchedulePeriods, List<LoanTransaction> loanTransactions,
CurrencyData currencyDetail, BigDecimal arrearsTolerance) {
// 1. derive repayment schedule
LoanRepaymentScheduleData repaymentScheduleDetails = generateRepaymentScheduleData(repaymentSchedulePeriods, currencyDetail);
List<LoanRepaymentPeriodData> loanRepaymentPeriods = repaymentScheduleDetails.getPeriods();
// break up payments based on principal and interest
List<LoanTransactionData> loanRepayments = new ArrayList<LoanTransactionData>();
int repaymentScheduleIndex = 0;
for (LoanTransaction transaction : loanTransactions) {
if (transaction.isRepayment() || transaction.isWaiver()) {
MonetaryCurrency currency = new MonetaryCurrency(currencyDetail.getCode(), currencyDetail.getDecimalPlaces());
if (transaction.getAmount(currency).isGreaterThanZero()) {
LoanRepaymentPeriodData scheduledRepaymentPeriod = loanRepaymentPeriods
.get(repaymentScheduleIndex);
if (transaction.isRepayment()) {
LoanTransactionData repaymentData = processLoanRepayment(
transaction, scheduledRepaymentPeriod,
repaymentScheduleIndex, loanRepaymentPeriods,
arrearsTolerance, currencyDetail);
loanRepayments.add(repaymentData);
}
if (transaction.isWaiver()) {
LoanTransactionData repaymentData = processWaiver(transaction, scheduledRepaymentPeriod, currencyDetail);
loanRepayments.add(repaymentData);
}
if (scheduledRepaymentPeriod.isFullyPaid()) {
repaymentScheduleIndex++;
}
}
}
}
loanRepaymentPeriods = calculateArrears(loanRepaymentPeriods, arrearsTolerance, currencyDetail);
LoanRepaymentScheduleData processRepaymentScheduleData = new LoanRepaymentScheduleData(loanRepaymentPeriods);
LoanAccountSummaryData summaryData = calculateTotalsFromRepaymentScheduleData(loanRepaymentPeriods, currencyDetail);
Collections.sort(loanRepayments, new LoanRepaymentDataComparator());
if (loanRepayments.isEmpty()) {
loanRepayments = null;
}
return new DerivedLoanData(processRepaymentScheduleData, summaryData, loanRepayments);
}
private LoanTransactionData processWaiver(LoanTransaction transaction,
LoanRepaymentPeriodData scheduledRepaymentPeriod, CurrencyData currencyData) {
MonetaryCurrency currency = new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces());
Money totalToWaive = transaction.getAmount(currency);
Money interestToWaive = moneyFrom(scheduledRepaymentPeriod.getInterestOutstanding());
Money principalToWaive = moneyFrom(scheduledRepaymentPeriod.getPrincipalOutstanding());
Money interestWaived = Money.zero(currency);
if (totalToWaive.isGreaterThanOrEqualTo(interestToWaive)) {
totalToWaive = totalToWaive.minus(interestToWaive);
interestWaived = interestToWaive;
} else {
interestWaived = totalToWaive;
totalToWaive = Money.zero(currency);
}
Money principalWaived = Money.zero(currency);
if (totalToWaive.isGreaterThanOrEqualTo(principalToWaive)) {
totalToWaive = totalToWaive.minus(principalToWaive);
principalWaived = principalToWaive;
} else {
principalWaived = totalToWaive;
totalToWaive = Money.zero(currency);
}
MoneyData zero = moneyDataFrom(currencyData, Money.zero(currency));
Money totalWaivedInPeriodToDate = Money.zero(currency);
if (scheduledRepaymentPeriod.getTotalWaived() != null) {
totalWaivedInPeriodToDate = moneyFrom(scheduledRepaymentPeriod.getTotalWaived());
}
Money totalWaivedInTransaction = interestWaived.plus(principalWaived);
totalWaivedInPeriodToDate = totalWaivedInPeriodToDate.plus(totalWaivedInTransaction);
scheduledRepaymentPeriod.setTotalWaived(moneyDataFrom(currencyData, totalWaivedInPeriodToDate));
MoneyData totalWaived = moneyDataFrom(currencyData, totalWaivedInTransaction);
Money principalOutstanding = moneyFrom(scheduledRepaymentPeriod.getPrincipalOutstanding());
Money remainingPrincipalOutstanding = principalOutstanding.minus(principalWaived);
scheduledRepaymentPeriod.setPrincipalOutstanding(moneyDataFrom(currencyData, remainingPrincipalOutstanding));
Money interestOutstanding = moneyFrom(scheduledRepaymentPeriod.getInterestOutstanding());
Money remainingInterestOutstanding = interestOutstanding.minus(interestWaived);
scheduledRepaymentPeriod.setInterestOutstanding(moneyDataFrom(currencyData, remainingInterestOutstanding));
Money totalOutstanding = moneyFrom(scheduledRepaymentPeriod.getTotalOutstanding());
Money remainingOutstanding = totalOutstanding.minus(moneyFrom(totalWaived));
scheduledRepaymentPeriod.setTotalOutstanding(moneyDataFrom(currencyData, remainingOutstanding));
EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVED);
LoanTransactionData loanRepaymentData = new LoanTransactionData(transaction.getId(), transactionType, transaction.getTransactionDate(), moneyDataFrom(currencyData, principalWaived),
moneyDataFrom(currencyData, interestWaived), totalWaived, zero);
loanRepaymentData.setTotalWaived(totalWaived);
return loanRepaymentData;
}
private List<LoanRepaymentPeriodData> calculateArrears(
List<LoanRepaymentPeriodData> loanRepaymentPeriods,
BigDecimal arrearsTolerance, CurrencyData currencyData) {
LocalDate today = new LocalDate();
int repaymentIndex = 0;
for (LoanRepaymentPeriodData currentRepaymentSchedule : loanRepaymentPeriods) {
if (currentRepaymentSchedule.isInArrearsWithToleranceOf(
MoneyData.of(currencyData, arrearsTolerance), today)) {
currentRepaymentSchedule
.setTotalArrears(currentRepaymentSchedule
.getTotalOutstanding());
LocalDate arrearsFrom = determineInArrearsFrom(currentRepaymentSchedule, loanRepaymentPeriods, repaymentIndex);
LocalDate arrearsTo = determineInArrearsTo(loanRepaymentPeriods, repaymentIndex);
currentRepaymentSchedule.setArrearsFrom(arrearsFrom);
currentRepaymentSchedule.setArrearsTo(arrearsTo);
}
repaymentIndex++;
}
return loanRepaymentPeriods;
}
private LocalDate determineInArrearsTo(
List<LoanRepaymentPeriodData> loanRepaymentPeriods,
int repaymentIndex) {
LocalDate today = new LocalDate();
LocalDate arrearsDate = null;
if (loanRepaymentPeriods.size() - 1 > repaymentIndex) {
LoanRepaymentPeriodData nextRepaymentSchedule = loanRepaymentPeriods
.get(repaymentIndex);
arrearsDate = nextRepaymentSchedule.getDate().minusDays(1);
}
if (loanRepaymentPeriods.size() - 1 == repaymentIndex) {
arrearsDate = today;
}
return arrearsDate;
}
private LocalDate determineInArrearsFrom(
LoanRepaymentPeriodData currentRepaymentSchedule,
List<LoanRepaymentPeriodData> loanRepaymentPeriods,
int repaymentIndex) {
int previousScheduleIndex = repaymentIndex - 1;
LocalDate arrearsDate = null;
if (currentRepaymentSchedule.getLastAffectingPaymentOn() != null) {
if (currentRepaymentSchedule.getLastAffectingPaymentOn().isAfter(
currentRepaymentSchedule.getDate())) {
if (loanRepaymentPeriods.size() - 1 > previousScheduleIndex && previousScheduleIndex > -1) {
LoanRepaymentPeriodData lastRepaymentSchedule = loanRepaymentPeriods
.get(previousScheduleIndex);
arrearsDate = lastRepaymentSchedule.getDate().plusDays(1);
} else {
arrearsDate = currentRepaymentSchedule.getDate().minusMonths(1);
}
} else {
arrearsDate = currentRepaymentSchedule.getLastAffectingPaymentOn();
}
} else {
if (loanRepaymentPeriods.size() - 1 > previousScheduleIndex && previousScheduleIndex > -1) {
LoanRepaymentPeriodData lastRepaymentSchedule = loanRepaymentPeriods
.get(previousScheduleIndex);
arrearsDate = lastRepaymentSchedule.getDate().plusDays(1);
} else {
arrearsDate = currentRepaymentSchedule.getDate().minusMonths(1);
}
}
return arrearsDate;
}
private LoanAccountSummaryData calculateTotalsFromRepaymentScheduleData(
List<LoanRepaymentPeriodData> loanRepaymentPeriods, CurrencyData currencyData) {
MonetaryCurrency monetaryCurrency = new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces());
Money principalExpected = Money.zero(monetaryCurrency);
Money principalPaid = Money.zero(monetaryCurrency);
Money principalOutstanding = Money.zero(monetaryCurrency);
Money interestExpected = Money.zero(monetaryCurrency);
Money interestPaid = Money.zero(monetaryCurrency);
Money interestOutstanding = Money.zero(monetaryCurrency);
Money totalExpected = Money.zero(monetaryCurrency);
Money totalPaid = Money.zero(monetaryCurrency);
Money totalOutstanding = Money.zero(monetaryCurrency);
Money totalInArrears = Money.zero(monetaryCurrency);
Money totalWaived = Money.zero(monetaryCurrency);
for (LoanRepaymentPeriodData period : loanRepaymentPeriods) {
principalExpected = principalExpected.plus(moneyFrom(period.getPrincipal()));
principalPaid = principalPaid.plus(moneyFrom(period.getPrincipalPaid()));
principalOutstanding = principalOutstanding.plus(moneyFrom(period.getPrincipalOutstanding()));
interestExpected = interestExpected.plus(moneyFrom(period.getInterest()));
interestPaid = interestPaid.plus(moneyFrom(period.getInterestPaid()));
interestOutstanding = interestOutstanding.plus(moneyFrom(period.getInterestOutstanding()));
totalExpected = totalExpected.plus(moneyFrom(period.getTotal()));
totalPaid = totalPaid.plus(moneyFrom(period.getTotalPaid()));
totalOutstanding = totalOutstanding.plus(moneyFrom(period.getTotalOutstanding()));
if (period.getTotalWaived() != null) {
totalWaived = totalWaived.plus(moneyFrom(period.getTotalWaived()));
}
if (period.getTotalArrears() != null) {
totalInArrears = totalInArrears.plus(moneyFrom(period.getTotalArrears()));
}
}
return new LoanAccountSummaryData(moneyDataFrom(currencyData, principalExpected), moneyDataFrom(currencyData, principalPaid),
moneyDataFrom(currencyData, principalOutstanding), moneyDataFrom(currencyData, interestExpected),
moneyDataFrom(currencyData, interestPaid), moneyDataFrom(currencyData, interestOutstanding),
moneyDataFrom(currencyData, totalExpected), moneyDataFrom(currencyData, totalPaid), moneyDataFrom(currencyData, totalOutstanding),
moneyDataFrom(currencyData, totalInArrears), moneyDataFrom(currencyData, totalWaived));
}
/**
* TODO - pays off interest first then principal so should make this configurable when generating 'derived view of loan account'
*/
private LoanTransactionData processLoanRepayment(LoanTransaction repayment,
LoanRepaymentPeriodData scheduledRepayment,
int repaymentScheduleIndex,
List<LoanRepaymentPeriodData> repaymentSchedulePeriods,
BigDecimal arrearsTolerance, CurrencyData currencyDetail) {
MoneyData zero = MoneyData.zero(currencyDetail);
MoneyData cumulativeInterestPaid = MoneyData.zero(currencyDetail);
MoneyData cumulativePrincipalPaid = MoneyData.zero(currencyDetail);
LoanRepaymentPeriodData currentRepaymentSchedule = scheduledRepayment;
int currentRepaymentScheduleIndex = repaymentScheduleIndex;
MoneyData overpaid = null;
MonetaryCurrency currency = new MonetaryCurrency(currencyDetail.getCode(), currencyDetail.getDecimalPlaces());
Money remaining = repayment.getAmount(currency);
while (remaining.isGreaterThanZero()) {
Money interestOutstanding = moneyFrom(currentRepaymentSchedule.getInterestOutstanding());
Money principalOutstanding = moneyFrom(currentRepaymentSchedule.getPrincipalOutstanding());
// pay off interest component
if (remaining.isGreaterThanOrEqualTo(interestOutstanding)) {
Money previousInterestPaid = moneyFrom(cumulativeInterestPaid);
cumulativeInterestPaid = moneyDataFrom(currencyDetail, previousInterestPaid.plus(interestOutstanding));
MoneyData interestPaid = moneyDataFrom(currencyDetail, interestOutstanding);
Money paidToDate = moneyFrom(currentRepaymentSchedule.getInterestPaid());
Money amountPaid = moneyFrom(interestPaid);
paidToDate = paidToDate.plus(amountPaid);
MoneyData totalInterest = moneyDataFrom(currencyDetail, paidToDate);
currentRepaymentSchedule.setInterestPaid(totalInterest);
currentRepaymentSchedule.setInterestOutstanding(zero);
Money totalPaidToDate = moneyFrom(currentRepaymentSchedule.getTotalPaid());
totalPaidToDate = totalPaidToDate.plus(interestOutstanding);
currentRepaymentSchedule.setTotalPaid(moneyDataFrom(currencyDetail, totalPaidToDate));
Money outstanding = moneyFrom(currentRepaymentSchedule.getTotal()).minus(moneyFrom(currentRepaymentSchedule.getTotalPaid()));
currentRepaymentSchedule.setTotalOutstanding(moneyDataFrom(currencyDetail, outstanding));
if (interestOutstanding.isGreaterThanZero()) {
currentRepaymentSchedule.setLastAffectingPaymentOn(repayment.getTransactionDate());
}
remaining = remaining.minus(interestOutstanding);
} else {
// partial payment of principal
Money previousInterestPaid = moneyFrom(cumulativeInterestPaid);
cumulativeInterestPaid = moneyDataFrom(currencyDetail, previousInterestPaid.plus(remaining));
MoneyData interestPaid = moneyDataFrom(currencyDetail, remaining);
Money paidToDate = moneyFrom(currentRepaymentSchedule.getInterestPaid());
Money amountPaid = moneyFrom(interestPaid);
paidToDate = paidToDate.plus(amountPaid);
MoneyData totalInterest = moneyDataFrom(currencyDetail, paidToDate);
currentRepaymentSchedule.setInterestPaid(totalInterest);
interestOutstanding = interestOutstanding.minus(amountPaid);
currentRepaymentSchedule.setInterestOutstanding(moneyDataFrom(currencyDetail, interestOutstanding));
Money totalPaidToDate = moneyFrom(currentRepaymentSchedule.getTotalPaid());
totalPaidToDate = totalPaidToDate.plus(amountPaid);
currentRepaymentSchedule.setTotalPaid(moneyDataFrom(currencyDetail, totalPaidToDate));
Money outstanding = moneyFrom(currentRepaymentSchedule.getTotal()).minus(moneyFrom(currentRepaymentSchedule.getTotalPaid()));
currentRepaymentSchedule.setTotalOutstanding(moneyDataFrom(currencyDetail, outstanding));
if (interestPaid.isGreaterThanZero()) {
currentRepaymentSchedule.setLastAffectingPaymentOn(repayment.getTransactionDate());
}
remaining = remaining.minus(amountPaid);
}
// pay off principal component
if (remaining.isGreaterThanOrEqualTo(principalOutstanding)) {
Money previousPrincipalPaid = moneyFrom(cumulativePrincipalPaid);
cumulativePrincipalPaid = moneyDataFrom(currencyDetail, previousPrincipalPaid.plus(principalOutstanding));
MoneyData principalPaid = moneyDataFrom(currencyDetail, principalOutstanding);
Money paidToDate = moneyFrom(currentRepaymentSchedule.getPrincipalPaid());
Money amountPaid = moneyFrom(principalPaid);
paidToDate = paidToDate.plus(amountPaid);
MoneyData totalPrincipal = moneyDataFrom(currencyDetail, paidToDate);
currentRepaymentSchedule.setPrincipalPaid(totalPrincipal);
currentRepaymentSchedule.setPrincipalOutstanding(zero);
Money totalPaidToDate = moneyFrom(currentRepaymentSchedule.getTotalPaid());
totalPaidToDate = totalPaidToDate.plus(principalOutstanding);
currentRepaymentSchedule.setTotalPaid(moneyDataFrom(currencyDetail, totalPaidToDate));
Money outstanding = moneyFrom(currentRepaymentSchedule.getTotal()).minus(moneyFrom(currentRepaymentSchedule.getTotalPaid()));
currentRepaymentSchedule.setTotalOutstanding(moneyDataFrom(currencyDetail, outstanding));
remaining = remaining.minus(principalOutstanding);
} else {
// partial payment of principal
Money previousPrincipalPaid = moneyFrom(cumulativePrincipalPaid);
cumulativePrincipalPaid = moneyDataFrom(currencyDetail, previousPrincipalPaid.plus(remaining));
MoneyData principalPaid = moneyDataFrom(currencyDetail, remaining);
Money paidToDate = moneyFrom(currentRepaymentSchedule.getPrincipalPaid());
Money amountPaid = moneyFrom(principalPaid);
paidToDate = paidToDate.plus(amountPaid);
MoneyData totalPrincipal = moneyDataFrom(currencyDetail, paidToDate);
currentRepaymentSchedule.setPrincipalPaid(totalPrincipal);
principalOutstanding = principalOutstanding.minus(amountPaid);
currentRepaymentSchedule.setPrincipalOutstanding(moneyDataFrom(currencyDetail, principalOutstanding));
Money totalPaidToDate = moneyFrom(currentRepaymentSchedule.getTotalPaid());
totalPaidToDate = totalPaidToDate.plus(amountPaid);
currentRepaymentSchedule.setTotalPaid(moneyDataFrom(currencyDetail, totalPaidToDate));
Money outstanding = moneyFrom(currentRepaymentSchedule.getTotal()).minus(moneyFrom(currentRepaymentSchedule.getTotalPaid()));
currentRepaymentSchedule.setTotalOutstanding(moneyDataFrom(currencyDetail, outstanding));
if (principalPaid.isGreaterThanZero()) {
currentRepaymentSchedule.setLastAffectingPaymentOn(repayment.getTransactionDate());
}
remaining = remaining.minus(amountPaid);
}
// mark schedule as paid in full
if (currentRepaymentSchedule.isFullyPaid()) {
currentRepaymentSchedule.setPaidInFullOn(repayment.getTransactionDate());
}
// check if this installment has
if (currentRepaymentSchedule.isInArrearsWithToleranceOf(MoneyData.of(currencyDetail, arrearsTolerance), repayment.getTransactionDate())) {
currentRepaymentSchedule.setTotalArrears(currentRepaymentSchedule.getTotalOutstanding());
} else {
currentRepaymentSchedule.setTotalArrears(MoneyData.zero(currencyDetail));
}
if (remaining.isGreaterThanZero()) {
currentRepaymentScheduleIndex++;
if (repaymentSchedulePeriods.size() > currentRepaymentScheduleIndex) {
currentRepaymentSchedule = repaymentSchedulePeriods.get(currentRepaymentScheduleIndex);
} else {
// loan has been overpaid by an amount so
overpaid = moneyDataFrom(currencyDetail, remaining);
// note - maybe should set the overpayment 'amount' against repayment period.
remaining = Money.zero(remaining.getCurrency());
}
}
}
Money totalInterestComponent = moneyFrom(cumulativeInterestPaid);
Money totalPrincipalComponent= moneyFrom(cumulativePrincipalPaid);
MoneyData totalPaid = moneyDataFrom(currencyDetail, totalPrincipalComponent.plus(totalInterestComponent));
EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
LoanTransactionData loanRepaymentData = new LoanTransactionData(repayment.getId(), transactionType, repayment.getTransactionDate(), cumulativePrincipalPaid, cumulativeInterestPaid, totalPaid, overpaid);
return loanRepaymentData;
}
private LoanRepaymentScheduleData generateRepaymentScheduleData(
List<LoanRepaymentScheduleInstallment> repaymentSchedulePeriods,
CurrencyData currencyData) {
MoneyData zero = MoneyData.zero(currencyData);
MonetaryCurrency currency = new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces());
List<LoanRepaymentPeriodData> loanScheduleDetails = new ArrayList<LoanRepaymentPeriodData>();
for (LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedulePeriods) {
MoneyData principal = MoneyData.of(currencyData, scheduledRepayment.getPrincipal(currency).getAmount());
MoneyData interest = MoneyData.of(currencyData, scheduledRepayment.getInterest(currency).getAmount());
MoneyData total = MoneyData.of(currencyData, scheduledRepayment.getTotal(currency).getAmount());
LoanRepaymentPeriodData periodData = new LoanRepaymentPeriodData(
scheduledRepayment.getInstallmentNumber(),
scheduledRepayment.getDueDate(), zero, principal, interest,
total, total);
loanScheduleDetails.add(periodData);
}
return new LoanRepaymentScheduleData(loanScheduleDetails);
}
private Money moneyFrom(MoneyData money) {
MonetaryCurrency monetaryCurrency = new MonetaryCurrency(money.getCurrencyCode(), money.getDigitsAfterDecimal());
return Money.of(monetaryCurrency, money.getAmount());
}
private MoneyData moneyDataFrom(CurrencyData currencyData, Money money) {
return MoneyData.of(currencyData, money.getAmount());
}
}
@@ -5,9 +5,9 @@ import java.util.Collection;
import org.mifosng.platform.api.data.LoanAccountSummaryData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanRepaymentPeriodDatajpw;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionDatajpw;
import org.mifosng.platform.api.data.LoanTransactionDataDTO;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.NewLoanData;
@@ -15,13 +15,13 @@ public interface LoanReadPlatformService {
LoanBasicDetailsData retrieveLoanAccountDetails(Long loanId);
Collection<LoanRepaymentPeriodDatajpw> retrieveRepaymentSchedule(Long loanId);
Collection<LoanRepaymentPeriodData> retrieveRepaymentSchedule(Long loanId);
Collection<LoanTransactionDatajpw> retrieveLoanPayments(Long loanId);
Collection<LoanTransactionDataDTO> retrieveLoanPayments(Long loanId);
LoanAccountSummaryData retrieveSummary(MoneyData principal,
Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule,
Collection<LoanTransactionDatajpw> loanRepayments);
Collection<LoanRepaymentPeriodData> repaymentSchedule,
Collection<LoanTransactionDataDTO> loanRepayments);
LoanPermissionData retrieveLoanPermissions(
LoanBasicDetailsData loanBasicDetails, boolean isWaiverAllowed,
@@ -15,9 +15,9 @@ import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanProductLookup;
import org.mifosng.platform.api.data.LoanRepaymentPeriodDatajpw;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionDatajpw;
import org.mifosng.platform.api.data.LoanTransactionDataDTO;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.NewLoanData;
import org.mifosng.platform.client.service.ClientReadPlatformService;
@@ -90,7 +90,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public Collection<LoanRepaymentPeriodDatajpw> retrieveRepaymentSchedule(
public Collection<LoanRepaymentPeriodData> retrieveRepaymentSchedule(
Long loanId) {
try {
@@ -108,8 +108,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanAccountSummaryData retrieveSummary(MoneyData principal,
Collection<LoanRepaymentPeriodDatajpw> repaymentSchedule,
Collection<LoanTransactionDatajpw> loanRepayments) {
Collection<LoanRepaymentPeriodData> repaymentSchedule,
Collection<LoanTransactionDataDTO> loanRepayments) {
CurrencyData currencyData = new CurrencyData(
principal.getCurrencyCode(), principal.getDefaultName(),
@@ -128,7 +128,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
BigDecimal totalInArrears = BigDecimal.ZERO;
BigDecimal totalWaived = BigDecimal.ZERO;
for (LoanRepaymentPeriodDatajpw installment : repaymentSchedule) {
for (LoanRepaymentPeriodData installment : repaymentSchedule) {
originalPrincipal = originalPrincipal.add(installment
.getPrincipal().getAmount());
principalPaid = principalPaid.add(installment.getPrincipalPaid()
@@ -149,12 +149,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
totalOutstanding = totalOutstanding.add(installment
.getTotalOutstanding().getAmount());
if (installment.getDate().isBefore(new LocalDate())) {
totalInArrears = totalInArrears.add(installment
.getTotalOutstanding().getAmount());
}
}
totalInArrears = BigDecimal.TEN;
Long waiverType = (long) 4;
for (LoanTransactionDatajpw loanRepayment : loanRepayments) {
for (LoanTransactionDataDTO loanRepayment : loanRepayments) {
Long transactionType = loanRepayment.getTransactionType().getId();
if (transactionType.equals(waiverType)) {
totalWaived = totalWaived.add(loanRepayment.getTotal()
@@ -176,7 +178,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public Collection<LoanTransactionDatajpw> retrieveLoanPayments(Long loanId) {
public Collection<LoanTransactionDataDTO> retrieveLoanPayments(Long loanId) {
try {
context.authenticatedUser();
@@ -214,7 +216,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
boolean undoApprovalAllowed = waitingForDisbursal;
boolean undoDisbursalAllowed = isActive && (repaymentAndWaiveCount == 0);
boolean undoDisbursalAllowed = isActive
&& (repaymentAndWaiveCount == 0);
boolean disbursalAllowed = waitingForDisbursal;
@@ -524,7 +527,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
private static final class LoanScheduleMapper implements
RowMapper<LoanRepaymentPeriodDatajpw> {
RowMapper<LoanRepaymentPeriodData> {
public String loanScheduleSchema() {
@@ -538,7 +541,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public LoanRepaymentPeriodDatajpw mapRow(final ResultSet rs,
public LoanRepaymentPeriodData mapRow(final ResultSet rs,
final int rowNum) throws SQLException {
String currencyCode = rs.getString("currencyCode");
@@ -579,7 +582,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
principalBD.add(interestBD).subtract(principalPaidBD)
.subtract(interestPaidBD));
return new LoanRepaymentPeriodDatajpw(loanId, period, date,
return new LoanRepaymentPeriodData(loanId, period, date,
principal, principalPaid, principalOutstanding, interest,
interestPaid, interestOutstanding, total, totalPaid,
totalOutstanding);
@@ -587,7 +590,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
private static final class LoanPaymentsMapper implements
RowMapper<LoanTransactionDatajpw> {
RowMapper<LoanTransactionDataDTO> {
public String LoanPaymentsSchema() {
@@ -599,7 +602,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public LoanTransactionDatajpw mapRow(final ResultSet rs,
public LoanTransactionDataDTO mapRow(final ResultSet rs,
final int rowNum) throws SQLException {
String currencyCode = rs.getString("currencyCode");
@@ -622,7 +625,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
BigDecimal totalBD = rs.getBigDecimal("total");
MoneyData total = MoneyData.of(currencyData, totalBD);
return new LoanTransactionDatajpw(id, transactionType, date, total);
return new LoanTransactionDataDTO(id, transactionType, date, total);
}
}
@@ -1,376 +0,0 @@
package org.mifosng.platform.loan.domain;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DerivedLoanData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
import org.mifosng.platform.api.data.LoanRepaymentScheduleData;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DerivedLoanDataProcessorTest {
private DerivedLoanDataProcessor loanDataProcessor = new DerivedLoanDataProcessor();
private MonetaryCurrency cfaCurrency = new MonetaryCurrencyBuilder().withCode("XOF").withDigitsAfterDecimal(0).build();
private Money arrearsTolerance = new MoneyBuilder().with(cfaCurrency).with("0.0").build();
@Test
public void shouldDeriveLoanRepaymentScheduleWithSingleRepaymentPeriodAndRepaymentInFull() {
List<LoanRepaymentScheduleInstallment> repaymentScheduleWithSinglePeriod = new ArrayList<LoanRepaymentScheduleInstallment>();
LoanRepaymentScheduleInstallment firstRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now())
.build();
repaymentScheduleWithSinglePeriod.add(firstRepaymentPeriod);
List<LoanTransaction> singleRepaymentInFull = new ArrayList<LoanTransaction>();
Money fullAmount = new MoneyBuilder().with(cfaCurrency).with("1010").build();
LoanTransaction fullRepayment = new LoanTransactionBuilder().with(fullAmount).build();
singleRepaymentInFull.add(fullRepayment);
// exercise test
DerivedLoanData derivedData = loanDataProcessor.process(repaymentScheduleWithSinglePeriod, singleRepaymentInFull, asCurrencyData(cfaCurrency), arrearsTolerance);
// verification
assertThat(derivedData, is(notNullValue()));
LoanRepaymentScheduleData repaymentSchedule = derivedData.getRepaymentSchedule();
assertThat(repaymentSchedule, is(notNullValue()));
List<LoanRepaymentPeriodData> periods = repaymentSchedule.getPeriods();
assertThat(periods, is(notNullValue()));
assertThat(periods.size(), is(1));
LoanRepaymentPeriodData periodData = periods.get(0);
MoneyData expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedTotal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1010").build());
assertThat(periodData.getPrincipal(), is(expectedPrincipal));
assertThat(periodData.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(periodData.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(periodData.getInterest(), is(expectedInterest));
assertThat(periodData.getInterestPaid(), is(expectedInterestPaid));
assertThat(periodData.getInterestOutstanding(), is(expectedInterestOutstanding));
// assert repayments are ok
List<LoanTransactionData> loanRepayments = derivedData.getLoanRepayments();
assertThat(loanRepayments, is(notNullValue()));
assertThat(loanRepayments.size(), is(1));
LoanTransactionData firstRepayment = loanRepayments.get(0);
assertThat(firstRepayment.getPrincipal(), is(expectedPrincipalPaid));
assertThat(firstRepayment.getInterest(), is(expectedInterestPaid));
assertThat(firstRepayment.getTotal(), is(expectedTotal));
}
private CurrencyData asCurrencyData(MonetaryCurrency currency) {
return new CurrencyData(currency.getCode(), "", currency.getDigitsAfterDecimal(), "CFA", "");
}
@Test
public void shouldDeriveLoanRepaymentScheduleWithSingleRepaymentPeriodAndPartialRepayment() {
List<LoanRepaymentScheduleInstallment> repaymentScheduleWithSinglePeriod = new ArrayList<LoanRepaymentScheduleInstallment>();
LoanRepaymentScheduleInstallment firstRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now())
.build();
repaymentScheduleWithSinglePeriod.add(firstRepaymentPeriod);
List<LoanTransaction> singlePartialRepayment = new ArrayList<LoanTransaction>();
Money partialAmount = new MoneyBuilder().with(cfaCurrency).with("510").build();
LoanTransaction partialRepayment = new LoanTransactionBuilder().with(partialAmount).build();
singlePartialRepayment.add(partialRepayment);
// exercise test
DerivedLoanData derivedData = loanDataProcessor.process(repaymentScheduleWithSinglePeriod, singlePartialRepayment, asCurrencyData(cfaCurrency), arrearsTolerance);
// verification
assertThat(derivedData, is(notNullValue()));
LoanRepaymentScheduleData repaymentSchedule = derivedData.getRepaymentSchedule();
assertThat(repaymentSchedule, is(notNullValue()));
List<LoanRepaymentPeriodData> periods = repaymentSchedule.getPeriods();
assertThat(periods, is(notNullValue()));
assertThat(periods.size(), is(1));
LoanRepaymentPeriodData periodData = periods.get(0);
MoneyData expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("500").build());
MoneyData expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("500").build());
MoneyData expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedTotal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("510").build());
assertThat(periodData.getPrincipal(), is(expectedPrincipal));
assertThat(periodData.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(periodData.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(periodData.getInterest(), is(expectedInterest));
assertThat(periodData.getInterestPaid(), is(expectedInterestPaid));
assertThat(periodData.getInterestOutstanding(), is(expectedInterestOutstanding));
// assert repayments are ok
List<LoanTransactionData> loanRepayments = derivedData.getLoanRepayments();
assertThat(loanRepayments, is(notNullValue()));
assertThat(loanRepayments.size(), is(1));
LoanTransactionData firstRepayment = loanRepayments.get(0);
assertThat(firstRepayment.getPrincipal(), is(expectedPrincipalPaid));
assertThat(firstRepayment.getInterest(), is(expectedInterestPaid));
assertThat(firstRepayment.getTotal(), is(expectedTotal));
}
@Test
public void shouldDeriveLoanRepaymentScheduleWithSingleRepaymentPeriodAndPartialRepaymentLessThanInterestComponent() {
List<LoanRepaymentScheduleInstallment> repaymentScheduleWithSinglePeriod = new ArrayList<LoanRepaymentScheduleInstallment>();
LoanRepaymentScheduleInstallment firstRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now())
.build();
repaymentScheduleWithSinglePeriod.add(firstRepaymentPeriod);
List<LoanTransaction> singlePartialRepayment = new ArrayList<LoanTransaction>();
Money partialAmount = new MoneyBuilder().with(cfaCurrency).with("5").build();
LoanTransaction partialRepayment = new LoanTransactionBuilder().with(partialAmount).build();
singlePartialRepayment.add(partialRepayment);
// exercise test
DerivedLoanData derivedData = loanDataProcessor.process(repaymentScheduleWithSinglePeriod, singlePartialRepayment, asCurrencyData(cfaCurrency), arrearsTolerance);
// verification
assertThat(derivedData, is(notNullValue()));
LoanRepaymentScheduleData repaymentSchedule = derivedData.getRepaymentSchedule();
assertThat(repaymentSchedule, is(notNullValue()));
List<LoanRepaymentPeriodData> periods = repaymentSchedule.getPeriods();
assertThat(periods, is(notNullValue()));
assertThat(periods.size(), is(1));
LoanRepaymentPeriodData periodData = periods.get(0);
MoneyData expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("5").build());
MoneyData expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("5").build());
MoneyData expectedTotal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("5").build());
assertThat(periodData.getPrincipal(), is(expectedPrincipal));
assertThat(periodData.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(periodData.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(periodData.getInterest(), is(expectedInterest));
assertThat(periodData.getInterestPaid(), is(expectedInterestPaid));
assertThat(periodData.getInterestOutstanding(), is(expectedInterestOutstanding));
// assert repayments are ok
List<LoanTransactionData> loanRepayments = derivedData.getLoanRepayments();
assertThat(loanRepayments, is(notNullValue()));
assertThat(loanRepayments.size(), is(1));
LoanTransactionData firstRepayment = loanRepayments.get(0);
assertThat(firstRepayment.getPrincipal(), is(expectedPrincipalPaid));
assertThat(firstRepayment.getInterest(), is(expectedInterestPaid));
assertThat(firstRepayment.getTotal(), is(expectedTotal));
}
@Test
public void shouldDeriveLoanRepaymentScheduleWithSingleRepaymentPeriodAndOverPayment() {
List<LoanRepaymentScheduleInstallment> repaymentScheduleWithSinglePeriod = new ArrayList<LoanRepaymentScheduleInstallment>();
LoanRepaymentScheduleInstallment firstRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now())
.build();
repaymentScheduleWithSinglePeriod.add(firstRepaymentPeriod);
List<LoanTransaction> singleOverRepayment = new ArrayList<LoanTransaction>();
Money partialAmount = new MoneyBuilder().with(cfaCurrency).with("1200").build();
LoanTransaction overRepayment = new LoanTransactionBuilder().with(partialAmount).build();
singleOverRepayment.add(overRepayment);
// exercise test
DerivedLoanData derivedData = loanDataProcessor.process(repaymentScheduleWithSinglePeriod, singleOverRepayment, asCurrencyData(cfaCurrency), arrearsTolerance);
// verification
assertThat(derivedData, is(notNullValue()));
LoanRepaymentScheduleData repaymentSchedule = derivedData.getRepaymentSchedule();
assertThat(repaymentSchedule, is(notNullValue()));
List<LoanRepaymentPeriodData> periods = repaymentSchedule.getPeriods();
assertThat(periods, is(notNullValue()));
assertThat(periods.size(), is(1));
LoanRepaymentPeriodData periodData = periods.get(0);
MoneyData expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedTotal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1010").build());
MoneyData expectedOverpayment = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("190").build());
assertThat(periodData.getPrincipal(), is(expectedPrincipal));
assertThat(periodData.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(periodData.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(periodData.getInterest(), is(expectedInterest));
assertThat(periodData.getInterestPaid(), is(expectedInterestPaid));
assertThat(periodData.getInterestOutstanding(), is(expectedInterestOutstanding));
// assert repayments are ok
List<LoanTransactionData> loanRepayments = derivedData.getLoanRepayments();
assertThat(loanRepayments, is(notNullValue()));
assertThat(loanRepayments.size(), is(1));
LoanTransactionData firstRepayment = loanRepayments.get(0);
assertThat(firstRepayment.getPrincipal(), is(expectedPrincipalPaid));
assertThat(firstRepayment.getInterest(), is(expectedInterestPaid));
assertThat(firstRepayment.getTotal(), is(expectedTotal));
assertThat(firstRepayment.getOverpaid(), is(expectedOverpayment));
}
@Test
public void shouldDeriveLoanRepaymentScheduleWithTwoRepaymentPeriodAndOneSinglePayment() {
List<LoanRepaymentScheduleInstallment> repaymentScheduleWithSinglePeriod = new ArrayList<LoanRepaymentScheduleInstallment>();
LoanRepaymentScheduleInstallment firstRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now())
.build();
LoanRepaymentScheduleInstallment secondRepaymentPeriod = new LoanRepaymentScheduleInstallmentBuilder(cfaCurrency)
.withPrincipal("1000")
.withInterest("10")
.withDueDate(LocalDate.now().plusWeeks(1))
.build();
repaymentScheduleWithSinglePeriod.add(firstRepaymentPeriod);
repaymentScheduleWithSinglePeriod.add(secondRepaymentPeriod);
List<LoanTransaction> singleRepayment = new ArrayList<LoanTransaction>();
Money partialAmount = new MoneyBuilder().with(cfaCurrency).with("1200").build();
LoanTransaction overRepaymentForFirstPeriod = new LoanTransactionBuilder().with(partialAmount).build();
singleRepayment.add(overRepaymentForFirstPeriod);
// exercise test
DerivedLoanData derivedData = loanDataProcessor.process(repaymentScheduleWithSinglePeriod, singleRepayment, asCurrencyData(cfaCurrency), arrearsTolerance);
// verification
assertThat(derivedData, is(notNullValue()));
LoanRepaymentScheduleData repaymentSchedule = derivedData.getRepaymentSchedule();
assertThat(repaymentSchedule, is(notNullValue()));
List<LoanRepaymentPeriodData> periods = repaymentSchedule.getPeriods();
assertThat(periods, is(notNullValue()));
assertThat(periods.size(), is(2));
LoanRepaymentPeriodData firstPeriod = periods.get(0);
MoneyData expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
MoneyData expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
MoneyData expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
MoneyData expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
assertThat(firstPeriod.getPrincipal(), is(expectedPrincipal));
assertThat(firstPeriod.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(firstPeriod.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(firstPeriod.getInterest(), is(expectedInterest));
assertThat(firstPeriod.getInterestPaid(), is(expectedInterestPaid));
assertThat(firstPeriod.getInterestOutstanding(), is(expectedInterestOutstanding));
LoanRepaymentPeriodData secondPeriod = periods.get(1);
expectedPrincipal = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1000").build());
expectedPrincipalPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("180").build());
expectedPrincipalOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("820").build());
expectedInterest = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
expectedInterestPaid = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("10").build());
expectedInterestOutstanding = moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("0").build());
assertThat(secondPeriod.getPrincipal(), is(expectedPrincipal));
assertThat(secondPeriod.getPrincipalPaid(), is(expectedPrincipalPaid));
assertThat(secondPeriod.getPrincipalOutstanding(), is(expectedPrincipalOutstanding));
assertThat(secondPeriod.getInterest(), is(expectedInterest));
assertThat(secondPeriod.getInterestPaid(), is(expectedInterestPaid));
assertThat(secondPeriod.getInterestOutstanding(), is(expectedInterestOutstanding));
// assert repayments are ok
List<LoanTransactionData> loanRepayments = derivedData.getLoanRepayments();
assertThat(loanRepayments, is(notNullValue()));
assertThat(loanRepayments.size(), is(1));
LoanTransactionData firstRepayment = loanRepayments.get(0);
assertThat(firstRepayment.getPrincipal(), is(moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1180").build())));
assertThat(firstRepayment.getInterest(), is(moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("20").build())));
assertThat(firstRepayment.getTotal(), is(moneyDataFrom(new MoneyBuilder().with(cfaCurrency).with("1200").build())));
}
private MoneyData moneyDataFrom(Money money) {
CurrencyData currency = new CurrencyData(money.getCurrencyCode(), "", money.getCurrencyDigitsAfterDecimal(), "CFA", "");
return MoneyData.of(currency, money.getAmount());
}
}