MIFOSX-92 Tranche loan functionality

This commit is contained in:
Pramod Nuthakki
2014-02-17 09:20:47 -08:00
committed by Vishwas Babu A J
parent 0bb6e0d9f4
commit c548f501de
56 changed files with 2405 additions and 269 deletions
+33 -7
View File
@@ -5409,7 +5409,11 @@ GET https://DomainName/api/v1/loans/{loanId}
"linkedAccount":{
"id":1,
"accountNo":"000000001"
}
},
"disbursementDetails":[{"id":71,"expectedDisbursementDate":[2013,11,1],"principal":22000.000000,"approvedPrincipal":22000.000000}],
"fixedEmiAmount":1100.000000,
"maxOutstandingLoanBalance":35000,
"canDisburse":false
}
}
</code>
@@ -5834,7 +5838,9 @@ Request Body:
<td><div class="mifosXHeading2">Optional Fields</div></td>
</tr>
<tr class=alt>
<td>graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,linkAccountId</td>
<td>graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,linkAccountId,
fixedEmiAmount,maxOutstandingLoanBalance,disbursementData
</td>
</tr>
</table>
</div>
@@ -5864,8 +5870,12 @@ Request Body:
"interestCalculationPeriodType": 1,
"transactionProcessingStrategyId": 1,
"expectedDisbursementDate": "10 Jun 2013",
"submittedOnDate": "10 Jun 2013"
"linkAccountId" : "1"
"submittedOnDate": "10 Jun 2013",
"linkAccountId" : "1",
"fixedEmiAmount":1100,
"maxOutstandingLoanBalance":"35000",
"disbursementData":[{"expectedDisbursementDate":"01 November 2013",
"principal":22000,"approvedPrincipal":22000}]
}
</code>
<code class="method-response">
@@ -6109,6 +6119,15 @@ Request Body:
<td>actualDisbursementDate</td>
</tr>
</table>
<br/>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Optional Fields</div></td>
</tr>
<tr class=alt>
<td>transactionAmount,fixedEmiAmount</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
@@ -6121,6 +6140,8 @@ Request Body:
{
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"transactionAmount":10000,
"fixedEmiAmount""1100,
"actualDisbursementDate": "14 May 2013",
"paymentTypeId": "12",
"note": "",
@@ -6158,7 +6179,8 @@ Request Body:
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"actualDisbursementDate": "14 May 2013"
"actualDisbursementDate": "14 May 2013",
"transactionAmount":10000
}
}
</code>
@@ -9153,7 +9175,8 @@ GET https://DomainName/api/v1/loanproducts/{productId}
"name": "income from penalities 2",
"glCode": "11"
}
}
},
"multiDisburseLoan":true,"maxTrancheCount":3,"outstandingLoanBalance":36000.000000
]
}
</code>
@@ -9193,7 +9216,10 @@ GET https://DomainName/api/v1/loanproducts/{productId}
<td>inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle,
useBorrowerCycle,principalVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle</td>
interestRateVariationsForBorrowerCycle,
multiDisburseLoan,maxTrancheCount,
outstandingLoanBalance
</td>
</tr>
<tr class="matrixHeadingBG">
@@ -0,0 +1,38 @@
ALTER TABLE `m_product_loan`
ADD COLUMN `multi_disburse_loan` TINYINT(1) NOT NULL DEFAULT '0' AFTER `close_date`,
ADD COLUMN `max_tranche_count` INT(2) NULL DEFAULT NULL AFTER `multi_disburse_loan`,
ADD COLUMN `outstanding_loan_balance` DECIMAL(19,6) NULL DEFAULT NULL AFTER `max_tranche_count`;
CREATE TABLE `m_loan_disbursement_detail` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_id` BIGINT(20) NOT NULL,
`expected_disburse_date` DATETIME NOT NULL,
`disbursedon_date` DATETIME NULL,
`principal` DECIMAL(19,6) NOT NULL,
`approved_principal` DECIMAL(19,6) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_loan_disbursement_detail_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
ALTER TABLE `m_loan`
ADD COLUMN `fixed_emi_amount` DECIMAL(19,6) NULL AFTER `loan_product_counter`,
ADD COLUMN `approved_principal` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
ADD COLUMN `max_outstanding_loan_balance` DECIMAL(19,6) NULL DEFAULT NULL AFTER `fixed_emi_amount`;
UPDATE m_loan ml SET ml.approved_principal = ml.principal_amount;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_DISBURSEMENTDETAIL', 'DISBURSEMENTDETAIL', 'UPDATE', 0);
CREATE TABLE `m_loan_term_variations` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_id` BIGINT(20) NOT NULL,
`term_type` SMALLINT(2) NOT NULL,
`applicable_from` DATE NOT NULL,
`term_value` DECIMAL(19,6) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_loan_id_m_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
@@ -329,7 +329,10 @@ public class CommandWrapper {
public boolean isLoanChargeResource() {
return this.entityName.equalsIgnoreCase("LOANCHARGE");
}
public boolean isLoanDisburseDetailResource() {
return this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL");
}
public boolean isCollateralResource() {
return this.entityName.equalsIgnoreCase("COLLATERAL");
}
@@ -381,6 +384,10 @@ public class CommandWrapper {
public boolean isUndoLoanWriteOff() {
return this.actionName.equalsIgnoreCase("UNDOWRITEOFF") && this.entityName.equalsIgnoreCase("LOAN");
}
public boolean isUpdateDisbursementDate() {
return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL");
}
public boolean isCloseLoanAsObligationsMet() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("LOAN");
@@ -629,6 +629,16 @@ public class CommandWrapperBuilder {
this.href = "/loans/" + loanId;
return this;
}
public CommandWrapperBuilder updateDisbusementDate(final Long loanId,final Long disbursementId) {
this.actionName = "UPDATE";
this.entityName = "DISBURSEMENTDETAIL";
this.entityId = disbursementId;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/disbursementdetail/" + disbursementId;
return this;
}
public CommandWrapperBuilder deleteLoanApplication(final Long loanId) {
this.actionName = "DELETE";
@@ -348,7 +348,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("loanApplicationDeletionCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUndoLoanWriteOff()) {
handler = this.applicationContext.getBean("undoWriteOffLoanCommandHandler", NewCommandSourceHandler.class);
}else {
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isLoanChargeResource()) {
@@ -363,7 +363,11 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else if (wrapper.isPayLoanCharge()) {
handler = this.applicationContext.getBean("payLoanChargeCommandHandler", NewCommandSourceHandler.class);
}
} else if (wrapper.isGLAccountResource()) {
} else if(wrapper.isLoanDisburseDetailResource()){
if(wrapper.isUpdateDisbursementDate()){
handler = this.applicationContext.getBean("updateLoanDisbuseDateCommandHandler", NewCommandSourceHandler.class);
}
}else if (wrapper.isGLAccountResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createGLAccountCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
@@ -0,0 +1,26 @@
package org.mifosplatform.portfolio.loanaccount.api;
public interface LoanApiConstants {
public static final String emiAmountParameterName = "fixedEmiAmount";
public static final String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance";
public static final String disbursementDataParameterName = "disbursementData";
public static final String disbursementDateParameterName = "expectedDisbursementDate";
public static final String disbursementPrincipalParameterName = "principal";
public static final String DISBURSEMENT_DATE_START_WITH_ERROR = "first.disbursement.date.must.start.with.expected.disbursement.date";
public static final String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal";
public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches";
public static final String disbursementIdParameterName = "id";
public static final String principalDisbursedParameterName = "transactionAmount";
public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date";
}
@@ -0,0 +1,95 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/disbursements")
@Component
@Scope("singleton")
public class LoanDisbursementDetailApiResource {
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "expectedDisbursementDate","actualDisbursementDate",
"principal","approvedPrincipal"));
private final String resourceNameForPermissions = "LOAN";
private final DefaultToApiJsonSerializer<DisbursementData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final PlatformSecurityContext context;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final LoanReadPlatformService loanReadPlatformService;
@Autowired
public LoanDisbursementDetailApiResource(final DefaultToApiJsonSerializer<DisbursementData> toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,final PlatformSecurityContext context,
final ApiRequestParameterHelper apiRequestParameterHelper, final LoanReadPlatformService loanReadPlatformService) {
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.context = context;
this.loanReadPlatformService = loanReadPlatformService;
}
@PUT
@Path("{disbursementId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateDisbursementDate(@PathParam("loanId") final Long loanId, @PathParam("disbursementId") final Long disbursementId,
final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDisbusementDate(loanId,disbursementId).withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@GET
@Path("{disbursementId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retriveDetail(@PathParam("loanId") final Long loanId,@PathParam("disbursementId") final Long disbursementId,
@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final DisbursementData disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetail(loanId, disbursementId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, disbursementData, this.RESPONSE_DATA_PARAMETERS);
}
}
@@ -58,10 +58,13 @@ import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
import org.mifosplatform.portfolio.loanaccount.exception.NotSupportedLoanTemplateTypeException;
import org.mifosplatform.portfolio.loanaccount.guarantor.data.GuarantorData;
@@ -281,6 +284,8 @@ public class LoansApiResource {
CalendarData meeting = null;
Collection<NoteData> notes = null;
PortfolioAccountData linkedAccount = null;
Collection<DisbursementData> disbursementData = null;
Collection<LoanTermVariationsData> emiAmountVariations = null;
final Set<String> mandatoryResponseParameters = new HashSet<String>();
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
@@ -288,7 +293,7 @@ public class LoansApiResource {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "charges", "guarantors", "collateral",
"notes", "linkedAccount"));
"notes", "linkedAccount","multiDisburseDetails"));
}
if (associationParameters.contains("guarantors")) {
@@ -306,12 +311,23 @@ public class LoansApiResource {
loanRepayments = currentLoanRepayments;
}
}
if(associationParameters.contains("multiDisburseDetails") || associationParameters.contains("repaymentSchedule")){
mandatoryResponseParameters.add("multiDisburseDetails");
disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetails(loanId);
}
if(associationParameters.contains("emiAmountVariations") || associationParameters.contains("repaymentSchedule")){
mandatoryResponseParameters.add("emiAmountVariations");
emiAmountVariations = this.loanReadPlatformService.retrieveLoanTermVariations(loanId, LoanTermVariationType.EMI_AMOUNT.getValue());
}
if (associationParameters.contains("repaymentSchedule")) {
mandatoryResponseParameters.add("repaymentSchedule");
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData();
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData);
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData, disbursementData);
}
if (associationParameters.contains("charges")) {
@@ -347,6 +363,7 @@ public class LoansApiResource {
mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
}
}
Collection<LoanProductData> productOptions = null;
@@ -411,7 +428,7 @@ public class LoansApiResource {
charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount);
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData,emiAmountVariations);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -12,17 +12,25 @@ import org.joda.time.LocalDate;
/**
* Immutable data object representing disbursement information.
*/
public class DisbursementData {
public class DisbursementData implements Comparable<DisbursementData>{
@SuppressWarnings("unused")
private final Long id;
private final LocalDate expectedDisbursementDate;
private final LocalDate actualDisbursementDate;
private final BigDecimal principal;
@SuppressWarnings("unused")
private final BigDecimal approvedPrincipal;
public DisbursementData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final BigDecimal principalDisbursed) {
public DisbursementData(Long id, final LocalDate expectedDisbursementDate,
final LocalDate actualDisbursementDate, final BigDecimal principalDisbursed,
final BigDecimal approvedPrincipal) {
this.id = id;
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.principal = principalDisbursed;
this.approvedPrincipal = approvedPrincipal;
}
public LocalDate disbursementDate() {
@@ -40,4 +48,24 @@ public class DisbursementData {
public boolean isDisbursed() {
return this.actualDisbursementDate != null;
}
@Override
public int compareTo(final DisbursementData obj) {
if (obj == null) { return -1; }
return obj.expectedDisbursementDate.compareTo(this.expectedDisbursementDate);
}
public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
final LocalDate dueDate = disbursementDate();
return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate);
}
private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive,
final LocalDate target) {
return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
}
}
@@ -27,6 +27,7 @@ import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
import org.mifosplatform.portfolio.fund.data.FundData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.guarantor.data.GuarantorData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
@@ -71,6 +72,7 @@ public class LoanAccountData {
// terms
private final CurrencyData currency;
private final BigDecimal principal;
private final BigDecimal approvedPrincipal;
private final Integer termFrequency;
private final EnumOptionData termPeriodFrequencyType;
private final Integer numberOfRepayments;
@@ -108,7 +110,7 @@ public class LoanAccountData {
private final Collection<GuarantorData> guarantors;
private final CalendarData meeting;
private final Collection<NoteData> notes;
private final Collection<DisbursementData> disbursementDetails;
// template
private final Collection<LoanProductData> productOptions;
private final Collection<StaffData> loanOfficerOptions;
@@ -136,7 +138,17 @@ public class LoanAccountData {
// linkable account details
private final PortfolioAccountData linkedAccount;
private final Collection<PortfolioAccountData> accountLinkingOptions;
private final Boolean multiDisburseLoan;
private final BigDecimal fixedEmiAmount;
private final BigDecimal maxOutstandingLoanBalance;
private final Boolean canDisburse;
private final Collection<LoanTermVariationsData> emiAmountVariations;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
* for now.
@@ -212,9 +224,14 @@ public class LoanAccountData {
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
final Collection<DisbursementData> disbursementData = null;
final Boolean multiDisburseLoan = null;
final BigDecimal fixedEmiAmount = null;
final BigDecimal maxOutstandingLoanBalance = null;
final Collection<LoanTermVariationsData> emiAmountVariations = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
currencyData, principal,principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, transactionProcessingStrategyId, transactionProcessingStrategyName,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
@@ -223,7 +240,8 @@ public class LoanAccountData {
repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount);
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData,
multiDisburseLoan, fixedEmiAmount,maxOutstandingLoanBalance,emiAmountVariations);
}
/**
@@ -301,10 +319,15 @@ public class LoanAccountData {
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
final Collection<DisbursementData> disbursementData = null;
final Boolean multiDisburseLoan = null;
final BigDecimal fixedEmiAmount = null;
final BigDecimal maxOutstandingLoanBalance = null;
final Collection<LoanTermVariationsData> emiAmountVariations = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
currencyData, principal,principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, transactionProcessingStrategyId, transactionProcessingStrategyName,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
@@ -313,7 +336,8 @@ public class LoanAccountData {
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount);
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance,
emiAmountVariations);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -321,7 +345,7 @@ public class LoanAccountData {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientName,
clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency,
acc.principal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType,
acc.principal,acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType,
acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
@@ -332,7 +356,8 @@ public class LoanAccountData {
acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions,
acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount);
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,acc.maxOutstandingLoanBalance,
acc.emiAmountVariations);
}
/**
@@ -411,10 +436,15 @@ public class LoanAccountData {
final Integer loanProductCounter = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
final Collection<DisbursementData> disbursementData = null;
final Boolean multiDisburseLoan = null;
final BigDecimal fixedEmiAmount = null;
final BigDecimal maxOutstandingBalance = null;
final Collection<LoanTermVariationsData> emiAmountVariations = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
currencyData, principal,principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, transactionProcessingStrategyId, transactionProcessingStrategyName,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
@@ -423,14 +453,15 @@ public class LoanAccountData {
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount);
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount,maxOutstandingBalance,
emiAmountVariations);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,acc.approvedPrincipal,
acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments,
acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName,
acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
@@ -442,7 +473,8 @@ public class LoanAccountData {
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount);
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,acc.maxOutstandingLoanBalance,
acc.emiAmountVariations);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -514,6 +546,8 @@ public class LoanAccountData {
final Integer loanProductCounter = null;
final PortfolioAccountData linkedAccount = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final Collection<DisbursementData> disbursementData = null;
final Collection<LoanTermVariationsData> emiAmountVariations = null;
BigDecimal principal = null;
BigDecimal interestRatePerPeriod = null;
Integer numberOfRepayments = null;
@@ -542,11 +576,11 @@ public class LoanAccountData {
}
final Integer termFrequency = numberOfRepayments * product.getRepaymentEvery();
final BigDecimal fixedEmi = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), principal, totalOverpaid,
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), principal,principal, totalOverpaid,
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(),
product.getRepaymentFrequencyType(), product.getTransactionProcessingStrategyId(), transactionProcessingStrategyName,
product.getAmortizationType(), interestRatePerPeriod, product.getInterestRateFrequencyType(),
@@ -557,7 +591,8 @@ public class LoanAccountData {
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount);
notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi, product.getOutstandingLoanBalance(),
emiAmountVariations);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -595,7 +630,7 @@ public class LoanAccountData {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(),
product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
product.getCurrency(), product.getPrincipal(), acc.totalOverpaid, product.getInArrearsTolerance(), termFrequency,
product.getCurrency(), product.getPrincipal(),product.getPrincipal(), acc.totalOverpaid, product.getInArrearsTolerance(), termFrequency,
termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
product.getTransactionProcessingStrategyId(), product.getTransactionProcessingStrategyName(),
product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(),
@@ -607,7 +642,8 @@ public class LoanAccountData {
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions,
acc.linkedAccount);
acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount,product.getOutstandingLoanBalance(),
acc.emiAmountVariations);
}
/*
@@ -618,8 +654,9 @@ public class LoanAccountData {
final String externalId, final Long clientId, final String clientName, final Long clientOfficeId, final GroupGeneralData group,
final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription,
final Long fundId, final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId,
final String loanOfficerName, final CurrencyData currencyData, final BigDecimal principal, final BigDecimal totalOverpaid,
final BigDecimal inArrearsTolerance, final Integer termFrequency, final EnumOptionData termPeriodFrequencyType,
final String loanOfficerName, final CurrencyData currencyData, final BigDecimal principal,final BigDecimal approvedPrincipal,
final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance,
final Integer termFrequency, final EnumOptionData termPeriodFrequencyType,
final Integer numberOfRepayments, final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType,
final Long transactionStrategyId, final String transactionStrategyName, final EnumOptionData amortizationType,
final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, final BigDecimal annualInterestRate,
@@ -627,7 +664,8 @@ public class LoanAccountData {
final LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment,
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged,
final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter) {
final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter,
final Boolean multiDisburseLoan,final BigDecimal fixedEmiAmont , final BigDecimal outstandingLoanBalance) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -653,10 +691,12 @@ public class LoanAccountData {
final Collection<CalendarData> calendarOptions = null;
final Collection<PortfolioAccountData> accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
final Collection<DisbursementData> disbursementData= null;
final Collection<LoanTermVariationsData> emiAmountVariations= null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
currencyData, principal,approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, transactionStrategyId, transactionStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
@@ -665,7 +705,7 @@ public class LoanAccountData {
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes,
accountLinkingOptions, linkedAccount);
accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance,emiAmountVariations);
}
/*
@@ -684,10 +724,11 @@ public class LoanAccountData {
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
final PortfolioAccountData linkedAccount) {
final PortfolioAccountData linkedAccount,final Collection<DisbursementData> disbursementDetails,
final Collection<LoanTermVariationsData> emiAmountVariations) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,acc.approvedPrincipal,
acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments,
acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName,
acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
@@ -697,7 +738,8 @@ public class LoanAccountData {
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount);
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementDetails,
acc.multiDisburseLoan, acc.fixedEmiAmount,acc.maxOutstandingLoanBalance,emiAmountVariations);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -708,13 +750,13 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
acc.linkedAccount);
acc.linkedAccount, acc.disbursementDetails,acc.emiAmountVariations);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,acc.approvedPrincipal,
acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments,
acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName,
acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
@@ -726,7 +768,8 @@ public class LoanAccountData {
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount);
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,acc.maxOutstandingLoanBalance,
acc.emiAmountVariations);
}
private LoanAccountData(
@@ -750,6 +793,7 @@ public class LoanAccountData {
final String loanOfficerName, //
final CurrencyData currency,
final BigDecimal principal,
final BigDecimal approvedPrincipal,
final BigDecimal totalOverpaid,
final BigDecimal inArrearsTolerance, //
final Integer termFrequency,
@@ -775,7 +819,8 @@ public class LoanAccountData {
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter,
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
final PortfolioAccountData linkedAccount) {
final PortfolioAccountData linkedAccount,final Collection<DisbursementData> disbursementDetails,final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount,
final BigDecimal maxOutstandingLoanBalance,final Collection<LoanTermVariationsData> emiAmountVariations) {
this.id = id;
this.accountNo = accountNo;
this.status = status;
@@ -796,6 +841,7 @@ public class LoanAccountData {
this.loanOfficerName = loanOfficerName;
this.currency = currency;
this.principal = principal;
this.approvedPrincipal = approvedPrincipal;
this.totalOverpaid = totalOverpaid;
this.inArrearsTolerance = inArrearsTolerance;
this.termFrequency = termFrequency;
@@ -888,10 +934,29 @@ public class LoanAccountData {
this.linkedAccount = linkedAccount;
this.accountLinkingOptions = accountLinkingOptions;
this.disbursementDetails = disbursementDetails;
this.multiDisburseLoan = multiDisburseLoan;
this.fixedEmiAmount = fixedEmiAmount;
this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
if(this.status != null && LoanStatus.fromInt(this.status.id().intValue()).isApproved()){
this.canDisburse = true;
}else{
boolean canDisburse = false;
if(this.multiDisburseLoan != null && this.multiDisburseLoan && this.disbursementDetails != null){
for(DisbursementData disbursementData : this.disbursementDetails){
if(!disbursementData.isDisbursed()){
canDisburse = true;
}
}
}
this.canDisburse = canDisburse;
}
this.emiAmountVariations = emiAmountVariations;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
return this.timeline.repaymentScheduleRelatedData(this.currency, this.principal, this.inArrearsTolerance,
return this.timeline.repaymentScheduleRelatedData(this.currency, this.principal,this.approvedPrincipal, this.inArrearsTolerance,
this.feeChargesAtDisbursementCharged);
}
@@ -122,9 +122,9 @@ public class LoanApplicationTimelineData {
this.writeOffByLastname = writeOffByLastname;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal,
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal,final BigDecimal approvedPrincipal,
final BigDecimal inArrearsTolerance, final BigDecimal totalFeeChargesAtDisbursement) {
return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal,
return new RepaymentScheduleRelatedLoanData(this.expectedDisbursementDate, this.actualDisbursementDate, currency, principal,approvedPrincipal,
inArrearsTolerance, totalFeeChargesAtDisbursement);
}
}
@@ -0,0 +1,38 @@
package org.mifosplatform.portfolio.loanaccount.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
public class LoanTermVariationsData {
@SuppressWarnings("unused")
private final Long id;
private final EnumOptionData termType;
private final LocalDate termVariationApplicableFrom;
private final BigDecimal termValue;
public LoanTermVariationsData(final Long id, final EnumOptionData termType,
final LocalDate termVariationApplicableFrom, final BigDecimal termValue) {
this.id = id;
this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.termValue = termValue;
}
public EnumOptionData getTermType() {
return this.termType;
}
public LocalDate getTermApplicableFrom() {
return this.termVariationApplicableFrom;
}
public BigDecimal getTermValue() {
return this.termValue;
}
}
@@ -38,6 +38,7 @@ public class LoanTransactionData {
private final BigDecimal overpaymentPortion;
private final String externalId;
private final AccountTransferData transfer;
private final BigDecimal fixedEmiAmount;
// templates
final Collection<CodeValueData> paymentTypeOptions;
@@ -48,23 +49,24 @@ public class LoanTransactionData {
loanTransactionData.type, loanTransactionData.paymentDetailData, loanTransactionData.currency, loanTransactionData.date,
loanTransactionData.amount, loanTransactionData.principalPortion, loanTransactionData.interestPortion,
loanTransactionData.feeChargesPortion, loanTransactionData.penaltyChargesPortion, loanTransactionData.overpaymentPortion,
paymentTypeOptions, loanTransactionData.externalId, loanTransactionData.transfer);
paymentTypeOptions, loanTransactionData.externalId, loanTransactionData.transfer,loanTransactionData.fixedEmiAmount);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final String externalId,
final AccountTransferData transfer) {
final AccountTransferData transfer, BigDecimal fixedEmiAmount) {
this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overpaymentPortion, null, externalId, transfer);
feeChargesPortion, penaltyChargesPortion, overpaymentPortion, null, externalId, transfer,fixedEmiAmount);
}
public LoanTransactionData(final Long id, final Long officeId, final String officeName, final LoanTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final Collection<CodeValueData> paymentTypeOptions, final String externalId, final AccountTransferData transfer) {
final Collection<CodeValueData> paymentTypeOptions, final String externalId, final AccountTransferData transfer,
final BigDecimal fixedEmiAmount) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
@@ -81,6 +83,7 @@ public class LoanTransactionData {
this.externalId = externalId;
this.transfer = transfer;
this.overpaymentPortion = overpaymentPortion;
this.fixedEmiAmount = fixedEmiAmount;
}
public LocalDate dateOf() {
@@ -19,16 +19,18 @@ public class RepaymentScheduleRelatedLoanData {
private final LocalDate actualDisbursementDate;
private final CurrencyData currency;
private final BigDecimal principal;
private final BigDecimal approvedPrincipal;
private final BigDecimal inArrearsTolerance;
private final BigDecimal totalFeeChargesAtDisbursement;
public RepaymentScheduleRelatedLoanData(final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final CurrencyData currency, final BigDecimal principal, final BigDecimal inArrearsTolerance,
final CurrencyData currency, final BigDecimal principal,final BigDecimal approvedPrincipal, final BigDecimal inArrearsTolerance,
final BigDecimal totalFeeChargesAtDisbursement) {
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.currency = currency;
this.principal = principal;
this.approvedPrincipal = approvedPrincipal;
this.inArrearsTolerance = inArrearsTolerance;
this.totalFeeChargesAtDisbursement = totalFeeChargesAtDisbursement;
}
@@ -62,6 +64,6 @@ public class RepaymentScheduleRelatedLoanData {
}
public DisbursementData disbursementData() {
return new DisbursementData(this.expectedDisbursementDate, this.actualDisbursementDate, this.principal);
return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal,this.approvedPrincipal);
}
}
@@ -46,6 +46,7 @@ import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator;
import org.mifosplatform.organisation.holiday.domain.Holiday;
@@ -67,14 +68,20 @@ import org.mifosplatform.portfolio.collateral.data.CollateralData;
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
@@ -83,7 +90,6 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleM
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductBorrowerCycleVariations;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
@@ -92,6 +98,10 @@ import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@Entity
@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
@UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") })
@@ -275,42 +285,68 @@ public class Loan extends AbstractPersistable<Long> {
private LoanLifecycleStateMachine loanLifecycleStateMachine;
@Transient
private LoanSummaryWrapper loanSummaryWrapper;
@Column(name = "approved_principal", scale = 6, precision = 19, nullable = false)
private BigDecimal approvedPrincipal;
@Column(name = "fixed_emi_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal fixedEmiAmount;
@Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19, nullable = false)
private BigDecimal maxOutstandingLoanBalance;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private Set<LoanDisbursementDetails> disbursementDetails = new HashSet<LoanDisbursementDetails>();
@OrderBy(value = "termApplicableFrom, id")
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private final Set<LoanTermVariations> loanTermVariations = new HashSet<LoanTermVariations>();
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral) {
final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance) {
final LoanStatus status = null;
final Group group = null;
final Boolean syncDisbursementWithMeeting = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails,maxOutstandingLoanBalance);
}
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Boolean syncDisbursementWithMeeting) {
final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> disbursementDetails,final BigDecimal maxOutstandingLoanBalance) {
final LoanStatus status = null;
final CodeValue loanPurpose = null;
final Set<LoanCollateral> collateral = null;
final Client client = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails,maxOutstandingLoanBalance);
}
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Boolean syncDisbursementWithMeeting) {
final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> disbursementDetails,final BigDecimal maxOutstandingLoanBalance) {
final LoanStatus status = null;
final CodeValue loanPurpose = null;
final Set<LoanCollateral> collateral = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting);
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails,maxOutstandingLoanBalance);
}
protected Loan() {
@@ -320,7 +356,9 @@ public class Loan extends AbstractPersistable<Long> {
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting) {
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
@@ -360,6 +398,10 @@ public class Loan extends AbstractPersistable<Long> {
this.loanOfficerHistory = null;
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
this.fixedEmiAmount = fixedEmiAmount;
this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
this.disbursementDetails = disbursementDetails;
this.approvedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
}
private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
@@ -1191,10 +1233,141 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put(loanTermFrequencyTypeParamName, newTermPeriodFrequencyType.getValue());
this.termPeriodFrequencyType = newValue;
}
final String principalParamName = "principal";
if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.approvedPrincipal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
this.approvedPrincipal = newValue;
}
if (loanProduct.isMultiDisburseLoan()) {
updateDisbursementDetails(command, actualChanges);
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName, this.maxOutstandingLoanBalance)) {
this.maxOutstandingLoanBalance = command.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName);
}
if (this.disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if(disbursementDetails.size() > loanProduct.maxTrancheCount()){
final String errorMessage = "Number of tranche shouldn't be greter than "+loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(),disbursementDetails.size());
}
} else {
this.disbursementDetails.clear();
this.fixedEmiAmount = null;
}
return actualChanges;
}
private void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) {
List<Long> list = fetchDisbursementIds();
if (command.parameterExists(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
String dateFormat = null;
Locale locale = null;
if (command.parsedJson().isJsonObject()) {
JsonObject topLevel = command.parsedJson().getAsJsonObject();
final String dateFormatParameter = "dateFormat";
if (topLevel.has(dateFormatParameter) && topLevel.get(dateFormatParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(dateFormatParameter).getAsJsonPrimitive();
dateFormat = primitive.getAsString();
}
final String localeParameter = "locale";
if (topLevel.has(localeParameter) && topLevel.get(localeParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(localeParameter).getAsJsonPrimitive();
String localeString = primitive.getAsString();
locale = JsonParserHelper.localeFromString(localeString);
}
}
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
Date expectedDisbursementDate = null;
BigDecimal principal = null;
LocalDate date = null;
Long id = null;
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)
&& jsonObject.get(LoanApiConstants.disbursementDateParameterName) != null
&& jsonObject.get(LoanApiConstants.disbursementDateParameterName).isJsonPrimitive()) {
final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName, dateFormat,
locale);
}
}
if (date != null) {
expectedDisbursementDate = date.toDate();
}
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanApiConstants.disbursementIdParameterName)
&& jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) {
id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong();
}
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, null, principal);
if (id == null) {
disbursementDetails.updateLoan(this);
this.disbursementDetails.add(disbursementDetails);
actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
actualChanges.put("recalculateLoanSchedule", true);
} else {
list.remove(id);
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id);
if (!loanDisbursementDetail.equals(disbursementDetails)) {
loanDisbursementDetail.copy(disbursementDetails);
actualChanges.put("disbursementDetailId", id);
actualChanges.put("recalculateLoanSchedule", true);
}
}
i++;
} while (i < disbursementDataArray.size());
for (Long id : list) {
this.disbursementDetails.remove(fetchLoanDisbursementsById(id));
actualChanges.put("recalculateLoanSchedule", true);
}
}
}
}
public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) {
LoanDisbursementDetails loanDisbursementDetail = null;
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (id.equals(disbursementDetail.getId())) {
loanDisbursementDetail = disbursementDetail;
break;
}
}
return loanDisbursementDetail;
}
private List<Long> fetchDisbursementIds() {
List<Long> list = new ArrayList<Long>();
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
list.add(disbursementDetails.getId());
}
return list;
}
private CollateralData[] listOfLoanCollateralData(final Set<LoanCollateral> setOfLoanCollateral) {
CollateralData[] existingLoanCollateral = null;
@@ -1512,18 +1685,32 @@ public class Loan extends AbstractPersistable<Long> {
return ids;
}
public Map<String, Object> disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
public void disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges, final PaymentDetail paymentDetail,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) {
validateAccountStatus(LoanEvent.LOAN_DISBURSED);
updateLoanToPreDisbursalState();
Date lastDusburseDate = this.actualDisbursementDate;
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
LoanStatus.fromInt(this.loanStatus));
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
boolean isMultiTrancheDisburse = false;
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
if(LoanStatus.fromInt(this.loanStatus).isActive() && isAllTranchesNotDisbursed()){
LoanDisbursementDetails details = fetchLastDisburseDetail();
if(details != null){
lastDusburseDate = details.actualDisbursementDate();
}
if(actualDisbursementDate.toDate().before(lastDusburseDate)){
final String errorMsg = "Loan can't be disbursed before "+lastDusburseDate;
throw new LoanDisbursalException(errorMsg, "actualdisbursementdate.before.lastdusbursedate", lastDusburseDate,actualDisbursementDate.toDate());
}
isMultiTrancheDisburse = true;
}
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus)) || isMultiTrancheDisburse) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
@@ -1531,10 +1718,43 @@ public class Loan extends AbstractPersistable<Long> {
this.loanStatus = statusEnum.getValue();
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
this.actualDisbursementDate = actualDisbursementDate.toDate();
Money disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal().zero();
BigDecimal principalDisbursed = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalDisbursedParameterName);
if(this.actualDisbursementDate == null){
this.actualDisbursementDate = actualDisbursementDate.toDate();
}
BigDecimal diff = BigDecimal.ZERO;
Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
if(principalDisbursed == null){
disburseAmount = this.loanRepaymentScheduleDetail.getPrincipal();
if(!details.isEmpty()){
disburseAmount = disburseAmount.zero();
for(LoanDisbursementDetails disbursementDetails:details){
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
disburseAmount = disburseAmount.plus(disbursementDetails.principal());
}
}
}else{
disburseAmount = disburseAmount.plus(principalDisbursed);
if(details.isEmpty()){
diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount();
}else{
for(LoanDisbursementDetails disbursementDetails:details){
disbursementDetails.updateActualDisbursementDate(actualDisbursementDate.toDate());
diff = diff.add(disbursementDetails.principal().subtract(principalDisbursed));
disbursementDetails.updatePrincipal(principalDisbursed);
}
}
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
if(diff.compareTo(BigDecimal.ZERO) == -1){
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved amount ";
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.amount", principalDisbursed,
this.loanRepaymentScheduleDetail.getPrincipal().getAmount());
}
}
this.disbursedBy = currentUser;
updateLoanScheduleDependentDerivedFields();
@@ -1546,9 +1766,15 @@ public class Loan extends AbstractPersistable<Long> {
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId);
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId,disburseAmount);
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
boolean isEmiAmountChanged = false;
if(this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0){
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),actualDisbursementDate.toDate(),emiAmount,this);
this.loanTermVariations.add(loanVariationTerms);
isEmiAmountChanged = true;
}
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || diff.compareTo(BigDecimal.ZERO) !=0 || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays);
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
@@ -1561,9 +1787,114 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction interestAppliedTransaction = LoanTransaction.applyInterest(getOffice(), this, interestApplied,
actualDisbursementDate);
this.loanTransactions.add(interestAppliedTransaction);
// changedTransactionDetail = reprocessTransactionForDisbursement(changedTransactionDetail);
}
return actualChanges;
}
public ChangedTransactionDetail reprocessTransactionForDisbursement() {
ChangedTransactionDetail changedTransactionDetail = null;
for(LoanCharge loanCharge:this.charges){
if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
}
}
if(this.loanProduct.isMultiDisburseLoan()){
if(!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())){
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
}
}
updateLoanSummaryDerivedFields();
}
return changedTransactionDetail;
}
private Collection<LoanDisbursementDetails> fetchUndisbursedDetail(){
Collection<LoanDisbursementDetails> disbursementDetails = new ArrayList<LoanDisbursementDetails>();
Date date = null;
for(LoanDisbursementDetails disbursementDetail:this.disbursementDetails){
if(disbursementDetail.actualDisbursementDate() == null){
if(date == null || disbursementDetail.expectedDisbursementDate().equals(date)){
disbursementDetails.add(disbursementDetail);
date =disbursementDetail.expectedDisbursementDate();
}else if(disbursementDetail.expectedDisbursementDate().before(date)){
disbursementDetails.clear();
disbursementDetails.add(disbursementDetail);
date =disbursementDetail.expectedDisbursementDate();
}
}
}
return disbursementDetails;
}
private LoanDisbursementDetails fetchLastDisburseDetail(){
LoanDisbursementDetails details = null;
Date date = this.actualDisbursementDate;
if(date != null){
for(LoanDisbursementDetails disbursementDetail:this.disbursementDetails){
if(disbursementDetail.actualDisbursementDate() != null){
if(disbursementDetail.actualDisbursementDate().after(date) ||
disbursementDetail.actualDisbursementDate().equals(date)){
date =disbursementDetail.actualDisbursementDate();
details = disbursementDetail;
}
}
}
}
return details;
}
private BigDecimal getDisbursedAmount(){
BigDecimal principal = BigDecimal.ZERO;
for(LoanDisbursementDetails disbursementDetail:this.disbursementDetails){
if(disbursementDetail.actualDisbursementDate() != null){
principal = principal.add(disbursementDetail.principal());
}
}
return principal;
}
private void removeDisbursementDetail(){
Set<LoanDisbursementDetails> details = new HashSet<LoanDisbursementDetails>(this.disbursementDetails);
for(LoanDisbursementDetails disbursementDetail:details){
if(disbursementDetail.actualDisbursementDate() == null){
this.disbursementDetails.remove(disbursementDetail);
}
}
}
private boolean isDisbursementAllowed(){
boolean isAllowed = false;
for(LoanDisbursementDetails disbursementDetail:this.disbursementDetails){
if(disbursementDetail.actualDisbursementDate() == null){
isAllowed = true;
break;
}
}
return isAllowed;
}
private boolean atleastOnceDisbursed(){
boolean isDisbursed = false;
for(LoanDisbursementDetails disbursementDetail:this.disbursementDetails){
if(disbursementDetail.actualDisbursementDate() != null){
isDisbursed = true;
break;
}
}
return isDisbursed;
}
private void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
@@ -1589,9 +1920,30 @@ public class Loan extends AbstractPersistable<Long> {
final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail);
final List<DisbursementData> disbursementData = new ArrayList<DisbursementData>();
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
boolean isDefaultEmiAmountReq = true;
for(LoanTermVariations variationTerms:this.loanTermVariations){
if(variationTerms.getTermType().isEMIAmountVariation()){
if(variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())){
isDefaultEmiAmountReq =false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if(isDefaultEmiAmountReq){
LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms
.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(),
getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, getInArrearsTolerance(),
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
this.maxOutstandingLoanBalance,loanVariationTermsData);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms,charges(),
isHolidayEnabled, holidays, workingDays);
@@ -1600,10 +1952,10 @@ public class Loan extends AbstractPersistable<Long> {
}
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn,
final String txnExternalId) {
final String txnExternalId, Money disburseAmt) {
// track disbursement transaction
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(getOffice(),
this.loanRepaymentScheduleDetail.getPrincipal(), paymentDetail, disbursedOn, txnExternalId);
disburseAmt, paymentDetail, disbursedOn, txnExternalId);
disbursementTransaction.updateLoan(this);
this.loanTransactions.add(disbursementTransaction);
@@ -1620,27 +1972,29 @@ public class Loan extends AbstractPersistable<Long> {
* "APPLY Charge" transactions are created for all other fees ( which
* are created during disbursal but not repaid)
**/
Money disbursentMoney = Money.zero(getCurrency());
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn,
null);
for (final LoanCharge charge : charges()) {
if (charge.isDueAtDisbursement()) {
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
charge.markAsFullyPaid();
// Add "Loan Charge Paid By" details to this transaction
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount());
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
disbursentMoney = disbursentMoney.plus(charge.amount());
if(disbursedOn.toDate().equals(this.actualDisbursementDate)){
Money disbursentMoney = Money.zero(getCurrency());
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn,
null);
for (final LoanCharge charge : charges()) {
if (charge.isDueAtDisbursement()) {
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
charge.markAsFullyPaid();
// Add "Loan Charge Paid By" details to this transaction
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount());
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
disbursentMoney = disbursentMoney.plus(charge.amount());
}
} else {
handleChargeAppliedTransaction(charge, disbursedOn);
}
} else {
handleChargeAppliedTransaction(charge, disbursedOn);
}
}
if (disbursentMoney.isGreaterThanZero()) {
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
if (disbursentMoney.isGreaterThanZero()) {
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(this);
this.loanTransactions.add(chargesPayment);
}
}
if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) {
@@ -1706,18 +2060,27 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
final LocalDate actualDisbursementDate = getDisbursementDate();
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
final boolean isScheduleRegenerateRequired = isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate);
this.actualDisbursementDate = null;
this.disbursedBy = null;
boolean isDisbueseAmtChanged = !this.approvedPrincipal.equals(this.loanRepaymentScheduleDetail.getPrincipal());
this.loanRepaymentScheduleDetail.setPrincipal(this.approvedPrincipal);
if(this.loanProduct.isMultiDisburseLoan()){
for(final LoanDisbursementDetails details:this.disbursementDetails){
details.updateActualDisbursementDate(null);
details.resetPrincipal();
}
}
boolean isEmiAmountChanged = this.loanTermVariations.size()>0;
updateLoanToPreDisbursalState();
if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged) {
// clear off actual disbusrement date so schedule regeneration
// uses expected date.
this.actualDisbursementDate = null;
this.disbursedBy = null;
regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays);
}
this.actualDisbursementDate = null;
this.disbursedBy = null;
actualChanges.put("actualDisbursementDate", "");
existingTransactionIds.addAll(findExistingTransactionIds());
@@ -1725,7 +2088,6 @@ public class Loan extends AbstractPersistable<Long> {
reverseExistingTransactions();
updateLoanToPreDisbursalState();
}
return actualChanges;
@@ -1749,6 +2111,7 @@ public class Loan extends AbstractPersistable<Long> {
currentInstallment.resetDerivedComponents();
}
this.loanTermVariations.clear();
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
@@ -1898,6 +2261,15 @@ public class Loan extends AbstractPersistable<Long> {
loanTransaction.getAmount(loanCurrency()), totalInterestOutstandingOnLoan.getAmount());
}
}
if(this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null){
BigDecimal totalDisbursed = getDisbursedAmount();
if(totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid())<0){
final String errorMessage = "The transaction cannot be done before the loan disbursement: "
+ getApprovedOnDate().toString();
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage);
}
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
@@ -2142,7 +2514,6 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds){
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction writeOffTransaction = findWriteOffTransaction();
@@ -2151,8 +2522,9 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges());
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
}
@@ -2205,9 +2577,17 @@ public class Loan extends AbstractPersistable<Long> {
return this.loanRepaymentScheduleDetail.getCurrency();
}
public LoanTransaction closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final AppUser currentUser) {
final AppUser currentUser,final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays, loanRepaymentScheduleTransactionProcessor);
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
@@ -2244,7 +2624,7 @@ public class Loan extends AbstractPersistable<Long> {
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);
}
loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions);
if (!isLastTransaction) {
@@ -2255,18 +2635,58 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(loanTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
updateLoanSummaryDerivedFields();
}
return loanTransaction;
if(changedTransactionDetail == null){
changedTransactionDetail = new ChangedTransactionDetail();
}
changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction);
return changedTransactionDetail;
}
public LoanTransaction close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
ChangedTransactionDetail changedTransactionDetail = null;
if(isDisbursementAllowed() && atleastOnceDisbursed()){
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
removeDisbursementDetail();
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue());
}
updateLoanSummaryDerivedFields();
LoanTransaction loanTransaction = findlatestTransaction();
doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
}
return changedTransactionDetail;
}
private LoanTransaction findlatestTransaction(){
LoanTransaction transaction = null;
for(LoanTransaction loanTransaction:this.loanTransactions){
if(!loanTransaction.isReversed() &&
(transaction == null || transaction.getTransactionDate().isBefore(loanTransaction.getTransactionDate()))){
transaction = loanTransaction;
}
}
return transaction;
}
public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -2291,6 +2711,10 @@ public class Loan extends AbstractPersistable<Long> {
final String errorMessage = "The date on which a loan is closed cannot be in the future.";
throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays, loanRepaymentScheduleTransactionProcessor);
LoanTransaction loanTransaction = null;
if (isOpen()) {
@@ -2304,7 +2728,6 @@ public class Loan extends AbstractPersistable<Long> {
changes.put("status", LoanEnumerations.status(this.loanStatus));
}
this.closedOnDate = closureDate.toDate();
loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, this.loanTransactions);
if (!isLastTransaction) {
@@ -2315,8 +2738,6 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(loanTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(),
this.repaymentScheduleInstallments);
@@ -2346,8 +2767,12 @@ public class Loan extends AbstractPersistable<Long> {
throw new InvalidLoanStateTransitionException("close", "loan.is.overpaid", errorMessage, totalLoanOverpayment.toString());
}
}
return loanTransaction;
if(changedTransactionDetail == null){
changedTransactionDetail = new ChangedTransactionDetail();
}
changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction);
return changedTransactionDetail;
}
/**
@@ -2440,6 +2865,13 @@ public class Loan extends AbstractPersistable<Long> {
private boolean isOpen() {
return status().isActive();
}
private boolean isAllTranchesNotDisbursed(){
return this.loanProduct.isMultiDisburseLoan()
&& (LoanStatus.fromInt(this.loanStatus).isActive()
||LoanStatus.fromInt(this.loanStatus).isApproved())
&& isDisbursementAllowed();
}
private boolean hasDisbursementTransaction() {
boolean hasRepaymentTransaction = false;
@@ -2483,6 +2915,34 @@ public class Loan extends AbstractPersistable<Long> {
}
return disbursementDate;
}
public LocalDate getExpectedDisbursedOnLocalDateForTemplate(){
LocalDate expectedDisbursementDate = null;
if (this.expectedDisbursementDate != null) {
expectedDisbursementDate = new LocalDate(this.expectedDisbursementDate);
}
Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
if(!details.isEmpty()){
for(LoanDisbursementDetails disbursementDetails:details){
expectedDisbursementDate = new LocalDate(disbursementDetails.expectedDisbursementDate());
}
}
return expectedDisbursementDate;
}
public BigDecimal getDisburseAmountForTemplate(){
BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
if(!details.isEmpty()){
principal =BigDecimal.ZERO;
for(LoanDisbursementDetails disbursementDetails:details){
principal = principal.add(disbursementDetails.principal());
}
}
return principal;
}
public LocalDate getExpectedFirstRepaymentOnDate() {
LocalDate firstRepaymentDate = null;
@@ -2498,7 +2958,14 @@ public class Loan extends AbstractPersistable<Long> {
}
private boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDate actualDisbursedOnDate) {
return !new LocalDate(this.expectedDisbursementDate).isEqual(actualDisbursedOnDate);
boolean isRegenerationRequired = false;
if(this.loanProduct.isMultiDisburseLoan()){
LoanDisbursementDetails details = fetchLastDisburseDetail();
if(!details.expectedDisbursementDate().equals(details.actualDisbursementDate())){
isRegenerationRequired = true;
}
}
return !new LocalDate(this.expectedDisbursementDate).isEqual(actualDisbursedOnDate) || isRegenerationRequired;
}
private boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) {
@@ -2947,6 +3414,9 @@ public class Loan extends AbstractPersistable<Long> {
}
public Integer getLoanProductLoanCounter() {
if(this.loanProductCounter == null){
return 0;
}
return this.loanProductCounter;
}
@@ -3115,7 +3585,8 @@ public class Loan extends AbstractPersistable<Long> {
}
break;
case LOAN_DISBURSED:
if (!(isApproved() && isNotDisbursed())) {
if ((!(isApproved() && isNotDisbursed()) && !this.loanProduct.isMultiDisburseLoan()) ||
(this.loanProduct.isMultiDisburseLoan() && !isAllTranchesNotDisbursed())) {
final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state.";
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
@@ -3188,6 +3659,14 @@ public class Loan extends AbstractPersistable<Long> {
dataValidationErrors.add(error);
}
break;
case LOAN_EDIT_MULTI_DISBURSE_DATE:
if (isClosed()) {
final String defaultUserMessage = "Edit disbursement is not allowed. Loan Account is not active.";
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.edit.disbursement.account.is.not.active", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
default:
break;
}
@@ -3214,5 +3693,51 @@ public class Loan extends AbstractPersistable<Long> {
}
return list;
}
public Set<LoanDisbursementDetails> getDisbursementDetails() {
return this.disbursementDetails;
}
public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails,final JsonCommand command,final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays){
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate());
actualChanges.put(LoanApiConstants.disbursementDateParameterName, command.stringValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName));
actualChanges.put(LoanApiConstants.disbursementIdParameterName, command.stringValueOfParameterNamed(LoanApiConstants.disbursementIdParameterName));
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue());
}
return changedTransactionDetail;
}
public BigDecimal retriveLastEmiAmount(){
BigDecimal emiAmount = this.fixedEmiAmount;
Date startDate = this.getDisbursementDate().toDate();
for(LoanTermVariations loanTermVariations:this.loanTermVariations){
if(loanTermVariations.getTermType().isEMIAmountVariation() &&
!startDate.after(loanTermVariations.getTermApplicableFrom())){
startDate = loanTermVariations.getTermApplicableFrom();
emiAmount = loanTermVariations.getTermValue();
}
}
return emiAmount;
}
}
@@ -662,15 +662,14 @@ public class LoanCharge extends AbstractPersistable<Long> {
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
this.paid = true;
} else {
amountPaidOnThisCharge = processAmount;
amountPaidToDate = amountPaidToDate.plus(processAmount);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
}
this.paid = determineIfFullyPaid();
return amountPaidOnThisCharge;
}
@@ -767,6 +766,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
public boolean hasNoLoanInstallmentCharges(){
return this.loanInstallmentCharge.isEmpty();
}
public Set<LoanInstallmentCharge> installmentCharges(){
return this.loanInstallmentCharge;
}
public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(final Money trasferedAmount){
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
@@ -0,0 +1,129 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_disbursement_detail")
public class LoanDisbursementDetails extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Temporal(TemporalType.DATE)
@Column(name = "expected_disburse_date")
private Date expectedDisbursementDate;
@Temporal(TemporalType.DATE)
@Column(name = "disbursedon_date")
private Date actualDisbursementDate;
@Column(name = "principal", scale = 6, precision = 19, nullable = false)
private BigDecimal principal;
@Column(name = "approved_principal", scale = 6, precision = 19, nullable = false)
private BigDecimal approvedPrincipal;
protected LoanDisbursementDetails() {
}
public LoanDisbursementDetails(final Date expectedDisbursementDate, final Date actualDisbursementDate, final BigDecimal principal) {
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.principal = principal;
this.approvedPrincipal = principal;
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
@Override
public boolean equals(final Object obj) {
final LoanDisbursementDetails loanDisbursementDetails = (LoanDisbursementDetails) obj;
if (loanDisbursementDetails.principal.equals(this.principal)
&& loanDisbursementDetails.expectedDisbursementDate.equals(this.expectedDisbursementDate)
&& loanDisbursementDetails.approvedPrincipal.equals(this.approvedPrincipal)) { return true; }
return false;
}
public void copy(final LoanDisbursementDetails disbursementDetails) {
this.principal = disbursementDetails.principal;
this.approvedPrincipal = disbursementDetails.approvedPrincipal;
this.expectedDisbursementDate = disbursementDetails.expectedDisbursementDate;
this.actualDisbursementDate = disbursementDetails.actualDisbursementDate;
}
public Date expectedDisbursementDate() {
return this.expectedDisbursementDate;
}
public Date actualDisbursementDate() {
return this.actualDisbursementDate;
}
public BigDecimal principal() {
return this.principal;
}
public void updatePrincipal(BigDecimal principal){
this.principal = principal;
}
public Date getDisbursementDate() {
Date disbursementDate = this.expectedDisbursementDate;
if (this.actualDisbursementDate != null) {
disbursementDate = this.actualDisbursementDate;
}
return disbursementDate;
}
public DisbursementData toData() {
LocalDate expectedDisburseDate = null;
LocalDate actualDisburseDate = null;
if(this.expectedDisbursementDate != null){
expectedDisburseDate = new LocalDate(this.expectedDisbursementDate);
actualDisburseDate = expectedDisburseDate;
}
if(getDisbursementDate() != null){
actualDisburseDate = new LocalDate(getDisbursementDate());
}
return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal,this.approvedPrincipal);
}
public void updateActualDisbursementDate(Date actualDisbursementDate) {
this.actualDisbursementDate = actualDisbursementDate;
}
public BigDecimal resetPrincipal() {
return this.principal = this.approvedPrincipal;
}
public void updateExpectedDisbursementDate(Date expectedDisbursementDate) {
this.expectedDisbursementDate = expectedDisbursementDate;
}
}
@@ -24,5 +24,6 @@ public enum LoanEvent {
LOAN_RESCHEDULE, //
INTERST_REBATE_OWED, //
LOAN_OVERPAYMENT, LOAN_CHARGE_PAYMENT,
LOAN_CLOSED;
LOAN_CLOSED,
LOAN_EDIT_MULTI_DISBURSE_DATE;
}
@@ -104,7 +104,9 @@ public class LoanRepaymentScheduleProcessingWrapper {
if(loanCharge.isFeeCharge()){
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
if(loanChargePerInstallment != null){
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
@@ -123,7 +125,9 @@ public class LoanRepaymentScheduleProcessingWrapper {
if(loanCharge.isFeeCharge()){
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
if(loanChargePerInstallment != null){
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
@@ -186,7 +190,9 @@ public class LoanRepaymentScheduleProcessingWrapper {
if (loanCharge.isPenaltyCharge()) {
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
if(loanChargePerInstallment != null){
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWaived(currency));
}
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWaived(currency));
}
@@ -205,7 +211,9 @@ public class LoanRepaymentScheduleProcessingWrapper {
if (loanCharge.isPenaltyCharge()) {
if(loanCharge.isInstalmentFee()){
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
if(loanChargePerInstallment!= null){
cumulative = cumulative.plus(loanChargePerInstallment.getAmountWrittenOff(currency));
}
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.getAmountWrittenOff(currency));
}
@@ -279,4 +279,9 @@ public final class LoanSummary {
public BigDecimal getTotalPrincipalDisbursed() {
return this.totalPrincipalDisbursed;
}
public BigDecimal getTotalPrincipalRepaid() {
return this.totalPrincipalRepaid;
}
}
@@ -0,0 +1,38 @@
package org.mifosplatform.portfolio.loanaccount.domain;
public enum LoanTermVariationType {
INVALID(0, "loanTermType.invalid"), //
EMI_AMOUNT(1, "loanTermType.emiAmount"); //
private final Integer value;
private final String code;
private LoanTermVariationType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public static LoanTermVariationType fromInt(final Integer value) {
LoanTermVariationType enumeration = LoanTermVariationType.INVALID;
switch (value) {
case 1:
enumeration = LoanTermVariationType.EMI_AMOUNT;
break;
}
return enumeration;
}
public boolean isEMIAmountVariation() {
return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue());
}
}
@@ -0,0 +1,74 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_term_variations")
public class LoanTermVariations extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "term_type", nullable = false)
private Integer termType;
@Temporal(TemporalType.DATE)
@Column(name = "applicable_from")
private Date termApplicableFrom;
@Column(name = "term_value", scale = 6, precision = 19)
private BigDecimal termValue;
public LoanTermVariations(final Integer termType,final Date termApplicableFrom,final BigDecimal termValue,final Loan loan){
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.termValue = termValue;
}
protected LoanTermVariations(){
}
public LoanTermVariationType getTermType() {
return LoanTermVariationType.fromInt(this.termType);
}
public LoanTermVariationsData toData() {
LocalDate termStartDate = new LocalDate(this.termApplicableFrom);
EnumOptionData type = LoanEnumerations.loanvariationType(this.termType);
return new LoanTermVariationsData(getId(), type, termStartDate, this.termValue);
}
public Date getTermApplicableFrom() {
return this.termApplicableFrom;
}
public BigDecimal getTermValue() {
return this.termValue;
}
}
@@ -441,7 +441,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
}
return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData,
currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion,
this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer);
this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null);
}
public Map<String, Object> toMapData(final CurrencyData currencyData) {
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.loanaccount.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class ExceedingTrancheCountException extends AbstractPlatformDomainRuleException {
public ExceedingTrancheCountException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".exceeding.max.tranche.count", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -9,4 +9,7 @@ public class LoanDisbursalException extends AbstractPlatformDomainRuleException
+ "` are not allowed to co-exist", new Object[] { cureentProduct, restrictedProduct });
}
public LoanDisbursalException(final String defaultUserMessage,final String entity,final Object... defaultUserMessageArgs) {
super("error.msg.loan." + entity , defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.loanaccount.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementDataRequiredException extends AbstractPlatformDomainRuleException {
public MultiDisbursementDataRequiredException(final String entity, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg." + entity + ".required", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateLoanDisbuseDateCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService;
@Autowired
public UpdateLoanDisbuseDateCommandHandler(final LoanWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateDisbursementDateForTranche(command.getLoanId(), command.entityId(), command);
}
}
@@ -72,7 +72,7 @@ public class LoanSchedulePeriodData {
public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) {
final Integer periodNumber = Integer.valueOf(0);
final Integer periodNumber = null;
final LocalDate from = null;
return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, feeChargesDueAtTimeOfDisbursement,
isDisbursed);
@@ -19,7 +19,11 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
/**
*
@@ -35,10 +39,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final List<Holiday> holidays, final WorkingDays workingDays) {
// 1. generate list of proposed schedule due dates
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays,
final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
final LocalDate loanEndDate = scheduledDates.get(scheduledDates.size() - 1);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
// 2. determine the total charges due at time of disbursement
@@ -46,7 +48,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// 3. setup variables for tracking important facts required for loan
// schedule generation.
final Money principalDisbursed = loanApplicationTerms.getPrincipal();
Money principalDisbursed = loanApplicationTerms.getPrincipal();
final Money expectedPrincipalDisburse = loanApplicationTerms.getPrincipal();
final MonetaryCurrency currency = principalDisbursed.getCurrency();
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
@@ -67,20 +70,44 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mc);
// 5. loop over the known set of 'scheduled due dates'
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), scheduledDates);
LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
LocalDate periodStartDateApplicableForInterest = periodStartDate;
LocalDate actualRepaymentDate = periodStartDate;
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
LocalDate periodStartDateApplicableForInterest = periodStartDate;
int periodNumber = 1;
Money totalCumulativePrincipal = principalDisbursed.zero();
Money totalCumulativeInterest = principalDisbursed.zero();
Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero();
Money outstandingBalance = principalDisbursed;
for (final LocalDate scheduledDueDate : scheduledDates) {
if(loanApplicationTerms.isMultiDisburseLoan()){
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, periodStartDate, periods,chargesDueAtTimeOfDisbursement);
principalDisbursed = principalDisbursed.zero().plus(disburseAmt);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
}
while(!outstandingBalance.isZero()) {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms);
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
if(loanApplicationTerms.isMultiDisburseLoan()){
loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);
BigDecimal disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, periods,BigDecimal.ZERO);
principalDisbursed = principalDisbursed.plus(disburseAmt);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt));
outstandingBalance = outstandingBalance.plus(disburseAmt);
if(loanApplicationTerms.getMaxOutstandingBalance() != null &&
outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())){
String errorMsg = "Outstanding balance must not exceed the amount: "+loanApplicationTerms.getMaxOutstandingBalance();
throw new MultiDisbursementOutstandingAmoutException(errorMsg,
loanApplicationTerms.getMaxOutstandingBalance().getAmount(),disburseAmt);
}
}
int daysInPeriodApplicableForInterest = daysInPeriod;
if (periodStartDate.isBefore(idealDisbursementDate)) {
@@ -99,6 +126,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInPeriodApplicableForInterest, outstandingBalance, loanApplicationTerms, periodNumber, mc);
if(loanApplicationTerms.getFixedEmiAmount() !=null
&& loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1){
String errorMsg = "EMI amount must be greter than : "+principalInterestForThisPeriod.interest().getAmount();
throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(),
loanApplicationTerms.getFixedEmiAmount());
}
// update cumulative fields for principal & interest
totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal());
totalCumulativeInterest = totalCumulativeInterest.plus(principalInterestForThisPeriod.interest());
@@ -107,12 +140,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// 6. update outstandingLoanBlance using correct 'principalDue'
outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal());
Money feeChargesForInstallment = principalDisbursed.zero();
Money penaltyChargesForInstallment = principalDisbursed.zero();
if(!(loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getFixedEmiAmount()!=null)){
// 7. determine fees and penalties
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency,
principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges,
currency, principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency,
principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges,
currency, principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
}
// 8. sum up real totalInstallmentDue from components
final Money totalInstallmentDue = principalInterestForThisPeriod.principal() //
.plus(principalInterestForThisPeriod.interest()) //
@@ -137,6 +173,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
periodNumber++;
}
if(principalDisbursed.isNotEqualTo(expectedPrincipalDisburse)){
final String errorMsg = "One of the Disbursement date is not falling on Loan Schedule";
throw new MultiDisbursementDisbursementDateException(errorMsg);
}
if(loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getFixedEmiAmount()!=null
&& !loanCharges.isEmpty()){
// 7. determine fees and penalties
for(LoanScheduleModelPeriod loanScheduleModelPeriod:periods){
if(loanScheduleModelPeriod.isRepaymentPeriod()){
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency,principalInterest, principalDisbursed,
totalCumulativeInterest, numberOfRepayments);
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, totalCumulativeInterest,
numberOfRepayments);
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
}
}
}
return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
@@ -161,15 +222,49 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
return chargesDueAtTimeOfDisbursement;
}
private BigDecimal disbursementForPeriod(final LoanApplicationTerms loanApplicationTerms,LocalDate startDate,LocalDate endDate,
final Collection<LoanScheduleModelPeriod> periods,final BigDecimal chargesDueAtTimeOfDisbursement){
BigDecimal principal = BigDecimal.ZERO;
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
for(DisbursementData disbursementData:loanApplicationTerms.getDisbursementDatas()){
if(disbursementData.isDueForDisbursement(startDate, endDate)){
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
disbursementData.disbursementDate(),Money.of(currency,disbursementData.amount()),chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);
principal = principal.add(disbursementData.amount());
}
}
return principal;
}
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms,LocalDate disbursementDate,
final Collection<LoanScheduleModelPeriod> periods,final BigDecimal chargesDueAtTimeOfDisbursement){
BigDecimal principal = BigDecimal.ZERO;
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
for(DisbursementData disbursementData:loanApplicationTerms.getDisbursementDatas()){
if(disbursementData.disbursementDate().equals(disbursementDate)){
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
disbursementData.disbursementDate(),Money.of(currency,disbursementData.amount()),chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);
principal = principal.add(disbursementData.amount());
}
}
return principal;
}
private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
final Collection<LoanScheduleModelPeriod> periods = new ArrayList<LoanScheduleModelPeriod>(numberOfRepayments + 1);
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);
Collection<LoanScheduleModelPeriod> periods = null ;
if(loanApplicationTerms.isMultiDisburseLoan()){
periods = new ArrayList<LoanScheduleModelPeriod>(numberOfRepayments + loanApplicationTerms.getDisbursementDatas().size());
}else{
periods = new ArrayList<LoanScheduleModelPeriod>(numberOfRepayments + 1);
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);
}
return periods;
}
@@ -5,7 +5,6 @@
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
@@ -18,41 +17,47 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public List<LocalDate> generate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
final List<LocalDate> dueRepaymentPeriodDates = new ArrayList<LocalDate>(numberOfRepayments);
LocalDate startDate = loanApplicationTerms.getExpectedDisbursementDate();
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
LocalDate dueRepaymentPeriodDate = startDate;
if (repaymentPeriod == 1 && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else {
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, startDate);
}
startDate = dueRepaymentPeriodDate;
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, dueRepaymentPeriodDate);
dueRepaymentPeriodDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(dueRepaymentPeriodDate, nextDueRepaymentPeriodDate,
workingDays);
if (isHolidayEnabled) {
dueRepaymentPeriodDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(dueRepaymentPeriodDate, holidays);
}
dueRepaymentPeriodDates.add(dueRepaymentPeriodDate);
}
return dueRepaymentPeriodDates;
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms);
}
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
return lastRepaymentDate;
}
@Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate,final LoanApplicationTerms loanApplicationTerms){
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null;
if(firstRepaymentPeriodDate !=null && firstRepaymentPeriodDate.isAfter(lastRepaymentDate)){
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
}else{
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, lastRepaymentDate);
}
return dueRepaymentPeriodDate;
}
@Override
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,final LoanApplicationTerms loanApplicationTerms,final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays){
LocalDate adjustedDate = dueRepaymentPeriodDate;
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, adjustedDate);
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
workingDays);
if (isHolidayEnabled) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidays);
}
return adjustedDate;
}
private LocalDate getRepaymentPeriodDate(final LoanApplicationTerms loanApplicationTerms, final LocalDate startDate) {
final int repaidEvery = loanApplicationTerms.getRepaymentEvery();
LocalDate dueRepaymentPeriodDate = startDate;
@@ -77,9 +82,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
final int repaidEvery, final List<LocalDate> scheduledDates) {
final LocalDate firstRepaymentDate = scheduledDates.get(0);
final int repaidEvery, final LocalDate firstRepaymentDate) {
LocalDate idealDisbursementDate = null;
@@ -38,4 +38,16 @@ public class FinanicalFunctions {
}
return payment;
}
public static int nop(final double interestRateFraction, final double emiAmount, final double principal,
final double futureValue, final boolean type){
double numberOfPayments = 0;
if (interestRateFraction == 0) {
numberOfPayments = ((Double)(-1 * (futureValue + principal) / emiAmount)).intValue();
}else{
final double r1 = interestRateFraction + 1;
numberOfPayments = (futureValue + principal * Math.pow(r1, emiAmount)) * interestRateFraction
/ ((type ? r1 : 1) * (1 - Math.pow(r1, emiAmount))); }
return Double.valueOf(numberOfPayments).intValue();
}
}
@@ -7,13 +7,17 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
@@ -36,7 +40,7 @@ public final class LoanApplicationTerms {
private final BigDecimal annualNominalInterestRate;
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
private final Money principal;
private Money principal;
private final LocalDate expectedDisbursementDate;
private final LocalDate repaymentsStartingFromDate;
private final LocalDate calculatedRepaymentsStartingFromDate;
@@ -85,6 +89,16 @@ public final class LoanApplicationTerms {
// added
private LocalDate loanEndDate;
private List<DisbursementData> disbursementDatas;
private boolean multiDisburseLoan;
private BigDecimal fixedEmiAmount;
private BigDecimal maxOutstandingBalance;
private List<LoanTermVariationsData> emiAmountVariations;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -95,19 +109,24 @@ public final class LoanApplicationTerms {
final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment,
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate,
final Money inArrearsTolerance) {
final Money inArrearsTolerance,final boolean multiDisburseLoan,final BigDecimal emiAmount,
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
final List<LoanTermVariationsData> emiAmountVariations) {
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType,
annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate,
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance);
graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas,
maxOutstandingBalance,emiAmountVariations);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate,
final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate,
final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail) {
final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail,
final boolean multiDisburseLoan,final BigDecimal emiAmount,final List<DisbursementData> disbursementDatas,
final BigDecimal maxOutstandingBalance,final List<LoanTermVariationsData> emiAmountVariations) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -131,7 +150,8 @@ public final class LoanApplicationTerms {
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance);
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan,
emiAmount, disbursementDatas, maxOutstandingBalance,emiAmountVariations);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -142,7 +162,9 @@ public final class LoanApplicationTerms {
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Money principal,
final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
final LocalDate calculatedRepaymentsStartingFromDate, final Integer principalGrace, final Integer interestPaymentGrace,
final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance) {
final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance,
final boolean multiDisburseLoan,final BigDecimal emiAmount,final List<DisbursementData> disbursementDatas,
final BigDecimal maxOutstandingBalance,final List<LoanTermVariationsData> emiAmountVariations) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -168,6 +190,11 @@ public final class LoanApplicationTerms {
this.interestChargedFromDate = interestChargedFromDate;
this.inArrearsTolerance = inArrearsTolerance;
this.multiDisburseLoan = multiDisburseLoan;
this.fixedEmiAmount = emiAmount;
this.disbursementDatas = disbursementDatas;
this.maxOutstandingBalance = maxOutstandingBalance;
this.emiAmountVariations = emiAmountVariations;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -180,6 +207,12 @@ public final class LoanApplicationTerms {
// paid too much principal, subtract amount that overpays from
// principal paid for period.
adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs());
} else if(multiDisburseLoan){
final Money difference = this.principal.minus(totalCumulativePrincipalToDate);
final Money halfPrincipalVal = principalForPeriod.dividedBy(2, RoundingMode.HALF_DOWN);
if(difference.isLessThan(halfPrincipalVal)){
adjusted = principalForPeriod.plus(difference.abs());
}
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
final Money difference = totalCumulativePrincipalToDate.minus(this.principal);
@@ -269,10 +302,8 @@ public final class LoanApplicationTerms {
// equal installments
final int periodsElapsed = periodNumber - 1;
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc);
final Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(
periodicInterestRateForRepaymentPeriod, outstandingBalance, periodsElapsed);
Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(
periodicInterestRateForRepaymentPeriod, outstandingBalance, periodsElapsed);
principalForInstallment = calculatePrincipalDueForInstallment(calculator, mc, periodNumber,
totalPmtForThisInstallment, daysInPeriod, outstandingBalance);
break;
@@ -605,6 +636,10 @@ public final class LoanApplicationTerms {
}
private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
if(multiDisburseLoan && fixedEmiAmount != null){
return fixedEmiAmount.doubleValue();
}
final double futureValue = 0;
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
@@ -751,6 +786,10 @@ public final class LoanApplicationTerms {
return dateValue;
}
public void setPrincipal(Money principal){
this.principal = principal;
}
public LocalDate getInterestChargedFromLocalDate() {
return this.interestChargedFromDate;
}
@@ -786,4 +825,35 @@ public final class LoanApplicationTerms {
public Money getPrincipal() {
return this.principal;
}
public List<DisbursementData> getDisbursementDatas() {
return this.disbursementDatas;
}
public boolean isMultiDisburseLoan() {
return this.multiDisburseLoan;
}
public Money getMaxOutstandingBalance() {
return Money.of(getCurrency(), this.maxOutstandingBalance);
}
public BigDecimal getFixedEmiAmount() {
return this.fixedEmiAmount;
}
public void setFixedEmiAmountForPeriod(LocalDate periodDate){
LocalDate startDate = expectedDisbursementDate;
for(LoanTermVariationsData loanVariationTermsData: this.emiAmountVariations){
if(!periodDate.isBefore(loanVariationTermsData.getTermApplicableFrom())
&& !startDate.isAfter(loanVariationTermsData.getTermApplicableFrom())){
this.fixedEmiAmount = loanVariationTermsData.getTermValue();
startDate = loanVariationTermsData.getTermApplicableFrom();
}
}
}
}
@@ -18,7 +18,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePer
public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod {
@SuppressWarnings("unused")
private final int periodNumber;
private final Integer periodNumber;
private final LocalDate disbursementDate;
private final Money principalDisbursed;
private final BigDecimal chargesDueAtTimeOfDisbursement;
@@ -27,16 +27,24 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
final BigDecimal chargesDueAtTimeOfDisbursement) {
final int periodNumber = 0;
return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms, chargesDueAtTimeOfDisbursement);
return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms.getExpectedDisbursementDate(),
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
}
public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate,final Money principalDisbursed,
final BigDecimal chargesDueAtTimeOfDisbursement){
return new LoanScheduleModelDisbursementPeriod(null, disbursementDate,principalDisbursed, chargesDueAtTimeOfDisbursement);
}
private LoanScheduleModelDisbursementPeriod(final int periodNumber, final LoanApplicationTerms loanApplicationTerms,
private LoanScheduleModelDisbursementPeriod(final Integer periodNumber,final LocalDate disbursementDate,final Money principalDisbursed,
final BigDecimal chargesDueAtTimeOfDisbursement) {
this.periodNumber = periodNumber;
this.disbursementDate = loanApplicationTerms.getExpectedDisbursementDate();
this.principalDisbursed = loanApplicationTerms.getPrincipal();
this.disbursementDate = disbursementDate;
this.principalDisbursed = principalDisbursed;
this.chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement;
}
@Override
public LoanSchedulePeriodData toData() {
@@ -83,4 +91,9 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
public BigDecimal penaltyChargesDue() {
return null;
}
@Override
public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) {
return;
}
}
@@ -29,4 +29,6 @@ public interface LoanScheduleModelPeriod {
BigDecimal feeChargesDue();
BigDecimal penaltyChargesDue();
void addLoanCharges(BigDecimal feeCharge,BigDecimal penaltyCharge);
}
@@ -23,9 +23,9 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
private final Money principalDue;
private final Money outstandingLoanBalance;
private final Money interestDue;
private final Money feeChargesDue;
private final Money penaltyChargesDue;
private final Money totalDue;
private Money feeChargesDue;
private Money penaltyChargesDue;
private Money totalDue;
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
@@ -34,7 +34,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
interestDue, feeChargesDue, penaltyChargesDue, totalDue);
}
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
final Money penaltyChargesDue, final Money totalDue) {
@@ -115,4 +115,11 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
return value;
}
@Override
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
this.feeChargesDue = this.feeChargesDue.plus(feeCharge);
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge);
this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge);
}
}
@@ -14,9 +14,14 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public interface ScheduledDateGenerator {
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays,
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays,
final WorkingDays workingDays);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
List<LocalDate> scheduledDates);
final LocalDate firstRepaymentDate);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled,
List<Holiday> holidays, WorkingDays workingDays);
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementDisbursementDateException extends AbstractPlatformDomainRuleException {
public MultiDisbursementDisbursementDateException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loanschedule.out.of.schedule.dusbursement.date", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementEmiAmountException extends AbstractPlatformDomainRuleException {
public MultiDisbursementEmiAmountException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loanschedule.emi.amount.must.be.greter.than.interest", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementOutstandingAmoutException extends AbstractPlatformDomainRuleException {
public MultiDisbursementOutstandingAmoutException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loanschedule.exceeding.max.outstanding.balance", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -8,9 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
@@ -34,7 +37,12 @@ import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariations;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
@@ -50,10 +58,13 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Service
public class LoanScheduleAssembler {
@@ -188,13 +199,54 @@ public class LoanScheduleAssembler {
// other
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance);
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney,loanProduct.isMultiDisburseLoan(),
emiAmount,disbursementDatas, maxOutstandingBalance,loanVariationTermsData);
}
private List<DisbursementData> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
List<DisbursementData> disbursementDatas = new ArrayList<DisbursementData>();
if (command.has(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
LocalDate expectedDisbursementDate = null;
BigDecimal principal = null;
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject,dateFormat,locale);
}
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal,null));
i++;
} while (i < disbursementDataArray.size());
}
}
return disbursementDatas;
}
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
if (repaymentsStartingFromDate != null
@@ -22,6 +22,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -42,7 +43,8 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId"));
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId",LoanApiConstants.disbursementDataParameterName,
LoanApiConstants.emiAmountParameterName,LoanApiConstants.maxOutstandingBalanceParameterName));
private final FromJsonHelper fromApiJsonHelper;
@@ -23,7 +23,9 @@ import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.springframework.beans.factory.annotation.Autowired;
@@ -51,7 +53,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
"transactionProcessingStrategyId", // settings
"calendarId", // optional
"syncDisbursementWithMeeting",// optional
"linkAccountId"));
"linkAccountId",LoanApiConstants.disbursementDataParameterName,LoanApiConstants.emiAmountParameterName,
LoanApiConstants.maxOutstandingBalanceParameterName));
private final FromJsonHelper fromApiJsonHelper;
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
@@ -331,6 +334,16 @@ public final class LoanApplicationCommandFromApiJsonHelper {
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
}
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)){
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
}
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)){
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance).ignoreIfNull().positiveAmount();
}
validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@@ -404,17 +417,18 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
final String principalParameterName = "principal";
BigDecimal principal = null;
if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount();
}
final String inArrearsToleranceParameterName = "inArrearsTolerance";
if (this.fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(principal).ignoreIfNull().positiveAmount();
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull().positiveAmount();
}
final String loanTermFrequencyParameterName = "loanTermFrequency";
@@ -490,6 +504,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
final String expectedDisbursementDateParameterName = "expectedDisbursementDate";
LocalDate expectedDisbursementDate = null;
if (this.fromApiJsonHelper.parameterExists(expectedDisbursementDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
@@ -497,7 +512,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
element);
baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDateStr).notBlank();
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName,
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName,
element);
baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull();
}
@@ -650,6 +665,19 @@ public final class LoanApplicationCommandFromApiJsonHelper {
final Object forceError = null;
baseDataValidator.reset().anyOfNotNull(forceError);
}
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)){
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
}
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)){
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance).ignoreIfNull().positiveAmount();
}
validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
@@ -719,5 +747,57 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
private void validateLoanMultiDisbursementdate(final JsonElement element, final DataValidatorBuilder baseDataValidator,LocalDate expectedDisbursement,
BigDecimal totalPrincipal){
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement !=null
&& totalPrincipal !=null) {
BigDecimal tatalDisbursement = BigDecimal.ZERO;
boolean isFirstinstallmentOnExpectedDisbursementDate = false;
final JsonArray variationArray= this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, element);
List<LocalDate> expectedDisbursementDates = new ArrayList<LocalDate>();
if (variationArray != null && variationArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(
LoanApiConstants.disbursementPrincipalParameterName, jsonObject,locale);;
LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
jsonObject,dateFormat,locale);
if(expectedDisbursementDates.contains(expectedDisbursementDate)){
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).failWithCode(LoanApiConstants.DISBURSEMENT_DATE_UNIQUE_ERROR);
}
expectedDisbursementDates.add(expectedDisbursementDate);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName).parameterAtIndexArray(LoanApiConstants.disbursementPrincipalParameterName, i).value(principal).notBlank();
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName).parameterAtIndexArray(LoanApiConstants.disbursementDateParameterName, i).value(expectedDisbursementDate).notNull();
if(principal != null){
tatalDisbursement = tatalDisbursement.add(principal);
}
if(expectedDisbursement.equals(expectedDisbursementDate)){
isFirstinstallmentOnExpectedDisbursementDate = true;
}
i++;
} while (i < variationArray.size());
if(!isFirstinstallmentOnExpectedDisbursementDate){
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR);
}
if(!tatalDisbursement.equals(totalPrincipal)){
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName).failWithCode(LoanApiConstants.PRINCIPAL_AMOUNT_SHOULD_BE_SAME);
}
final String interestTypeParameterName = "interestType";
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element);
baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).ignoreIfNull().integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
}
}
}
}
@@ -25,6 +25,8 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -51,7 +53,8 @@ public final class LoanEventApiJsonValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("actualDisbursementDate", "externalId", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber"));
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber",
LoanApiConstants.principalDisbursedParameterName,LoanApiConstants.emiAmountParameterName));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
@@ -65,6 +68,12 @@ public final class LoanEventApiJsonValidator {
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalDisbursedParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.principalDisbursedParameterName).value(principal).ignoreIfNull().positiveAmount();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount().notGreaterThanMax(principal);
validatePaymentDetails(baseDataValidator, element);
@@ -340,5 +349,29 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter("installmentNumber").value(installmentNumber).ignoreIfNull().integerGreaterThanZero();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdateDisbursementDate(final String json,LoanDisbursementDetails loanDisbursementDetails) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList(LoanApiConstants.disbursementDateParameterName,"locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.update.disbursement");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).value(actualDisbursementDate).notNull();
if(loanDisbursementDetails.actualDisbursementDate() != null){
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName).failWithCode(LoanApiConstants.ALREADY_DISBURSED);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
@@ -5,10 +5,15 @@
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper;
@@ -39,16 +44,20 @@ import org.mifosplatform.portfolio.group.domain.GroupRepository;
import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.group.exception.GroupNotFoundException;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
@@ -63,7 +72,9 @@ import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Service
public class LoanAssembler {
@@ -178,13 +189,33 @@ public class LoanAssembler {
final String loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
Set<LoanDisbursementDetails> disbursementDetails = null;
BigDecimal fixedEmiAmount = null;
BigDecimal maxOutstandingLoanBalance = null;
if(loanProduct.isMultiDisburseLoan()){
disbursementDetails = fetchDisbursementData(element.getAsJsonObject());
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.emiAmountParameterName, element, locale);
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element, locale);
if(disbursementDetails.isEmpty()){
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if(disbursementDetails.size() > loanProduct.maxTrancheCount()){
final String errorMessage = "Number of tranche shouldn't be greter than "+loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(),disbursementDetails.size());
}
}
if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
if (client.isNotActive()) { throw new ClientNotActiveException(clientId); }
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral);
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
fixedEmiAmount, disbursementDetails,maxOutstandingLoanBalance);
}
if (groupId != null) {
@@ -193,7 +224,8 @@ public class LoanAssembler {
if (group.isNotActive()) { throw new GroupNotActiveException(groupId); }
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting);
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance);
}
if (client != null && group != null) {
@@ -202,9 +234,9 @@ public class LoanAssembler {
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
syncDisbursementWithMeeting);
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,maxOutstandingLoanBalance);
}
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
@@ -212,6 +244,12 @@ public class LoanAssembler {
loanApplication.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
if(loanProduct.isMultiDisburseLoan()){
for(final LoanDisbursementDetails loanDisbursementDetails:loanApplication.getDisbursementDetails()){
loanDisbursementDetails.updateLoan(loanApplication);
}
}
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
@@ -227,6 +265,40 @@ public class LoanAssembler {
return loanApplication;
}
private Set<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
Set<LoanDisbursementDetails> disbursementDatas = new HashSet<LoanDisbursementDetails>();
if (command.has(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
Date expectedDisbursementDate = null;
BigDecimal principal = null;
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)) {
LocalDate date = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject,dateFormat,locale);
if(date!=null){
expectedDisbursementDate = date.toDate();
}
}
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, null, principal));
i++;
} while (i < disbursementDataArray.size());
}
}
return disbursementDatas;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
@@ -11,7 +11,9 @@ import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
@@ -21,7 +23,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveOne(Long loanId);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData, Collection<DisbursementData> disbursementData);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
@@ -65,4 +67,10 @@ public interface LoanReadPlatformService {
Integer retriveLoanCounter(Long groupId, Integer loanType, Long productId);
Integer retriveLoanCounter(Long clientId, Long productId);
Collection<DisbursementData> retrieveLoanDisbursementDetails(Long loanId);
DisbursementData retrieveLoanDisbursementDetail(Long loanId,Long disbursementId);
Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId,Integer termType);
}
@@ -54,11 +54,13 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanApplicationTimelineData;
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
@@ -155,13 +157,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData) {
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) {
try {
this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
repaymentScheduleRelatedLoanData);
repaymentScheduleRelatedLoanData, disbursementData);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId });
@@ -340,7 +342,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
possibleNextRepaymentAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null);
possibleNextRepaymentAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null,null);
}
@Override
@@ -364,7 +366,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal amount = waiveOfInterest.getAmount(currency).getAmount();
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, waiveOfInterest.getTransactionDate(), amount,
null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null);
}
@Override
@@ -374,7 +376,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
return new LoanTransactionData(null, null, null, transactionType, null, null, DateUtils.getLocalDateOfTenant(), null, null, null,
null, null, null, null, null);
null, null, null, null, null, null);
}
@Override
@@ -384,8 +386,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.DISBURSEMENT);
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDate(), null, null,
null, null, null, null, paymentOptions, null, null);
return new LoanTransactionData(null, null, null, transactionType, null, null, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(),
null,null, null, null, null, paymentOptions, null, null,loan.retriveLastEmiAmount());
}
@Override
@@ -417,6 +419,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return "l.id as id, l.account_no as accountNo, l.external_id as externalId, l.fund_id as fundId, f.name as fundName,"
+ " l.loan_type_enum as loanType, l.loanpurpose_cv_id as loanPurposeId, cv.code_value as loanPurposeName,"
+ " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription,"
+ " lp.multi_disburse_loan as multiDisburseLoan,"
+ " c.id as clientId, c.display_name as clientName, c.office_id as clientOfficeId,"
+ " g.id as groupId, g.display_name as groupName,"
+ " g.office_id as groupOfficeId, g.staff_id As groupStaffId , g.parent_id as groupParentId, "
@@ -429,7 +432,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate, dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,"
+ " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, "
+ " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.expected_maturedon_date as expectedMaturityDate, "
+ " l.principal_amount as principal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
+ " l.principal_amount as principal,l.approved_principal as approvedPrincipal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
+ " l.grace_on_principal_periods as graceOnPrincipalPayment, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,"
+ " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
+ " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, "
@@ -467,6 +470,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.total_writtenoff_derived as totalWrittenOff,"
+ " l.total_outstanding_derived as totalOutstanding,"
+ " l.total_overpaid_derived as totalOverpaid,"
+ " l.fixed_emi_amount as fixedEmiAmount,"
+ " l.max_outstanding_loan_balance as outstandingLoanBalance,"
+ " la.principal_overdue_derived as principalOverdue,"
+ " la.interest_overdue_derived as interestOverdue,"
+ " la.fee_charges_overdue_derived as feeChargesOverdue,"
@@ -536,6 +541,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long loanProductId = JdbcSupport.getLong(rs, "loanProductId");
final String loanProductName = rs.getString("loanProductName");
final String loanProductDescription = rs.getString("loanProductDescription");
final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
@@ -580,6 +587,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname);
final BigDecimal principal = rs.getBigDecimal("principal");
final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal");
final BigDecimal totalOverpaid = rs.getBigDecimal("totalOverpaid");
final BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance");
@@ -687,15 +695,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Integer loanCounter = JdbcSupport.getInteger(rs, "loanCounter");
final Integer loanProductCounter = JdbcSupport.getInteger(rs, "loanProductCounter");
final BigDecimal fixedEmiAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "fixedEmiAmount");
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData,
loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName,
loanOfficerId, loanOfficerName, currencyData, principal, totalOverpaid, inArrearsTolerance, termFrequency,
loanOfficerId, loanOfficerName, currencyData, principal,approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId,
transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary,
feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter);
feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan,
fixedEmiAmount,outstandingLoanBalance);
}
}
@@ -737,15 +747,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final CurrencyData currency;
private final DisbursementData disbursement;
private final BigDecimal totalFeeChargesDueAtDisbursement;
private final Collection<DisbursementData> disbursementData;
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData) {
public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.amount();
this.disbursementData = disbursementData;
}
public String schema() {
@@ -767,11 +779,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.disbursement.isDisbursed());
final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
periods.add(disbursementPeriod);
final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.code(), this.currency.decimalPlaces(),
this.currency.currencyInMultiplesOf());
final BigDecimal totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
BigDecimal totalPrincipalDisbursed = BigDecimal.ZERO;
if(disbursementData == null || disbursementData.isEmpty()){
periods.add(disbursementPeriod);
totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount();
}else{
this.outstandingLoanPrincipalBalance = BigDecimal.ZERO;
}
Money totalPrincipalExpected = Money.zero(monCurrency);
Money totalPrincipalPaid = Money.zero(monCurrency);
Money totalInterestCharged = Money.zero(monCurrency);
@@ -800,6 +818,28 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
final boolean complete = rs.getBoolean("complete");
if(disbursementData!=null){
BigDecimal principal = BigDecimal.ZERO;
for(DisbursementData data:disbursementData){
if(fromDate.equals(this.disbursement.disbursementDate())
&& data.disbursementDate().equals(fromDate)){
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement,
data.isDisbursed());
periods.add(periodData);
}else if(data.isDueForDisbursement(fromDate, dueDate)){
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO,
data.isDisbursed());
periods.add(periodData);
}
}
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(principal);
totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
}
Integer daysInPeriod = Integer.valueOf(0);
if (fromDate != null) {
@@ -895,6 +935,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
totalWrittenOff.getAmount(), totalRepaymentExpected.getAmount(), totalRepayment.getAmount(),
totalPaidInAdvance.getAmount(), totalPaidLate.getAmount(), totalOutstanding.getAmount());
}
}
private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionData> {
@@ -989,7 +1030,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalAmount,
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, externalId, transfer);
principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, externalId, transfer, null);
}
}
@@ -1179,4 +1220,71 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final String sql = "Select MAX(l.loan_product_counter) from m_loan l where l.client_id = ? and l.product_id=?";
return this.jdbcTemplate.queryForInt(sql, clientId,productId);
}
@Override
public Collection<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper();
final String sql = "select " + rm.schema() + " where dd.loan_id=? order by dd.expected_disburse_date";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
private static final class LoanDisbursementDetailMapper implements RowMapper<DisbursementData> {
public String schema() {
return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate,dd.disbursedon_date as actualDisbursementdate,dd.principal as principal,dd.approved_principal as approvedPrincipal "
+ "from m_loan_disbursement_detail dd";
}
@Override
public DisbursementData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
throws SQLException {
final Long id = rs.getLong("id");
final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate");
final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate");
final BigDecimal principal = rs.getBigDecimal("principal");
final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal");
final DisbursementData disbursementData = new DisbursementData(id,expectedDisbursementdate,actualDisbursementdate,principal,approvedPrincipal);
return disbursementData;
}
}
@Override
public DisbursementData retrieveLoanDisbursementDetail(Long loanId,Long disbursementId) {
final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper();
final String sql = "select " + rm.schema() + " where dd.loan_id=? and dd.id=?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanId,disbursementId });
}
@Override
public Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType) {
final LoanTermVariationsMapper rm = new LoanTermVariationsMapper();
final String sql = "select " + rm.schema() + " where tv.loan_id=? and tv.term_type=?";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId,termType });
}
private static final class LoanTermVariationsMapper implements RowMapper<LoanTermVariationsData> {
public String schema() {
return "tv.id as id,tv.applicable_from as variationApplicableFrom,tv.term_value as termValue "
+ "from m_loan_term_variations tv";
}
@Override
public LoanTermVariationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum)
throws SQLException {
final Long id = rs.getLong("id");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal termValue = rs.getBigDecimal("termValue");
final LoanTermVariationsData loanTermVariationsData = new LoanTermVariationsData(id,LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT),
variationApplicableFrom,termValue);
return loanTermVariationsData;
}
}
}
@@ -76,4 +76,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult undoWriteOff(Long loanId);
CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command);
}
@@ -47,6 +47,8 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWr
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.data.AccountTransferDTO;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.domain.AccountTransfer;
import org.mifosplatform.portfolio.account.domain.AccountTransferRepository;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersWritePlatformService;
@@ -85,6 +87,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
@@ -151,6 +154,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanChargeReadPlatformService LoanChargeReadPlatformService;
private final LoanReadPlatformService loanReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final AccountTransferRepository accountTransferRepository;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -169,7 +173,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
final FromJsonHelper fromApiJsonHelper) {
final FromJsonHelper fromApiJsonHelper,final AccountTransferRepository accountTransferRepository) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -195,6 +199,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.LoanChargeReadPlatformService = loanChargeReadPlatformService;
this.loanReadPlatformService = loanReadPlatformService;
this.fromApiJsonHelper = fromApiJsonHelper;
this.accountTransferRepository = accountTransferRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -247,12 +252,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
updateLoanCounters(loan, actualDisbursementDate);
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, changes, paymentDetail, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency,
existingTransactionIds, existingReversedTransactionIds, changes, paymentDetail, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays,workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
if (!changes.isEmpty()) {
try {
try{
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
@@ -265,11 +270,35 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
"validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); }
}
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
//fix me
//adding loan installment charges along with the actual disbursement, is trying to add 2 extra entries with null values. which is causing issues
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionForDisbursement();
try{
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); }
}
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
@@ -277,14 +306,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Set<LoanCharge> loanCharges = loan.charges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<Long, BigDecimal>();
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
} else if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()
&& loanCharge.isNotFullyPaid() && !loanCharge.isWaived()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding());
}
}
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
@@ -398,19 +425,38 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
//fix me
//adding loan installment charges along with the actual disbursement, is trying to add 2 extra entries with null values. which is causing issues
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionForDisbursement();
try{
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); }
}
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
final Set<LoanCharge> loanCharges = loan.charges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<Long, BigDecimal>();
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
} else if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()
&& loanCharge.isNotFullyPaid() && !loanCharge.isWaived()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding());
}
}
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
@@ -737,12 +783,30 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction writeoff = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, currentUser);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
updateLoanCounters(loan, loan.getDisbursementDate());
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
this.loanTransactionRepository.save(writeoff);
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
@@ -782,12 +846,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction possibleClosingTransaction = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
updateLoanCounters(loan, loan.getDisbursementDate());
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds,this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
if (possibleClosingTransaction != null) {
this.loanTransactionRepository.save(possibleClosingTransaction);
}
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
this.loanRepository.save(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -1747,4 +1831,57 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withLoanId(loanId) //
.build();
}
@Override
@Transactional
public CommandProcessingResult updateDisbursementDateForTranche(final Long loanId,final Long disbursementId, final JsonCommand command){
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(),loanDisbursementDetails);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails,command, existingTransactionIds, existingReversedTransactionIds,
changes,this.loanScheduleFactory,applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
this.loanRepository.saveAndFlush(loan);
if (changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes)
.build();
}
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransfer transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) {
transferTransaction.updateToLoanTransaction(newLoanTransaction);
this.accountTransferRepository.save(transferTransaction);
}
}
}
@@ -50,4 +50,9 @@ public interface LoanProductConstants {
public static final String VALUE_CONDITION_END_WITH_ERROR = "condition.type.must.end.with.greterthan";
public static final String VALUE_CONDITION_START_WITH_ERROR = "condition.type.must.start.with.equal";
public static final String shortName = "shortName";
public static final String multiDisburseLoanParameterName = "multiDisburseLoan";
public static final String maxTrancheCountParameterName = "maxTrancheCount";
public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance";
}
@@ -106,6 +106,10 @@ public class LoanProductData {
private final Map<String, List<GLAccountData>> accountingMappingOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> valueConditionTypeOptions;
private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount;
private final BigDecimal outstandingLoanBalance;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -148,13 +152,16 @@ public class LoanProductData {
final LocalDate closeDate = null;
final String status = null;
final String externalId = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments,
final Boolean multiDisburseLoan = null;
final Integer maxTrancheCount = null;
final BigDecimal outstandingLoanBalance = null;
return new LoanProductData(id, name,shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations);
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
}
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
@@ -197,6 +204,10 @@ public class LoanProductData {
final Collection<LoanProductBorrowerCycleVariationData> principalVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> interestRateVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations = new ArrayList<LoanProductBorrowerCycleVariationData>(1);
final Boolean multiDisburseLoan = null;
final Integer maxTrancheCount = null;
final BigDecimal outstandingLoanBalance = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
@@ -204,7 +215,7 @@ public class LoanProductData {
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations);
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -253,6 +264,10 @@ public class LoanProductData {
final LocalDate closeDate = null;
final String status = null;
final String externalId = null;
final Boolean multiDisburseLoan = null;
final Integer maxTrancheCount = null;
final BigDecimal outstandingLoanBalance = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
@@ -260,7 +275,7 @@ public class LoanProductData {
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle);
interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -286,7 +301,8 @@ public class LoanProductData {
final Collection<ChargeData> charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle,
boolean useBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId,
Collection<LoanProductBorrowerCycleVariationData> principalVariations, Collection<LoanProductBorrowerCycleVariationData> interestRateVariations,
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations) {
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations, Boolean multiDisburseLoan, Integer maxTrancheCount,
BigDecimal outstandingLoanBalance) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -347,6 +363,9 @@ public class LoanProductData {
this.principalVariationsForBorrowerCycle = principalVariations;
this.interestRateVariationsForBorrowerCycle = interestRateVariations;
this.numberOfRepaymentVariationsForBorrowerCycle = numberOfRepaymentVariations;
this.multiDisburseLoan = multiDisburseLoan;
this.outstandingLoanBalance = outstandingLoanBalance;
this.maxTrancheCount = maxTrancheCount;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -419,6 +438,9 @@ public class LoanProductData {
this.graceOnInterestCharged = productData.graceOnInterestCharged;
this.includeInBorrowerCycle = productData.includeInBorrowerCycle;
this.useBorrowerCycle = productData.useBorrowerCycle;
this.multiDisburseLoan = productData.multiDisburseLoan;
this.maxTrancheCount = productData.maxTrancheCount;
this.outstandingLoanBalance = productData.outstandingLoanBalance;
this.amortizationTypeOptions = amortizationTypeOptions;
this.interestTypeOptions = interestTypeOptions;
@@ -610,4 +632,14 @@ public class LoanProductData {
public Collection<LoanProductBorrowerCycleVariationData> getNumberOfRepaymentVariationsForBorrowerCycle() {
return this.numberOfRepaymentVariationsForBorrowerCycle;
}
public Boolean getMultiDisburseLoan() {
return this.multiDisburseLoan;
}
public BigDecimal getOutstandingLoanBalance() {
return this.outstandingLoanBalance;
}
}
@@ -96,6 +96,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
@Column(name = "use_borrower_cycle")
private boolean useBorrowerCycle;
@Embedded
private LoanProductTrancheDetails loanProducTrancheDetails;
@Column(name = "start_date", nullable = true)
@Temporal(TemporalType.DATE)
@@ -165,12 +168,22 @@ public class LoanProduct extends AbstractPersistable<Long> {
populateBorrowerCyclevariations(command, loanProductBorrowerCycleVariations);
}
final boolean multiDisburseLoan = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.multiDisburseLoanParameterName);
Integer maxTrancheCount = null;
BigDecimal outstandingLoanBalance = null;
if(multiDisburseLoan){
outstandingLoanBalance = command.bigDecimalValueOfParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName);
maxTrancheCount = command.integerValueOfParameterNamed(LoanProductConstants.maxTrancheCountParameterName);
}
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal,
interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate,
interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments,
minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate,
externalId, useBorrowerCycle, loanProductBorrowerCycleVariations);
externalId, useBorrowerCycle, loanProductBorrowerCycleVariations,multiDisburseLoan,maxTrancheCount,outstandingLoanBalance);
}
/**
@@ -379,7 +392,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges,
final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate,
final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle,
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations) {
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations,
final boolean multiDisburseLoan,final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -424,6 +438,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) {
borrowerCycleVariations.updateLoanProduct(this);
}
this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
}
public MonetaryCurrency getCurrency() {
@@ -586,10 +601,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
actualChanges.put(accountingTypeParamName, newValue);
this.externalId = newValue;
}
loanProducTrancheDetails.update(command, actualChanges, localeAsInput);
return actualChanges;
}
public boolean isCashBasedAccountingEnabled() {
return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule);
}
@@ -678,6 +695,18 @@ public class LoanProduct extends AbstractPersistable<Long> {
public boolean useBorrowerCycle() {
return this.useBorrowerCycle;
}
public boolean isMultiDisburseLoan(){
return this.loanProducTrancheDetails.isMultiDisburseLoan();
}
public BigDecimal outstandingLoanBalance() {
return this.loanProducTrancheDetails.outstandingLoanBalance();
}
public Integer maxTrancheCount() {
return this.loanProducTrancheDetails.maxTrancheCount();
}
public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id) {
LoanProductBorrowerCycleVariations borrowerCycleVariation = null;
@@ -38,7 +38,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal principal;
@Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false)
private BigDecimal nominalInterestRatePerPeriod;
@@ -155,6 +155,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return Money.of(this.currency, this.principal);
}
public void setPrincipal(BigDecimal principal){
this.principal = principal;
}
public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance);
}
@@ -0,0 +1,77 @@
package org.mifosplatform.portfolio.loanproduct.domain;
import java.math.BigDecimal;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
@Embeddable
public class LoanProductTrancheDetails {
@Column(name = "multi_disburse_loan")
private boolean multiDisburseLoan;
@Column(name = "max_tranche_count", nullable = false)
private Integer maxTrancheCount;
@Column(name = "outstanding_loan_balance", scale = 6, precision = 19, nullable = false)
private BigDecimal outstandingLoanBalance;
protected LoanProductTrancheDetails() {
// TODO Auto-generated constructor stub
}
public LoanProductTrancheDetails(final boolean multiDisburseLoan, final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance) {
this.multiDisburseLoan = multiDisburseLoan;
this.maxTrancheCount = maxTrancheCount;
this.outstandingLoanBalance = outstandingLoanBalance;
}
public void update(final JsonCommand command, final Map<String, Object> actualChanges, final String localeAsInput) {
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.multiDisburseLoanParameterName, this.multiDisburseLoan)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.multiDisburseLoanParameterName);
actualChanges.put(LoanProductConstants.multiDisburseLoanParameterName, newValue);
this.multiDisburseLoan = newValue;
}
if (this.multiDisburseLoan) {
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.maxTrancheCountParameterName, this.maxTrancheCount)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.maxTrancheCountParameterName);
actualChanges.put(LoanProductConstants.maxTrancheCountParameterName, newValue);
this.maxTrancheCount = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName,
this.outstandingLoanBalance)) {
final BigDecimal newValue = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.outstandingLoanBalanceParameterName);
actualChanges.put(LoanProductConstants.outstandingLoanBalanceParameterName, newValue);
actualChanges.put(LoanProductConstants.outstandingLoanBalanceParameterName, localeAsInput);
this.outstandingLoanBalance = newValue;
}
} else {
this.maxTrancheCount = null;
this.outstandingLoanBalance = null;
}
}
public boolean isMultiDisburseLoan() {
return this.multiDisburseLoan;
}
public BigDecimal outstandingLoanBalance() {
return this.outstandingLoanBalance;
}
public Integer maxTrancheCount() {
return this.maxTrancheCount;
}
}
@@ -24,6 +24,7 @@ import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.springframework.beans.factory.annotation.Autowired;
@@ -56,7 +57,9 @@ public final class LoanProductDataValidator {
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),LoanProductConstants.useBorrowerCycleParameterName,
LoanProductConstants.principalVariationsForBorrowerCycleParameterName,LoanProductConstants.interestRateVariationsForBorrowerCycleParameterName,
LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, LoanProductConstants.shortName));
LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName,LoanProductConstants.shortName,
LoanProductConstants.multiDisburseLoanParameterName,LoanProductConstants.outstandingLoanBalanceParameterName,
LoanProductConstants.maxTrancheCountParameterName));
private final FromJsonHelper fromApiJsonHelper;
@@ -325,11 +328,36 @@ public final class LoanProductDataValidator {
validateBorrowerCycleVariations(element, baseDataValidator);
}
}
validateMultiDisburseLoanData(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateMultiDisburseLoanData(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
Boolean multiDisburseLoan = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.multiDisburseLoanParameterName, element)) {
multiDisburseLoan = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.multiDisburseLoanParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.multiDisburseLoanParameterName).value(multiDisburseLoan).ignoreIfNull()
.validateForBooleanValue();
}
if(multiDisburseLoan){
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.outstandingLoanBalanceParameterName, element)) {
final BigDecimal outstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanProductConstants.outstandingLoanBalanceParameterName,
element);
baseDataValidator.reset().parameter(LoanProductConstants.outstandingLoanBalanceParameterName).value(outstandingLoanBalance).ignoreIfNull()
.zeroOrPositiveAmount();
}
final Integer maxTrancheCount = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.maxTrancheCountParameterName, element,Locale.getDefault());
baseDataValidator.reset().parameter(LoanProductConstants.maxTrancheCountParameterName).value(maxTrancheCount).notNull().integerGreaterThanZero();
final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault());
baseDataValidator.reset().parameter("interestType").value(interestType).ignoreIfNull().integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
}
}
public void validateForUpdate(final String json, final LoanProduct loanProduct) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -580,6 +608,8 @@ public final class LoanProductDataValidator {
validateBorrowerCycleVariations(element, baseDataValidator);
}
}
validateMultiDisburseLoanData(baseDataValidator, element);
@@ -11,6 +11,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
@@ -416,5 +417,25 @@ public class LoanEnumerations {
}
return optionData;
}
public static EnumOptionData loanvariationType(final int id) {
return loanvariationType(LoanTermVariationType.fromInt(id));
}
public static EnumOptionData loanvariationType(final LoanTermVariationType type) {
EnumOptionData optionData = null;
switch (type) {
case EMI_AMOUNT:
optionData = new EnumOptionData(LoanTermVariationType.EMI_AMOUNT.getValue().longValue(), LoanTermVariationType.EMI_AMOUNT.getCode(), "emiAmount");
break;
default:
optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(), LoanTermVariationType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
}
@@ -120,6 +120,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,"
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
+ "lp.multi_disburse_loan as multiDisburseLoan, lp.max_tranche_count as maxTrancheCount, lp.outstanding_loan_balance as outstandingLoanBalance, "
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId "
+ " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id"
@@ -213,6 +214,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
}
}
}
final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
final Integer maxTrancheCount = rs.getInt("maxTrancheCount");
final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -221,7 +227,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
transactionStrategyId, transactionStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate,
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle);
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
}
}