mifosx-61: support waive of interest along with loan write off capability. refactor loanRepayments as transactions on api.

This commit is contained in:
Keith Woodlock
2012-10-09 14:53:34 +01:00
parent a6a72541eb
commit b97f02ef8e
39 changed files with 611 additions and 274 deletions
@@ -0,0 +1,3 @@
alter table m_loan_repayment_schedule add column principal_writtenoff_derived decimal(19,6) DEFAULT NULL after principal_completed_derived;
alter table m_loan_repayment_schedule add column interest_writtenoff_derived decimal(19,6) DEFAULT NULL after interest_completed_derived;
@@ -14,42 +14,57 @@ import org.mifosng.platform.api.data.LoanSchedulePeriodData;
* <li>repayment schedule - the principal due, outstanding balance and cost of loan items such as interest and charges</li>
* </ul>
*/
// TODO - KW - rename file
@SuppressWarnings("unused")
public class LoanScheduleNewData {
public class LoanScheduleData {
@SuppressWarnings("unused")
private final Integer loanTermInDays;
/**
* The currency associated with all monetary values in loan schedule.
*/
@SuppressWarnings("unused")
private final CurrencyData currency;
/**
* The cumulative total of all money (principal) disbursed to the loan applicant.
*/
@SuppressWarnings("unused")
private final BigDecimal cumulativePrincipalDisbursed;
/**
* The cumulative total of all <code>principalDue</code> for each period of loan schedule. (originalPrincipal)
*/
@SuppressWarnings("unused")
private final BigDecimal cumulativePrincipalDue;
@SuppressWarnings("unused")
private final BigDecimal cumulativePrincipalPaid;
@SuppressWarnings("unused")
private final BigDecimal cumulativePrincipalWrittenOff;
@SuppressWarnings("unused")
private final BigDecimal cumulativePrincipalOutstanding;
/**
* The cumulative total of interest expected on any principal disbursed. (originalInterest)
*/
@SuppressWarnings("unused")
private final BigDecimal cumulativeInterestExpected;
@SuppressWarnings("unused")
private final BigDecimal cumulativeInterestPaid;
@SuppressWarnings("unused")
private final BigDecimal cumulativeInterestWaived;
@SuppressWarnings("unused")
private final BigDecimal cumulativeInterestWrittenOff;
@SuppressWarnings("unused")
private final BigDecimal cumulativeInterestOutstanding;
/**
* The cumulative total of all charges applied on the loan to date.
*/
@SuppressWarnings("unused")
private final BigDecimal cumulativeChargesToDate;
@SuppressWarnings("unused")
private final BigDecimal cumulativeChargesPaid;
@SuppressWarnings("unused")
private final BigDecimal cumulativeChargesOutstanding;
/**
@@ -60,23 +75,33 @@ public class LoanScheduleNewData {
* <li>Charges (fees and penalties)</li>
* </ul>
*/
@SuppressWarnings("unused")
private final BigDecimal totalCostOfLoan;
/**
* The sum of <code>principalDisbursed</code> and <code>totalCostOfLoan</code>. (originalTotal)
*/
@SuppressWarnings("unused")
private final BigDecimal totalExpectedRepayment;
/**
* The cumulative sum of all repayments to date. (totalPaid)
*/
@SuppressWarnings("unused")
private final BigDecimal totalPaidToDate;
/**
* The cumulative sum of all waivers to date.
*/
@SuppressWarnings("unused")
private final BigDecimal totalWaivedToDate;
/**
* The cumulative sum of all waivers to date.
*/
@SuppressWarnings("unused")
private final BigDecimal totalWrittenOffToDate;
/**
* The cumulative sum of all principal, interest & charges outstanding.
*/
@@ -87,6 +112,7 @@ public class LoanScheduleNewData {
*
* Different to outstanding as arrears is based on whether a portion of the total outstanding is late and above any tolerance setting.
*/
@SuppressWarnings("unused")
private final BigDecimal totalInArrears;
/**
@@ -96,15 +122,17 @@ public class LoanScheduleNewData {
*/
private final Collection<LoanSchedulePeriodData> periods;
public LoanScheduleNewData() {
public LoanScheduleData() {
this.cumulativePrincipalDisbursed = null;
this.cumulativePrincipalPaid = null;
this.cumulativePrincipalWrittenOff = null;
this.cumulativePrincipalDue = null;
this.cumulativePrincipalOutstanding = null;
this.cumulativeInterestExpected = null;
this.cumulativeInterestPaid = null;
this.cumulativeInterestWaived = null;
this.cumulativeInterestWrittenOff = null;
this.cumulativeInterestOutstanding = null;
this.cumulativeChargesToDate = null;
@@ -116,6 +144,7 @@ public class LoanScheduleNewData {
this.totalExpectedRepayment = null;
this.totalPaidToDate = null;
this.totalWaivedToDate = null;
this.totalWrittenOffToDate = null;
this.totalOutstanding = null;
this.totalInArrears = null;
@@ -124,7 +153,7 @@ public class LoanScheduleNewData {
this.loanTermInDays = null;
}
public LoanScheduleNewData(
public LoanScheduleData(
final CurrencyData currency,
final Collection<LoanSchedulePeriodData> periods,
final Integer loanTermInDays,
@@ -141,11 +170,13 @@ public class LoanScheduleNewData {
this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed;
this.cumulativePrincipalDue = cumulativePrincipalDue;
this.cumulativePrincipalPaid = null;
this.cumulativePrincipalWrittenOff = null;
this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding;
this.cumulativeInterestExpected = cumulativeInterestExpected;
this.cumulativeInterestPaid = null;
this.cumulativeInterestWaived = null;
this.cumulativeInterestWrittenOff = null;
this.cumulativeInterestOutstanding = null;
this.cumulativeChargesToDate = cumulativeChargesToDate;
@@ -163,6 +194,7 @@ public class LoanScheduleNewData {
this.totalExpectedRepayment = totalExpectedRepayment;
this.totalPaidToDate = BigDecimal.ZERO;
this.totalWaivedToDate = BigDecimal.ZERO;
this.totalWrittenOffToDate = BigDecimal.ZERO;
this.totalOutstanding = totalExpectedRepayment;
this.totalInArrears = BigDecimal.ZERO;
}
@@ -170,15 +202,17 @@ public class LoanScheduleNewData {
/*
* Used when fully populating
*/
public LoanScheduleNewData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods,
public LoanScheduleData(final CurrencyData currency, final Collection<LoanSchedulePeriodData> periods,
final Integer loanTermInDays,
final BigDecimal cumulativePrincipalDisbursed,
final BigDecimal cumulativePrincipalDue,
final BigDecimal cumulativePrincipalPaid,
final BigDecimal cumulativePrincipalWrittenOff,
final BigDecimal cumulativePrincipalOutstanding,
final BigDecimal cumulativeInterestExpected,
final BigDecimal cumulativeInterestPaid,
final BigDecimal cumulativeInterestWaived,
final BigDecimal cumulativeInterestWrittenOff,
final BigDecimal cumulativeInterestOutstanding,
final BigDecimal cumulativeChargesToDate,
final BigDecimal cumulativeChargesPaid,
@@ -187,6 +221,7 @@ public class LoanScheduleNewData {
final BigDecimal totalExpectedRepayment,
final BigDecimal totalPaidToDate,
final BigDecimal totalWaivedToDate,
final BigDecimal totalWrittenOffToDate,
final BigDecimal totalOutstanding,
final BigDecimal totalInArrears) {
this.currency = currency;
@@ -196,9 +231,11 @@ public class LoanScheduleNewData {
this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed;
this.cumulativePrincipalDue = cumulativePrincipalDue;
this.cumulativePrincipalPaid = cumulativePrincipalPaid;
this.cumulativePrincipalWrittenOff = cumulativePrincipalWrittenOff;
this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding;
this.cumulativeInterestExpected = cumulativeInterestExpected;
this.cumulativeInterestPaid = cumulativeInterestPaid;
this.cumulativeInterestWrittenOff = cumulativeInterestWrittenOff;
this.cumulativeInterestWaived = cumulativeInterestWaived;
this.cumulativeInterestOutstanding = cumulativeInterestOutstanding;
this.cumulativeChargesToDate = cumulativeChargesToDate;
@@ -208,6 +245,7 @@ public class LoanScheduleNewData {
this.totalExpectedRepayment = totalExpectedRepayment;
this.totalPaidToDate = totalPaidToDate;
this.totalWaivedToDate = totalWaivedToDate;
this.totalWrittenOffToDate = totalWrittenOffToDate;
this.totalOutstanding = totalOutstanding;
this.totalInArrears = totalInArrears;
}
@@ -38,7 +38,6 @@ import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductLookup;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
import org.mifosng.platform.api.data.MoneyData;
@@ -191,8 +190,8 @@ public class LoansApiResource {
LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId);
int loanRepaymentsCount = 0;
Collection<LoanRepaymentTransactionData> loanRepayments = null;
LoanScheduleNewData repaymentSchedule = null;
Collection<LoanTransactionNewData> loanRepayments = null;
LoanScheduleData repaymentSchedule = null;
LoanPermissionData permissions = null;
Collection<LoanChargeData> charges = null;
@@ -201,18 +200,21 @@ public class LoansApiResource {
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
responseParameters.addAll(Arrays.asList("repaymentSchedule", "loanRepayments", "permissions", "convenienceData", "charges"));
responseParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "permissions", "convenienceData", "charges"));
} else {
responseParameters.addAll(associationParameters);
}
Collection<LoanRepaymentTransactionData> currentLoanRepayments = this.loanReadPlatformService.retrieveLoanPayments(loanId);
if (!CollectionUtils.isEmpty(currentLoanRepayments)) {
loanRepayments = currentLoanRepayments;
loanRepaymentsCount = loanRepayments.size();
if (responseParameters.contains("transactions")) {
Collection<LoanTransactionNewData> currentLoanRepayments = this.loanReadPlatformService.retrieveLoanTransactions(loanId);
if (!CollectionUtils.isEmpty(currentLoanRepayments)) {
loanRepayments = currentLoanRepayments;
loanRepaymentsCount = loanRepayments.size();
}
}
if (responseParameters.contains("repaymentSchedule")) {
DisbursementData singleDisbursement = loanBasicDetails.toDisburementData();
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, loanBasicDetails.getCurrency(),
singleDisbursement, loanBasicDetails.getTotalDisbursementCharges(), loanBasicDetails.getInArrearsTolerance());
@@ -275,7 +277,8 @@ public class LoansApiResource {
repaymentSchedule, loanRepayments, permissions, charges,
productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers);
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers);
return this.apiJsonSerializerService.serializeLoanAccountDataToJson(prettyPrint, responseParameters, loanAccount);
}
@@ -302,7 +305,7 @@ public class LoansApiResource {
private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) {
final LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command);
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command);
final Set<String> typicalLoanScheduleResponseParameters = new HashSet<String>(
Arrays.asList("periods", "cumulativePrincipalDisbursed"));
@@ -406,26 +409,24 @@ public class LoansApiResource {
@QueryParam("command") final String commandParam,
final String jsonRequestBody) {
final LoanTransactionCommand command = this.apiDataConversionService
.convertJsonToLoanTransactionCommand(loanId, jsonRequestBody);
Response response = null;
final LoanTransactionCommand command = this.apiDataConversionService.convertJsonToLoanTransactionCommand(loanId, jsonRequestBody);
EntityIdentifier identifier = null;
if (is(commandParam, "repayment")) {
EntityIdentifier identifier = this.loanWritePlatformService
.makeLoanRepayment(command);
response = Response.ok().entity(identifier).build();
identifier = this.loanWritePlatformService.makeLoanRepayment(command);
} else if (is(commandParam, "waiveinterest")) {
EntityIdentifier identifier = this.loanWritePlatformService
.waiveInterestOnLoan(command);
response = Response.ok().entity(identifier).build();
identifier = this.loanWritePlatformService.waiveInterestOnLoan(command);
} else if (is(commandParam, "writeoff")) {
identifier = this.loanWritePlatformService.writeOff(command);
} else if (is(commandParam, "closeasrescheduled")) {
identifier = this.loanWritePlatformService.closeAsRescheduled(command);
}
if (response == null) {
if (identifier == null) {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return response;
return Response.ok().entity(identifier).build();
}
@GET
@@ -440,8 +441,8 @@ public class LoansApiResource {
LoanTransactionData transactionData = null;
if (is(commandParam, "repayment")) {
Set<String> typicalResponseParameters = new HashSet<String>(
Arrays.asList("id", "transactionType", "date", "principal", "interest", "total", "totalWaived", "overpaid")
);
Arrays.asList("id", "transactionType", "date", "principal",
"interest", "total", "totalWaived", "overpaid"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
@@ -460,7 +461,29 @@ public class LoansApiResource {
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewLoanWaiveInterestDetails(loanId);
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewLoanWaiveInterestDetails(loanId);
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
} else if (is(commandParam, "writeoff")) {
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());
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
} else if (is(commandParam, "closeasrescheduled")) {
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());
final LoanTransactionNewData newTransactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
return this.apiJsonSerializerService.serializeLoanTransactionDataToJson(prettyPrint, responseParameters, newTransactionData);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
@@ -4,7 +4,7 @@ import java.math.BigDecimal;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
/**
* Immutable data object representing loan account data.
@@ -55,8 +55,8 @@ public class LoanAccountData {
private final LocalDate lifeCycleStatusDate;
// associations
private final LoanScheduleNewData repaymentSchedule;
private final Collection<LoanRepaymentTransactionData> loanRepayments;
private final LoanScheduleData repaymentSchedule;
private final Collection<LoanTransactionNewData> transactions;
private final Collection<LoanChargeData> charges;
private final LoanPermissionData permissions;
private final LoanConvenienceData convenienceData;
@@ -78,8 +78,8 @@ public class LoanAccountData {
public LoanAccountData(
final LoanBasicDetailsData basicDetails,
final boolean convenienceDataRequired,
final LoanScheduleNewData repaymentSchedule,
final Collection<LoanRepaymentTransactionData> loanRepayments,
final LoanScheduleData repaymentSchedule,
final Collection<LoanTransactionNewData> transactions,
final LoanPermissionData permissions,
final Collection<LoanChargeData> charges,
final Collection<LoanProductLookup> productOptions,
@@ -95,7 +95,7 @@ public class LoanAccountData {
final ChargeData chargeTemplate,
final Collection<StaffData> loanOfficerOptions) {
this.repaymentSchedule = repaymentSchedule;
this.loanRepayments = loanRepayments;
this.transactions = transactions;
this.permissions = permissions;
this.charges = charges;
this.productOptions = productOptions;
@@ -1,59 +0,0 @@
package org.mifosng.platform.api.data;
import org.joda.time.LocalDate;
/**
* This data object replaces {@link LoanTransactionData}.
*/
public class LoanRepaymentTransactionData {
private Long id;
private EnumOptionData transactionType;
private LocalDate date;
private MoneyData total;
public LoanRepaymentTransactionData() {
//
}
public LoanRepaymentTransactionData(Long id, EnumOptionData transactionType,
final LocalDate date, final MoneyData total) {
this.id = id;
this.transactionType = transactionType;
this.date = date;
this.total = total;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EnumOptionData getTransactionType() {
return transactionType;
}
public void setTransactionType(EnumOptionData transactionType) {
this.transactionType = transactionType;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public MoneyData getTotal() {
return total;
}
public void setTotal(MoneyData total) {
this.total = total;
}
}
@@ -9,7 +9,6 @@ import org.joda.time.LocalDate;
* Immutable data object that represents a period of a loan schedule.
*
*/
@SuppressWarnings("unused")
public class LoanSchedulePeriodData {
private final Integer period;
@@ -17,26 +16,37 @@ public class LoanSchedulePeriodData {
private final LocalDate dueDate;
private final Integer daysInPeriod;
private final BigDecimal principalDisbursed;
@SuppressWarnings("unused")
private final BigDecimal principalOriginalDue;
private final BigDecimal principalDue;
private final BigDecimal principalPaid;
private final BigDecimal principalWrittenOff;
private final BigDecimal principalOutstanding;
@SuppressWarnings("unused")
private final BigDecimal principalLoanBalanceOutstanding;
@SuppressWarnings("unused")
private final BigDecimal interestOriginalDue;
private final BigDecimal interestDue;
private final BigDecimal interestPaid;
private final BigDecimal interestWaived;
private final BigDecimal interestWrittenOff;
private final BigDecimal interestOutstanding;
private final BigDecimal chargesDue;
private final BigDecimal chargesPaid;
private final BigDecimal chargesOutstanding;
@SuppressWarnings("unused")
private final BigDecimal totalOriginalDueForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalDueForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalPaidForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalWaivedForPeriod;
@SuppressWarnings("unused")
private final BigDecimal totalWrittenOffForPeriod;
private final BigDecimal totalOutstandingForPeriod;
private final BigDecimal totalOverdue;
@@ -54,32 +64,34 @@ public class LoanSchedulePeriodData {
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principalDue,
final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal totalDueForPeriod) {
BigDecimal principalDisbursed = null;
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding, interestDueOnPrincipalOutstanding, totalDueForPeriod);
}
public static LoanSchedulePeriodData repaymentPeriodWithPayments(
final Long loanId,
@SuppressWarnings("unused") final Long loanId,
final Integer periodNumber,
final LocalDate fromDate,
final LocalDate dueDate,
final BigDecimal principalOriginalDue,
final BigDecimal principalPaid,
final BigDecimal principalWrittenOff,
final BigDecimal principalOutstanding,
final BigDecimal outstandingPrincipalBalanceOfLoan,
final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal interestPaid,
final BigDecimal interestWaived,
final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding,
final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid,
final BigDecimal totalWaived,
final BigDecimal totalWrittenOff,
final BigDecimal totalOutstanding) {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate,
principalOriginalDue, principalPaid, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestOutstanding,
totalDueForPeriod, totalPaid, totalWaived, totalOutstanding);
principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding);
}
/*
@@ -96,7 +108,7 @@ public class LoanSchedulePeriodData {
this.fromDate = fromDate;
this.dueDate = dueDate;
if (fromDate != null) {
this.daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
} else {
this.daysInPeriod = null;
}
@@ -104,6 +116,7 @@ public class LoanSchedulePeriodData {
this.principalOriginalDue = null;
this.principalDue = null;
this.principalPaid = null;
this.principalWrittenOff = null;
this.principalOutstanding = null;
this.principalLoanBalanceOutstanding = principalDisbursed;
@@ -111,6 +124,7 @@ public class LoanSchedulePeriodData {
this.interestDue = null;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.interestOutstanding = null;
this.chargesDue = chargesDueAtTimeOfDisbursement;
@@ -126,6 +140,7 @@ public class LoanSchedulePeriodData {
this.totalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalPaidForPeriod = this.chargesPaid;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = this.chargesOutstanding;
if (dueDate.isBefore(new LocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
@@ -146,7 +161,7 @@ public class LoanSchedulePeriodData {
this.fromDate = fromDate;
this.dueDate = dueDate;
if (fromDate != null) {
this.daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
} else {
this.daysInPeriod = null;
}
@@ -154,6 +169,7 @@ public class LoanSchedulePeriodData {
this.principalOriginalDue = principalOriginalDue;
this.principalDue = principalOriginalDue;
this.principalPaid = null;
this.principalWrittenOff = null;
this.principalOutstanding = principalOriginalDue;
this.principalLoanBalanceOutstanding = principalOutstanding;
@@ -161,6 +177,7 @@ public class LoanSchedulePeriodData {
this.interestDue = interestDueOnPrincipalOutstanding;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.interestOutstanding = interestDueOnPrincipalOutstanding;
this.chargesDue = BigDecimal.ZERO;
@@ -171,6 +188,7 @@ public class LoanSchedulePeriodData {
this.totalDueForPeriod = totalDueForPeriod;
this.totalPaidForPeriod = BigDecimal.ZERO;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = totalDueForPeriod;
if (dueDate.isBefore(new LocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
@@ -188,21 +206,24 @@ public class LoanSchedulePeriodData {
final LocalDate dueDate,
final BigDecimal principalOriginalDue,
final BigDecimal principalPaid,
final BigDecimal principalWrittenOff,
final BigDecimal principalOutstanding,
final BigDecimal principalLoanBalanceOutstanding,
final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal interestPaid,
final BigDecimal interestWaived,
final BigDecimal interestWaived,
final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding,
final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid,
final BigDecimal totalWaived,
final BigDecimal totalWrittenOff,
final BigDecimal totalOutstanding) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
if (fromDate != null) {
this.daysInPeriod = Days.daysBetween(fromDate, dueDate).getDays();
this.daysInPeriod = Days.daysBetween(this.fromDate, this.dueDate).getDays();
} else {
this.daysInPeriod = null;
}
@@ -210,6 +231,7 @@ public class LoanSchedulePeriodData {
this.principalOriginalDue = principalOriginalDue;
this.principalDue = principalOriginalDue;
this.principalPaid = principalPaid;
this.principalWrittenOff = principalWrittenOff;
this.principalOutstanding = principalOutstanding;
this.principalLoanBalanceOutstanding = principalLoanBalanceOutstanding;
@@ -217,6 +239,7 @@ public class LoanSchedulePeriodData {
this.interestDue = interestDueOnPrincipalOutstanding;
this.interestPaid = interestPaid;
this.interestWaived = interestWaived;
this.interestWrittenOff = interestWrittenOff;
this.interestOutstanding = interestOutstanding;
this.chargesDue = BigDecimal.ZERO;
@@ -227,6 +250,7 @@ public class LoanSchedulePeriodData {
this.totalDueForPeriod = totalDueForPeriod;
this.totalPaidForPeriod = totalPaid;
this.totalWaivedForPeriod = totalWaived;
this.totalWrittenOffForPeriod = totalWrittenOff;
this.totalOutstandingForPeriod = totalOutstanding;
if (dueDate.isBefore(new LocalDate())) {
@@ -276,6 +300,10 @@ public class LoanSchedulePeriodData {
return defaultToZeroIfNull(this.principalPaid);
}
public BigDecimal principalWrittenOff() {
return defaultToZeroIfNull(this.principalWrittenOff);
}
public BigDecimal principalOutstanding() {
return defaultToZeroIfNull(this.principalOutstanding);
}
@@ -291,6 +319,10 @@ public class LoanSchedulePeriodData {
public BigDecimal interestWaived() {
return defaultToZeroIfNull(this.interestWaived);
}
public BigDecimal interestWrittenOff() {
return defaultToZeroIfNull(this.interestWrittenOff);
}
public BigDecimal interestOutstanding() {
return defaultToZeroIfNull(this.interestOutstanding);
@@ -25,11 +25,13 @@ public class LoanTransactionNewData {
private BigDecimal amount;
public LoanTransactionNewData(
final EnumOptionData transactionsType,
final Long id,
final EnumOptionData transactionType,
final CurrencyData currency,
final LocalDate date,
final BigDecimal amount) {
this.type = transactionsType;
this.id = id;
this.type = transactionType;
this.currency = currency;
this.date = date;
this.amount = amount;
@@ -3,7 +3,7 @@ package org.mifosng.platform.api.infrastructure;
import java.util.Collection;
import java.util.Set;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.AppUserData;
import org.mifosng.platform.api.data.AuthenticatedUserData;
@@ -99,7 +99,7 @@ public interface ApiJsonSerializerService {
String serializeNoteDataToJson(boolean prettyPrint, Set<String> responseParameters, NoteData note);
String serializeLoanScheduleDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanScheduleNewData loanSchedule);
String serializeLoanScheduleDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanScheduleData loanSchedule);
String serializeLoanAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, LoanAccountData loanAccount);
@@ -5,7 +5,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.AdditionalFieldsSetData;
import org.mifosng.platform.api.data.AppUserData;
import org.mifosng.platform.api.data.AuthenticatedUserData;
@@ -172,7 +172,7 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ
"closedOnDate", "expectedMaturityDate",
"status",
"lifeCycleStatusDate", "repaymentSchedule",
"loanRepayments", "permissions", "convenienceData", "charges",
"transactions", "permissions", "convenienceData", "charges",
"productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions",
"repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
"loanOfficerId", "loanOfficerName", "loanOfficerOptions", "chargeTemplate"));
@@ -535,7 +535,7 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ
}
@Override
public String serializeLoanScheduleDataToJson(final boolean prettyPrint, final Set<String> responseParameters, final LoanScheduleNewData loanSchedule) {
public String serializeLoanScheduleDataToJson(final boolean prettyPrint, final Set<String> responseParameters, final LoanScheduleData loanSchedule) {
final Gson gsonDeserializer = helper
.createGsonBuilderWithParameterExclusionSerializationStrategy(LOAN_SCHEDULE_DATA_PARAMETERS, prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, loanSchedule);
@@ -23,7 +23,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
*/
@Override
public void handleTransaction(
final List<LoanTransaction> repaymentsOrWaivers,
final List<LoanTransaction> transactionsPostDisbursement,
final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments) {
@@ -31,10 +31,15 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
currentInstallment.resetDerivedComponents();
}
for (LoanTransaction loanTransaction : repaymentsOrWaivers) {
for (LoanTransaction loanTransaction : transactionsPostDisbursement) {
loanTransaction.resetDerivedComponents();
handleTransaction(loanTransaction, currency, installments);
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) {
loanTransaction.resetDerivedComponents();
handleTransaction(loanTransaction, currency, installments);
} else if (loanTransaction.isWriteOff()) {
loanTransaction.resetDerivedComponents();
handleWriteOff(loanTransaction, currency, installments);
}
}
}
@@ -76,6 +81,29 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
}
}
@Override
public void handleWriteOff(
final LoanTransaction loanTransaction,
final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments) {
final Money interestWaivedPortion = Money.zero(currency);
Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(currency);
Money chargesPortion = Money.zero(currency);
// determine how much is written off in total and breakdown for principal, interest and charges
for (LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (currentInstallment.isNotFullyCompleted()) {
principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(currency));
interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(currency));
}
}
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, interestWaivedPortion, chargesPortion);
}
// abstract interface
/**
@@ -68,7 +68,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
Money interestWaivedPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
@@ -135,7 +135,7 @@ public class AdhikarLoanRepaymentScheduleTransactionProcessor extends AbstractLo
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
@@ -84,7 +84,7 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
@@ -53,7 +53,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
}
break;
case LOAN_REPAYMENT_OR_WAIVER:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED, LoanStatus.OVERPAID)) {
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.OVERPAID)) {
newState = stateOf(LoanStatus.ACTIVE, allowedLoanStatuses);
} else {
newState = from;
@@ -61,26 +61,26 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
break;
case REPAID_IN_FULL:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
newState = stateOf(LoanStatus.CLOSED, allowedLoanStatuses);
newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, allowedLoanStatuses);
}
break;
case LOAN_WRITE_OFF:
case WRITE_OFF_OUTSTANDING:
if (this.anyOfAllowedWhenComingFrom(from,LoanStatus.ACTIVE)) {
newState = stateOf(LoanStatus.CLOSED, allowedLoanStatuses);
newState = stateOf(LoanStatus.CLOSED_WRITTEN_OFF, allowedLoanStatuses);
}
break;
case LOAN_RESCHEDULE:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.ACTIVE)) {
newState = stateOf(LoanStatus.CLOSED, allowedLoanStatuses);
newState = stateOf(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT, allowedLoanStatuses);
}
break;
case INTERST_REBATE_OWED:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED)) {
newState = stateOf(LoanStatus.CLOSED, allowedLoanStatuses);
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET)) {
newState = stateOf(LoanStatus.CLOSED_OBLIGATIONS_MET, allowedLoanStatuses);
}
break;
case LOAN_OVERPAYMENT:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED, LoanStatus.ACTIVE)) {
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.ACTIVE)) {
newState = stateOf(LoanStatus.OVERPAID, allowedLoanStatuses);
}
break;
@@ -73,7 +73,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
@@ -116,7 +116,7 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
@@ -27,7 +27,7 @@ import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.client.domain.Client;
@@ -282,7 +282,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct,
final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanScheduleNewData modifiedLoanSchedule, final Set<LoanCharge> charges,
final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanScheduleData modifiedLoanSchedule, final Set<LoanCharge> charges,
final Staff loanOfficer) {
if (command.isClientChanged()) {
@@ -361,7 +361,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
// if the loan application/contract is modified when repayments are already against it - then need to re-process it
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfRepaymentOrWaiverTransactionsPostDisbursement();
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments);
@@ -617,7 +617,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanLifecycleStateMachine loanLifecycleStateMachine,
final LoanTransaction adjustedTransaction) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus));
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
loanTransaction.updateLoan(this);
@@ -626,7 +626,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanTransactions.add(loanTransaction);
if (loanTransaction.isNotRepayment() && loanTransaction.isNotWaiver()) {
if (loanTransaction.isNotRepayment() && loanTransaction.isNotInterestWaiver()) {
final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
throw new InvalidLoanTransactionTypeException("transaction",
"is.not.a.repayment.or.waiver.transaction", errorMessage);
@@ -647,7 +647,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
"cannot.be.a.future.date", errorMessage, loanTransactionDate);
}
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
if (adjustedTransaction != null) {
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount());
@@ -663,14 +663,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments);
} else {
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfRepaymentOrWaiverTransactionsPostDisbursement();
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments);
}
doPostLoanTransactionChecks(loanTransaction, loanLifecycleStateMachine);
}
private List<LoanTransaction> retreiveListOfRepaymentOrWaiverTransactionsPostDisbursement() {
private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
List<LoanTransaction> repaymentsOrWaivers = new ArrayList<LoanTransaction>();
for (LoanTransaction transaction : this.loanTransactions) {
if (!transaction.isDisbursement() && transaction.isNotContra()) {
@@ -738,6 +738,26 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return isChronologicallyLatestRepaymentOrWaiver;
}
private boolean isChronologicallyLatestTransaction(
final LoanTransaction loanTransaction,
final List<LoanTransaction> loanTransactions) {
boolean isChronologicallyLatestRepaymentOrWaiver = true;
LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
for (LoanTransaction previousTransaction : loanTransactions) {
if (previousTransaction.isNotContra()) {
if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) ||
currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) {
isChronologicallyLatestRepaymentOrWaiver = false;
break;
}
}
}
return isChronologicallyLatestRepaymentOrWaiver;
}
public LocalDate possibleNextRepaymentDate() {
LocalDate earliestUnpaidInstallmentDate = new LocalDate();
@@ -770,7 +790,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
if (installment.isNotFullyCompleted()) {
possibleNextRepaymentAmount = installment.getTotalDue(currency);
possibleNextRepaymentAmount = installment.getTotalOutstanding(currency);
break;
}
}
@@ -805,14 +825,20 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanTransaction newTransactionDetail,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) {
final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotInterestWaiver()) {
final String errorMessage = "Only transactions of type repayment or interest waiver can be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
"is.not.a.repayment.or.waiver.transaction", errorMessage);
"adjustment.is.only.allowed.to.repayment.or.waiver.transaction", errorMessage);
}
if (isClosed()) {
final String errorMessage = "Transactions of a closed loan cannot be adjusted.";
throw new InvalidLoanTransactionTypeException("transaction",
"adjustment.is.not.allowed.on.closed.loan", errorMessage);
}
transactionForAdjustment.contra();
if (newTransactionDetail.isRepayment() || newTransactionDetail.isWaiver()) {
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment);
}
}
@@ -867,32 +893,49 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail.getCurrency();
}
public void writeOff(final DateTime writtenOffOn,
public LoanTransaction closeAsWrittenOff(
final LocalDate writtenOffOnLocalDate,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WRITE_OFF, LoanStatus.fromInt(this.loanStatus));
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
this.closedOnDate = writtenOffOn.toDate();
this.writtenOffOnDate = writtenOffOn.toDate();
this.closedOnDate = writtenOffOnLocalDate.toDate();
this.writtenOffOnDate = writtenOffOnLocalDate.toDate();
LocalDate writtenOffOnLocalDate = new LocalDate(writtenOffOnDate);
if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The date on which a loan is withdrawn cannot be before the loan disbursement date: "
final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
+ getDisbursementDate().toString();
throw new InvalidLoanStateTransitionException("writeoff",
"cannot.be.before.submittal.date", errorMessage,
writtenOffOnLocalDate, getDisbursementDate());
}
if (writtenOffOnLocalDate.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a loan is written off cannot be in the future.";
throw new InvalidLoanStateTransitionException("writeoff",
"cannot.be.a.future.date", errorMessage,
writtenOffOnLocalDate);
}
final LoanTransaction loanTransaction = LoanTransaction.writeoff(this, writtenOffOnLocalDate);
boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions);
if (!isLastTransaction) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
throw new InvalidLoanStateTransitionException("writeoff","must.occur.on.or.after.other.transaction.dates", errorMessage, writtenOffOnLocalDate);
}
this.loanTransactions.add(loanTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
return loanTransaction;
}
public void reschedule(final DateTime rescheduledOn,
// FIXME - KW - finish off reschedule/restructure capability for loans.
public void reschedule(
final DateTime rescheduledOn,
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, LoanStatus.fromInt(this.loanStatus));
@@ -1,8 +1,21 @@
package org.mifosng.platform.loan.domain;
/**
*
*/
public enum LoanEvent {
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;
LOAN_CREATED, //
LOAN_REJECTED, //
LOAN_WITHDRAWN, //
LOAN_APPROVED, //
LOAN_APPROVAL_UNDO, //
LOAN_DISBURSED, //
LOAN_DISBURSAL_UNDO, //
LOAN_REPAYMENT_OR_WAIVER, //
REPAID_IN_FULL, //
WRITE_OFF_OUTSTANDING, //
LOAN_RESCHEDULE, //
INTERST_REBATE_OWED, //
LOAN_OVERPAYMENT;
}
@@ -19,7 +19,7 @@ import org.mifosng.platform.user.domain.AppUser;
@Entity
@Table(name = "m_loan_repayment_schedule")
public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<AppUser, Long> {
public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id")
@@ -37,11 +37,17 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
@Column(name = "principal_completed_derived", scale=6, precision=19)
private BigDecimal principalCompleted = BigDecimal.ZERO;
@Column(name = "principal_writtenoff_derived", scale=6, precision=19)
private BigDecimal principalWrittenOff = BigDecimal.ZERO;
@Column(name = "interest_completed_derived", scale = 6, precision = 19)
private BigDecimal interestCompleted = BigDecimal.ZERO;
@Column(name = "interest_waived_derived", scale = 6, precision = 19)
private BigDecimal interestWaived = BigDecimal.ZERO;
@Column(name = "interest_writtenoff_derived", scale=6, precision=19)
private BigDecimal interestWrittenOff = BigDecimal.ZERO;
@Column(name="completed_derived")
private boolean completed;
@@ -93,8 +99,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
return Money.of(currency, this.principalCompleted);
}
public Money getPrincipalWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.principalWrittenOff);
}
public Money getPrincipalOutstanding(final MonetaryCurrency currency) {
return getPrincipal(currency).minus(getPrincipalCompleted(currency));
final Money principalAccountedFor = getPrincipalCompleted(currency).plus(getPrincipalWrittenOff(currency));
return getPrincipal(currency).minus(principalAccountedFor);
}
public Money getInterest(final MonetaryCurrency currency) {
@@ -109,15 +120,20 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
return Money.of(currency, this.interestWaived);
}
public Money getInterestWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWrittenOff);
}
public Money getInterestOutstanding(final MonetaryCurrency currency) {
return getInterest(currency).minus(getInterestCompleted(currency).plus(getInterestWaived(currency)));
final Money interestAccountedFor = getInterestCompleted(currency).plus(getInterestWaived(currency)).plus(getInterestWrittenOff(currency));
return getInterest(currency).minus(interestAccountedFor);
}
public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterest(currency).minus(getInterestCompleted(currency).plus(getInterestWaived(currency))).isGreaterThanZero();
return getInterestOutstanding(currency).isGreaterThanZero();
}
public Money getTotal(final MonetaryCurrency currency) {
public Money getTotalPrincipalAndInterest(final MonetaryCurrency currency) {
return getPrincipal(currency).plus(getInterest(currency));
}
@@ -142,22 +158,15 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
}
public boolean isPrincipalCompleted(final MonetaryCurrency currency) {
return getPrincipal(currency).minus(getPrincipalCompleted(currency)).isZero();
}
public Money getTotalDue(MonetaryCurrency currency) {
final Money totalPaidOrWaived = getInterestWaived(currency).plus(getTotalCompleted(currency));
return getTotal(currency).minus(totalPaidOrWaived);
}
private Money getTotalCompleted(MonetaryCurrency currency) {
return getPrincipalCompleted(currency).plus(getInterestCompleted(currency));
return getPrincipalOutstanding(currency).isZero();
}
public void resetDerivedComponents() {
this.principalCompleted = BigDecimal.ZERO;
this.principalWrittenOff = BigDecimal.ZERO;
this.interestCompleted = BigDecimal.ZERO;
this.interestWaived = BigDecimal.ZERO;
this.interestWrittenOff = BigDecimal.ZERO;
this.completed = false;
}
@@ -175,7 +184,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.completed = getTotalDue(currency).isZero();
this.completed = getTotalOutstanding(currency).isZero();
return interestPortionOfTransaction;
}
@@ -194,7 +203,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
}
this.completed = getTotalDue(currency).isZero();
this.completed = getTotalOutstanding(currency).isZero();
return principalPortionOfTransaction;
}
@@ -212,10 +221,28 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableCustom<Ap
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.completed = getTotalDue(currency).isZero();
this.completed = getTotalOutstanding(currency).isZero();
return waivedInterestPortionOfTransaction;
}
public Money writeOffOutstandingPrincipal(final MonetaryCurrency currency) {
final Money principalDue = getPrincipalOutstanding(currency);
this.principalWrittenOff = principalDue.getAmount();
this.completed = getTotalOutstanding(currency).isZero();
return principalDue;
}
public Money writeOffOutstandingInterest(final MonetaryCurrency currency) {
final Money interestDue = getInterestOutstanding(currency);
this.interestWrittenOff = interestDue.getAmount();
this.completed = getTotalOutstanding(currency).isZero();
return interestDue;
}
public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(transactionDate);
@@ -10,4 +10,8 @@ public interface LoanRepaymentScheduleTransactionProcessor {
void handleTransaction(List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
void handleWriteOff(LoanTransaction loanTransaction,
MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
}
@@ -11,7 +11,9 @@ public enum LoanStatus {
ACTIVE(300, "loanStatusType.active"), //
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
REJECTED(500, "loanStatusType.rejected"), //
CLOSED(600, "loanStatusType.closed"), //
CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), //
CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), //
OVERPAID(700, "loanStatusType.overpaid");
private final Integer value;
@@ -37,7 +39,13 @@ public enum LoanStatus {
enumeration = LoanStatus.REJECTED;
break;
case 600:
enumeration = LoanStatus.CLOSED;
enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET;
break;
case 601:
enumeration = LoanStatus.CLOSED_WRITTEN_OFF;
break;
case 602:
enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT;
break;
case 700:
enumeration = LoanStatus.OVERPAID;
@@ -76,9 +84,21 @@ public enum LoanStatus {
}
public boolean isClosed() {
return this.value.equals(LoanStatus.CLOSED.getValue());
return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule();
}
public boolean isClosedObligationsMet() {
return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue());
}
public boolean isClosedWrittenOff() {
return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue());
}
public boolean isClosedWithOutsandingAmountMarkedForReschedule() {
return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue());
}
public boolean isWithdrawnByClient() {
return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue());
}
@@ -35,7 +35,7 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
private Loan loan;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private final BigDecimal amount;
private BigDecimal amount;
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = false)
private BigDecimal principalPortion = BigDecimal.ZERO;
@@ -63,7 +63,6 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
protected LoanTransaction() {
this.loan = null;
this.amount = null;
this.dateOf = null;
this.typeOf = null;
}
@@ -81,15 +80,19 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
}
public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) {
return new LoanTransaction(loan, LoanTransactionType.WAIVED, waived.getAmount(), waiveDate);
return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate);
}
private static LoanTransaction contra(final LoanTransaction originalTransaction) {
LoanTransaction contra = new LoanTransaction(null, LoanTransactionType.REVERSAL, originalTransaction.getAmount().negate(), new LocalDate(originalTransaction.getDateOf()));
LoanTransaction contra = new LoanTransaction(null, LoanTransactionType.CONTRA, originalTransaction.getAmount().negate(), new LocalDate(originalTransaction.getDateOf()));
contra.updateContra(originalTransaction);
return contra;
}
public static LoanTransaction writeoff(final Loan loan, final LocalDate writeOffDate) {
return new LoanTransaction(loan, LoanTransactionType.WRITEOFF, null, writeOffDate);
}
public void updateContra(final LoanTransaction transaction) {
this.contra = transaction;
}
@@ -142,23 +145,27 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf());
}
public boolean isWaiver() {
return LoanTransactionType.WAIVED.equals(getTypeOf()) && isNotContra();
public boolean isInterestWaiver() {
return LoanTransactionType.WAIVE_INTEREST.equals(getTypeOf()) && isNotContra();
}
public boolean isNotWaiver() {
return !isWaiver();
public boolean isNotInterestWaiver() {
return !isInterestWaiver();
}
public boolean isWriteOff() {
return getTypeOf().isWriteOff() && isNotContra();
}
public boolean isIdentifiedBy(Long identifier) {
public boolean isIdentifiedBy(final Long identifier) {
return this.getId().equals(identifier);
}
public boolean isBelongingToLoanOf(Loan check) {
public boolean isBelongingToLoanOf(final Loan check) {
return this.loan.getId().equals(check.getId());
}
public boolean isNotBelongingToLoanOf(Loan check) {
public boolean isNotBelongingToLoanOf(final Loan check) {
return !isBelongingToLoanOf(check);
}
@@ -187,6 +194,13 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
this.interestWaivedPortion = getInterestWaivedPortion(currency).plus(interestWaivedPortion).getAmount();
this.chargesPortion = getChargesPortion(currency).plus(charges).getAmount();
}
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money interestWaivedPortion, final Money charges) {
updateComponents(principal, interest, interestWaivedPortion, charges);
final MonetaryCurrency currency = principal.getCurrency();
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(this.chargesPortion).getAmount();
}
public Money getPrincipalPortion(final MonetaryCurrency currency) {
return Money.of(currency, principalPortion);
@@ -13,9 +13,9 @@ public class LoanTransactionComparator implements Comparator<LoanTransaction> {
final int comparsion = o1.getTransactionDate().compareTo(o2.getTransactionDate());
if (comparsion == 0) {
// equal transaction dates
if (o1.isWaiver() && o2.isNotWaiver()) {
if (o1.isInterestWaiver() && o2.isNotInterestWaiver()) {
compareResult = -1;
} else if (o1.isNotWaiver() && o2.isWaiver()) {
} else if (o1.isNotInterestWaiver() && o2.isInterestWaiver()) {
compareResult = 1;
} else {
compareResult = 0;
@@ -5,9 +5,15 @@ public enum LoanTransactionType {
INVALID(0, "loanTransactionType.invalid"), //
DISBURSEMENT(1, "loanTransactionType.disbursement"), //
REPAYMENT(2, "loanTransactionType.repayment"), //
REVERSAL(3, "loanTransactionType.reversal"), //
WAIVED(4, "loanTransactionType.waiver"), //
REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement");
CONTRA(3, "loanTransactionType.contra"), //
WAIVE_INTEREST(4, "loanTransactionType.waiver"), //
REPAYMENT_AT_DISBURSEMENT(5, "loanTransactionType.repaymentAtDisbursement"), //
WRITEOFF(6, "loanTransactionType.writeOff"),
MARKED_FOR_RESCHEDULING(7, "loanTransactionType.marked.for.rescheduling"), //
/**
* This type of transactions is allowed on written-off loans where mfi still attempts to recover payments from applicant after writing-off.
*/
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment");
private final Integer value;
private final String code;
@@ -40,18 +46,28 @@ public enum LoanTransactionType {
loanTransactionType = LoanTransactionType.REPAYMENT;
break;
case 3:
loanTransactionType = LoanTransactionType.REVERSAL;
loanTransactionType = LoanTransactionType.CONTRA;
break;
case 4:
loanTransactionType = LoanTransactionType.WAIVED;
loanTransactionType = LoanTransactionType.WAIVE_INTEREST;
break;
case 5:
loanTransactionType = LoanTransactionType.REPAYMENT_AT_DISBURSEMENT;
break;
case 6:
loanTransactionType = LoanTransactionType.WRITEOFF;
break;
case 7:
loanTransactionType = LoanTransactionType.MARKED_FOR_RESCHEDULING;
break;
default:
loanTransactionType = LoanTransactionType.INVALID;
break;
}
return loanTransactionType;
}
public boolean isWriteOff() {
return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
}
}
@@ -75,7 +75,7 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency());
Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isWaiver()) {
if (loanTransaction.isInterestWaiver()) {
interestWaivedPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestWaivedPortion);
} else {
@@ -1,12 +1,12 @@
package org.mifosng.platform.loan.service;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
public interface CalculationPlatformService {
LoanScheduleNewData calculateLoanScheduleNew(CalculateLoanScheduleCommand command);
LoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command);
/**
* Not used at present from application.
@@ -3,7 +3,7 @@ package org.mifosng.platform.loan.service;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.MoneyData;
@@ -51,7 +51,7 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
}
@Override
public LoanScheduleNewData calculateLoanScheduleNew(final CalculateLoanScheduleCommand command) {
public LoanScheduleData calculateLoanScheduleNew(final CalculateLoanScheduleCommand command) {
context.authenticatedUser();
@@ -5,7 +5,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.client.domain.Client;
@@ -122,7 +122,7 @@ public class LoanAssembler {
loan.setExternalId(command.getExternalId());
final LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
@@ -3,12 +3,11 @@ package org.mifosng.platform.loan.service;
import java.math.BigDecimal;
import java.util.Collection;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DisbursementData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
@@ -16,9 +15,9 @@ public interface LoanReadPlatformService {
LoanBasicDetailsData retrieveLoanAccountDetails(Long loanId);
LoanScheduleNewData retrieveRepaymentSchedule(Long loanId, CurrencyData currency, DisbursementData disbursement, BigDecimal totalChargesAtDisbursement, BigDecimal inArrearsTolerance);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, CurrencyData currency, DisbursementData disbursement, BigDecimal totalChargesAtDisbursement, BigDecimal inArrearsTolerance);
Collection<LoanRepaymentTransactionData> retrieveLoanPayments(Long loanId);
Collection<LoanTransactionNewData> retrieveLoanTransactions(Long loanId);
LoanPermissionData retrieveLoanPermissions(
LoanBasicDetailsData loanBasicDetails, boolean isWaiverAllowed,
@@ -33,4 +32,6 @@ public interface LoanReadPlatformService {
LoanTransactionNewData retrieveNewLoanWaiveInterestDetails(Long loanId);
LoanTransactionData retrieveLoanTransactionDetails(Long loanId, Long transactionId);
LoanTransactionNewData retrieveNewClosureDetails();
}
@@ -7,7 +7,7 @@ import java.util.ArrayList;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.ClientData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DisbursementData;
@@ -17,7 +17,6 @@ import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanTransactionNewData;
@@ -97,7 +96,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public LoanScheduleNewData retrieveRepaymentSchedule(final Long loanId,
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final CurrencyData currency,
final DisbursementData disbursement,
final BigDecimal totalChargesAtDisbursement,
@@ -123,10 +122,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal cumulativePrincipalDisbursed = wrapper.deriveCumulativePrincipalDisbursed();
final BigDecimal cumulativePrincipalDue = wrapper.deriveCumulativePrincipalDue();
final BigDecimal cumulativePrincipalPaid = wrapper.deriveCumulativePrincipalPaid();
final BigDecimal cumulativePrincipalWrittenOff = wrapper.deriveCumulativePrincipalWrittenOff();
final BigDecimal cumulativePrincipalOutstanding = wrapper.deriveCumulativePrincipalOutstanding();
final BigDecimal cumulativeInterestExpected = wrapper.deriveCumulativeInterestExpected();
final BigDecimal cumulativeInterestPaid = wrapper.deriveCumulativeInterestPaid();
final BigDecimal cumulativeInterestWaived = wrapper.deriveCumulativeInterestWaived();
final BigDecimal cumulativeInterestWrittenOff = wrapper.deriveCumulativeInterestWrittenOff();
final BigDecimal cumulativeInterestOutstanding = wrapper.deriveCumulativeInterestOutstanding();
final BigDecimal cumulativeChargesToDate = wrapper.deriveCumulativeChargesToDate();
final BigDecimal cumulativeChargesPaid = wrapper.deriveCumulativeChargesPaid();
@@ -136,6 +137,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalCostOfLoan);
final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeChargesPaid);
final BigDecimal totalWaivedToDate = cumulativeInterestWaived;
final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff);
final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeChargesOutstanding);
final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue();
@@ -149,31 +151,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
totalInArrears = totalOverdueMoney.getAmount();
}
return new LoanScheduleNewData(currency, periods, loanTermInDays,
cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalOutstanding,
cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestOutstanding,
return new LoanScheduleData(currency, periods, loanTermInDays,
cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding,
cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding,
cumulativeChargesToDate, cumulativeChargesPaid, cumulativeChargesOutstanding,
totalCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalOutstanding, totalInArrears);
totalCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate, totalOutstanding, totalInArrears);
} catch (EmptyResultDataAccessException e) {
throw new LoanNotFoundException(loanId);
}
}
@Override
public Collection<LoanRepaymentTransactionData> retrieveLoanPayments(Long loanId) {
public Collection<LoanTransactionNewData> retrieveLoanTransactions(final Long loanId) {
try {
context.authenticatedUser();
LoanPaymentsMapper rm = new LoanPaymentsMapper();
LoanTransactionsMapper rm = new LoanTransactionsMapper();
// retrieve all loan transactions that are not invalid (0) and not
// disbursements (1) and have not been 'contra'ed by another transaction
// retrieve all loan transactions that are not invalid and have not been 'contra'ed by another transaction
// repayments at time of disbursement (e.g. charges)
String sql = "select "
+ rm.LoanPaymentsSchema()
+ " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 1, 5) and tr.contra_id is null order by tr.transaction_date ASC";
+ " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.contra_id is null order by tr.transaction_date ASC";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
} catch (EmptyResultDataAccessException e) {
return null;
}
@@ -181,8 +181,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanPermissionData retrieveLoanPermissions(
LoanBasicDetailsData loanBasicDetails, boolean isWaiverAllowed,
int repaymentAndWaiveCount) {
final LoanBasicDetailsData loanBasicDetails,
final boolean isWaiverAllowed,
final int repaymentAndWaiveCount) {
boolean pendingApproval = (loanBasicDetails.getStatus().getId().equals(100L));
boolean waitingForDisbursal = (loanBasicDetails.getStatus().getId().equals(200L));
@@ -307,9 +308,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
LoanTransaction waiveOfInterest = loan.deriveDefaultInterestWaiverTransaction();
EnumOptionData waiverEnum = LoanEnumerations.transactionType(LoanTransactionType.WAIVED);
EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
return new LoanTransactionNewData(waiverEnum, currencyData, waiveOfInterest.getTransactionDate(), waiveOfInterest.getAmount());
return new LoanTransactionNewData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(), waiveOfInterest.getAmount());
}
@Override
public LoanTransactionNewData retrieveNewClosureDetails() {
context.authenticatedUser();
EnumOptionData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
return new LoanTransactionNewData(null, transactionType, null, new LocalDate(), null);
}
@Override
@@ -512,8 +523,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public String loanScheduleSchema() {
return " ls.loan_id as loanId, ls.installment as period, ls.duedate as dueDate, "
+ " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, "
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived "
+ " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, ls.principal_writtenoff_derived as principalWrittenOff, "
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff "
+ " from m_loan l "
+ " join m_loan_repayment_schedule ls on ls.loan_id = l.id ";
}
@@ -526,18 +537,32 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final BigDecimal principalDue = rs.getBigDecimal("principalDue");
final BigDecimal principalPaid = rs.getBigDecimal("principalPaid");
final BigDecimal principalOutstanding = principalDue.subtract(principalPaid);
BigDecimal principalWrittenOff = rs.getBigDecimal("principalWrittenOff");
if (principalWrittenOff == null) {
principalWrittenOff = BigDecimal.ZERO;
}
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns?
final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
final BigDecimal interestDue = rs.getBigDecimal("interestDue");
final BigDecimal interestPaid = rs.getBigDecimal("interestPaid");
BigDecimal interestWaived = rs.getBigDecimal("interestWaived");
if (interestWaived == null) {
interestWaived = BigDecimal.ZERO;
}
final BigDecimal interestOutstanding = interestDue.subtract(interestPaid).subtract(interestWaived);
BigDecimal interestWrittenOff = rs.getBigDecimal("interestWrittenOff");
if (interestWrittenOff == null) {
interestWrittenOff = BigDecimal.ZERO;
}
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns?
final BigDecimal interestOutstanding = interestDue.subtract(interestPaid).subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal totalDueForPeriod = principalDue.add(interestDue);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid);
final BigDecimal totalWaivedForPeriod = interestWaived;
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff);
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding);
final LocalDate fromDate = this.lastDueDate;
@@ -548,50 +573,44 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
outstandingLoanBalance = outstandingLoanBalance.subtract(principalDue);
return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate,
principalDue, principalPaid, principalOutstanding, outstandingPrincipleBalanceOfLoan, interestDue,
interestPaid, interestWaived, interestOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod,
principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, interestDue,
interestPaid, interestWaived, interestWrittenOff, interestOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod,
totalOutstandingForPeriod);
}
}
private static final class LoanPaymentsMapper implements
RowMapper<LoanRepaymentTransactionData> {
private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionNewData> {
public String LoanPaymentsSchema() {
return " tr.id as id, tr.transaction_type_enum as transactionType, tr.transaction_date as `date`, tr.amount as total, "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode "
+ " l.currency_code as currencyCode, l.currency_digits as currencyDigits, rc.`name` as currencyName, "
+ " rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode "
+ " from m_loan l "
+ " join m_loan_transaction tr on tr.loan_id = l.id"
+ " join m_currency rc on rc.`code` = l.currency_code ";
}
@Override
public LoanRepaymentTransactionData mapRow(final ResultSet rs,
public LoanTransactionNewData mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) throws SQLException {
String currencyCode = rs.getString("currencyCode");
String currencyName = rs.getString("currencyName");
String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs
.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs,
"currencyDigits");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
CurrencyData currencyData = new CurrencyData(currencyCode,
currencyName, currencyDigits, currencyDisplaySymbol,
currencyNameCode);
Long id = rs.getLong("id");
int transactionTypeInt = JdbcSupport.getInteger(rs,
"transactionType");
EnumOptionData transactionType = LoanEnumerations
.transactionType(transactionTypeInt);
int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
EnumOptionData transactionType = LoanEnumerations.transactionType(transactionTypeInt);
LocalDate date = JdbcSupport.getLocalDate(rs, "date");
BigDecimal totalBD = rs.getBigDecimal("total");
MoneyData total = MoneyData.of(currencyData, totalBD);
BigDecimal totalAmount = rs.getBigDecimal("total");
return new LoanRepaymentTransactionData(id, transactionType, date, total);
return new LoanTransactionNewData(id, transactionType, currencyData, date, totalAmount);
}
}
}
@@ -36,6 +36,14 @@ public class LoanSchedulePeriodDataWrapper {
}
return cumulativeValue;
}
public BigDecimal deriveCumulativePrincipalWrittenOff() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.principalWrittenOff());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativePrincipalOutstanding() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
@@ -68,6 +76,14 @@ public class LoanSchedulePeriodDataWrapper {
}
return cumulativeValue;
}
public BigDecimal deriveCumulativeInterestWrittenOff() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
for (LoanSchedulePeriodData period : this.periods) {
cumulativeValue = cumulativeValue.add(period.interestWrittenOff());
}
return cumulativeValue;
}
public BigDecimal deriveCumulativeInterestOutstanding() {
BigDecimal cumulativeValue = BigDecimal.ZERO;
@@ -12,7 +12,7 @@ public class LoanTransactionCommandValidator {
private final LoanTransactionCommand command;
public LoanTransactionCommandValidator(LoanTransactionCommand command) {
public LoanTransactionCommandValidator(final LoanTransactionCommand command) {
this.command = command;
}
@@ -31,4 +31,18 @@ public class LoanTransactionCommandValidator {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
public void validateNonMonetaryTransaction() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).notNull();
baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}
@@ -46,7 +46,13 @@ public interface LoanWritePlatformService {
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
EntityIdentifier waiveInterestOnLoan(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
EntityIdentifier writeOff(LoanTransactionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')")
EntityIdentifier closeAsRescheduled(LoanTransactionCommand command);
@PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')")
EntityIdentifier addLoanCharge(LoanChargeCommand command);
@@ -8,7 +8,7 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanApplicationCommand;
@@ -161,7 +161,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
final Set<LoanCharge> charges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), loan.getPrincpal().getAmount());
final LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer);
this.loanRepository.save(loan);
@@ -373,7 +373,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTermFrequency, loanTermFrequencyType,
actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanChargeCommands);
LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateCommand);
LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
@@ -494,7 +494,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// adjustment is only supported for repayments and waivers at present
LocalDate transactionDate = command.getTransactionDate();
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate);
if (transactionToAdjust.isWaiver()) {
if (transactionToAdjust.isInterestWaiver()) {
newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate);
}
@@ -540,7 +540,63 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return new EntityIdentifier(loan.getId());
}
@Transactional
@Override
public EntityIdentifier writeOff(final LoanTransactionCommand command) {
context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
validator.validateNonMonetaryTransaction();
final Loan loan = this.loanRepository.findOne(command.getLoanId());
if (loan == null) {
throw new LoanNotFoundException(command.getLoanId());
}
final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(writeoff);
this.loanRepository.save(loan);
final String noteText = command.getNote();
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, writeoff, noteText);
this.noteRepository.save(note);
}
return new EntityIdentifier(loan.getId());
}
@Transactional
@Override
public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) {
context.authenticatedUser();
final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
validator.validateNonMonetaryTransaction();
final Loan loan = this.loanRepository.findOne(command.getLoanId());
if (loan == null) {
throw new LoanNotFoundException(command.getLoanId());
}
// FIXME - kw - HANDLE closed and rescheduled case.
final LoanTransaction writeoff = loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(writeoff);
this.loanRepository.save(loan);
final String noteText = command.getNote();
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, writeoff, noteText);
this.noteRepository.save(note);
}
return new EntityIdentifier(loan.getId());
}
@Transactional
@Override
public EntityIdentifier addLoanCharge(LoanChargeCommand command) {
this.context.authenticatedUser();
@@ -579,6 +635,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return new EntityIdentifier(loanCharge.getId());
}
@Transactional
@Override
public EntityIdentifier updateLoanCharge(final LoanChargeCommand command) {
@@ -604,6 +661,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new ChargeNotFoundException(command.getId());
}
@Transactional
@Override
public EntityIdentifier deleteLoanCharge(Long loanId, Long loanChargeId) {
@@ -173,20 +173,32 @@ public class LoanEnumerations {
public static EnumOptionData transactionType(final LoanTransactionType type) {
EnumOptionData optionData = null;
switch (type) {
case INVALID:
optionData = new EnumOptionData(LoanTransactionType.INVALID.getValue().longValue(), LoanTransactionType.INVALID.getCode(), "Invalid");
break;
case DISBURSEMENT:
optionData= new EnumOptionData(LoanTransactionType.DISBURSEMENT.getValue().longValue(), LoanTransactionType.DISBURSEMENT.getCode(), "Dibursement");
break;
case REPAYMENT:
optionData = new EnumOptionData(LoanTransactionType.REPAYMENT.getValue().longValue(), LoanTransactionType.REPAYMENT.getCode(), "Repayment");
break;
case REVERSAL:
optionData = new EnumOptionData(LoanTransactionType.REVERSAL.getValue().longValue(), LoanTransactionType.REVERSAL.getCode(), "Reversal");
case REPAYMENT_AT_DISBURSEMENT:
optionData = new EnumOptionData(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue().longValue(), LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getCode(), "Repayment (at time of disbursement)");
break;
case WAIVED:
optionData = new EnumOptionData(LoanTransactionType.WAIVED.getValue().longValue(), LoanTransactionType.WAIVED.getCode(), "Waiver");
case CONTRA:
optionData = new EnumOptionData(LoanTransactionType.CONTRA.getValue().longValue(), LoanTransactionType.CONTRA.getCode(), "Reversal");
break;
default:
optionData = new EnumOptionData(LoanTransactionType.INVALID.getValue().longValue(), LoanTransactionType.INVALID.getCode(), "Invalid");
case WAIVE_INTEREST:
optionData = new EnumOptionData(LoanTransactionType.WAIVE_INTEREST.getValue().longValue(), LoanTransactionType.WAIVE_INTEREST.getCode(), "Waive interest");
break;
case MARKED_FOR_RESCHEDULING:
optionData = new EnumOptionData(LoanTransactionType.MARKED_FOR_RESCHEDULING.getValue().longValue(), LoanTransactionType.MARKED_FOR_RESCHEDULING.getCode(), "Close (as rescheduled)");
break;
case WRITEOFF:
optionData = new EnumOptionData(LoanTransactionType.WRITEOFF.getValue().longValue(), LoanTransactionType.WRITEOFF.getCode(), "Close (as written-off)");
break;
case RECOVERY_REPAYMENT:
optionData = new EnumOptionData(LoanTransactionType.RECOVERY_REPAYMENT.getValue().longValue(), LoanTransactionType.RECOVERY_REPAYMENT.getCode(), "Repayment (after write-off)");
break;
}
return optionData;
@@ -197,8 +209,11 @@ public class LoanEnumerations {
}
public static EnumOptionData status(final LoanStatus status) {
EnumOptionData optionData = null;
EnumOptionData optionData = new EnumOptionData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
switch (status) {
case INVALID:
optionData = new EnumOptionData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
break;
case SUBMITED_AND_PENDING_APPROVAL:
optionData = new EnumOptionData(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue().longValue(), LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
break;
@@ -214,16 +229,20 @@ public class LoanEnumerations {
case WITHDRAWN_BY_CLIENT:
optionData = new EnumOptionData(LoanStatus.WITHDRAWN_BY_CLIENT.getValue().longValue(), LoanStatus.WITHDRAWN_BY_CLIENT.getCode(), "Withdrawn by applicant");
break;
case CLOSED:
optionData = new EnumOptionData(LoanStatus.CLOSED.getValue().longValue(), LoanStatus.CLOSED.getCode(), "Closed");
case CLOSED_OBLIGATIONS_MET:
optionData = new EnumOptionData(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue().longValue(), LoanStatus.CLOSED_OBLIGATIONS_MET.getCode(), "Closed (obligations met)");
break;
case CLOSED_WRITTEN_OFF:
optionData = new EnumOptionData(LoanStatus.CLOSED_WRITTEN_OFF.getValue().longValue(), LoanStatus.CLOSED_WRITTEN_OFF.getCode(), "Closed (written off)");
break;
case CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT:
optionData = new EnumOptionData(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue().longValue(), LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getCode(), "Closed (rescheduled)");
break;
case OVERPAID:
optionData = new EnumOptionData(LoanStatus.OVERPAID.getValue().longValue(), LoanStatus.OVERPAID.getCode(), "Overpaid");
break;
default:
optionData = new EnumOptionData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
}
@@ -5,14 +5,14 @@ import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.LoanCharge;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public interface AmortizationLoanScheduleGenerator {
LoanScheduleNewData generate(
LoanScheduleData generate(
ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo,
LocalDate disbursementDate,
@@ -5,7 +5,7 @@ import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanCharge;
@@ -36,7 +36,7 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory();
@Override
public LoanScheduleNewData generate(
public LoanScheduleData generate(
final ApplicationCurrency applicationCurrency,
final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
@@ -8,7 +8,7 @@ import java.util.Set;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
@@ -27,7 +27,7 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
private final PmtCalculator pmtCalculator = new PmtCalculator();
@Override
public LoanScheduleNewData generate(
public LoanScheduleData generate(
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate,
@@ -150,7 +150,7 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
currency.getDisplaySymbol(),
currency.getNameCode());
return new LoanScheduleNewData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
}
}
@@ -8,7 +8,7 @@ import java.util.Set;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
@@ -23,7 +23,7 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override
public LoanScheduleNewData generate(
public LoanScheduleData generate(
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate,
@@ -131,7 +131,7 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
currency.getDisplaySymbol(),
currency.getNameCode());
return new LoanScheduleNewData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
}
}
@@ -10,7 +10,7 @@ import java.util.Set;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
@@ -26,7 +26,7 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override
public LoanScheduleNewData generate(
public LoanScheduleData generate(
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
@@ -138,7 +138,7 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
currency.getDisplaySymbol(),
currency.getNameCode());
return new LoanScheduleNewData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
}
}
@@ -3,7 +3,7 @@ package org.mifosng.platform.loanschedule.domain;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.LoanScheduleNewData;
import org.mifosng.platform.api.LoanScheduleData;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.LoanCharge;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
@@ -11,7 +11,7 @@ import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public interface LoanScheduleGenerator {
LoanScheduleNewData generate(ApplicationCurrency applicationCurrency,
LoanScheduleData generate(ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo,
Integer loanTermFrequency,
PeriodFrequencyType loanTermFrequencyType,