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