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