diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index b6131856e..96ede4560 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -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 } } @@ -5834,7 +5838,9 @@ Request Body:
@@ -6109,6 +6119,15 @@ Request Body:
actualDisbursementDate
+
+
+
+ Optional Fields
+
+
+ transactionAmount,fixedEmiAmount
+
+
@@ -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
}
}
@@ -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
]
}
@@ -9193,7 +9216,10 @@ GET https://DomainName/api/v1/loanproducts/{productId}
inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle,
useBorrowerCycle,principalVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle,
- interestRateVariationsForBorrowerCycle
+ interestRateVariationsForBorrowerCycle,
+ multiDisburseLoan,maxTrancheCount,
+ outstandingLoanBalance
+
diff --git a/mifosng-db/migrations/core_db/V146__tranche_loans.sql b/mifosng-db/migrations/core_db/V146__tranche_loans.sql
new file mode 100644
index 000000000..d009bce56
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V146__tranche_loans.sql
@@ -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;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
index 82d3f8ff8..d9278f271 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
@@ -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");
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
index 1e1aa0127..bcac311f6 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
@@ -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";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
index 4dc7901bf..4d4c20649 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
@@ -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()) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java
new file mode 100644
index 000000000..97c8e2039
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java
@@ -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";
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java
new file mode 100644
index 000000000..0a1f8b0fb
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanDisbursementDetailApiResource.java
@@ -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 RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "expectedDisbursementDate","actualDisbursementDate",
+ "principal","approvedPrincipal"));
+
+ private final String resourceNameForPermissions = "LOAN";
+
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final PlatformSecurityContext context;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final LoanReadPlatformService loanReadPlatformService;
+
+ @Autowired
+ public LoanDisbursementDetailApiResource(final DefaultToApiJsonSerializer 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);
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
index e98acf008..ae1634a28 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java
@@ -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 notes = null;
PortfolioAccountData linkedAccount = null;
+ Collection disbursementData = null;
+ Collection emiAmountVariations = null;
final Set mandatoryResponseParameters = new HashSet();
final Set 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 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);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java
index 74593e6e8..624e48804 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java
@@ -12,17 +12,25 @@ import org.joda.time.LocalDate;
/**
* Immutable data object representing disbursement information.
*/
-public class DisbursementData {
+public class DisbursementData implements Comparable{
+ @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);
+ }
+
+
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
index 0ac414e20..0802052fb 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java
@@ -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 guarantors;
private final CalendarData meeting;
private final Collection notes;
-
+ private final Collection disbursementDetails;
// template
private final Collection productOptions;
private final Collection loanOfficerOptions;
@@ -136,7 +138,17 @@ public class LoanAccountData {
// linkable account details
private final PortfolioAccountData linkedAccount;
private final Collection accountLinkingOptions;
-
+
+ private final Boolean multiDisburseLoan;
+
+ private final BigDecimal fixedEmiAmount;
+
+ private final BigDecimal maxOutstandingLoanBalance;
+
+ private final Boolean canDisburse;
+
+ private final Collection 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 accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
+ final Collection disbursementData = null;
+ final Boolean multiDisburseLoan = null;
+ final BigDecimal fixedEmiAmount = null;
+ final BigDecimal maxOutstandingLoanBalance = null;
+ final Collection 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 accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
+ final Collection disbursementData = null;
+ final Boolean multiDisburseLoan = null;
+ final BigDecimal fixedEmiAmount = null;
+ final BigDecimal maxOutstandingLoanBalance = null;
+ final Collection 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 accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
+ final Collection disbursementData = null;
+ final Boolean multiDisburseLoan = null;
+ final BigDecimal fixedEmiAmount = null;
+ final BigDecimal maxOutstandingBalance = null;
+ final Collection 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 accountLinkingOptions = null;
+ final Collection disbursementData = null;
+ final Collection 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 transactions = null;
@@ -653,10 +691,12 @@ public class LoanAccountData {
final Collection calendarOptions = null;
final Collection accountLinkingOptions = null;
final PortfolioAccountData linkedAccount = null;
-
+ final Collection disbursementData= null;
+ final Collection 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 loanOfficerOptions, final Collection loanPurposeOptions,
final Collection loanCollateralOptions, final Collection calendarOptions,
final Collection notes, final Collection accountLinkingOptions,
- final PortfolioAccountData linkedAccount) {
+ final PortfolioAccountData linkedAccount,final Collection disbursementDetails,
+ final Collection 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 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 loanCollateralOptions, final Collection calendarOptions,
final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter,
final Collection notes, final Collection accountLinkingOptions,
- final PortfolioAccountData linkedAccount) {
+ final PortfolioAccountData linkedAccount,final Collection disbursementDetails,final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount,
+ final BigDecimal maxOutstandingLoanBalance,final Collection 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);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java
index 15d922a38..07d235e9f 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanApplicationTimelineData.java
@@ -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);
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java
new file mode 100644
index 000000000..7d9f20bbc
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java
@@ -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;
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java
index 657815a5c..481b140d4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionData.java
@@ -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 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 paymentTypeOptions, final String externalId, final AccountTransferData transfer) {
+ final Collection 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() {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
index e1df92867..69cb3b5bf 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
@@ -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);
}
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
index 9c3acaa03..c54db3e5d 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
@@ -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 {
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 disbursementDetails = new HashSet();
+
+ @OrderBy(value = "termApplicableFrom, id")
+ @LazyCollection(LazyCollectionOption.FALSE)
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
+ private final Set loanTermVariations = new HashSet();
+
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 loanCharges,
- final Set collateral) {
+ final Set collateral, final BigDecimal fixedEmiAmount, final Set 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 loanCharges,
- final Boolean syncDisbursementWithMeeting) {
+ final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
+ final Set disbursementDetails,final BigDecimal maxOutstandingLoanBalance) {
final LoanStatus status = null;
final CodeValue loanPurpose = null;
final Set 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 loanCharges,
- final Boolean syncDisbursementWithMeeting) {
+ final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
+ final Set disbursementDetails,final BigDecimal maxOutstandingLoanBalance) {
final LoanStatus status = null;
final CodeValue loanPurpose = null;
final Set 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 {
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 loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting) {
+ final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting,
+ final BigDecimal fixedEmiAmount, final Set disbursementDetails,
+ final BigDecimal maxOutstandingLoanBalance) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
@@ -360,6 +398,10 @@ public class Loan extends AbstractPersistable {
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 {
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 actualChanges) {
+
+ List 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 fetchDisbursementIds() {
+ List list = new ArrayList();
+ for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
+ list.add(disbursementDetails.getId());
+ }
+ return list;
+ }
+
private CollateralData[] listOfLoanCollateralData(final Set setOfLoanCollateral) {
CollateralData[] existingLoanCollateral = null;
@@ -1512,18 +1685,32 @@ public class Loan extends AbstractPersistable {
return ids;
}
- public Map disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
+ public void disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
final JsonCommand command, final ApplicationCurrency currency, final List existingTransactionIds,
final List existingReversedTransactionIds, final Map actualChanges, final PaymentDetail paymentDetail,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List 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 {
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 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 {
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 {
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 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+ allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+ for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+ mapEntry.getValue().updateLoan(this);
+ }
+
+ }
+ updateLoanSummaryDerivedFields();
+ }
+
+
+ return changedTransactionDetail;
+ }
+
+ private Collection fetchUndisbursedDetail(){
+ Collection disbursementDetails = new ArrayList();
+ 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 details = new HashSet(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 {
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 = new ArrayList();
+ for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
+ disbursementData.add(disbursementDetails.toData());
+ }
+ final List loanVariationTermsData = new ArrayList();
+ 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 {
}
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 {
* "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 {
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 {
reverseExistingTransactions();
- updateLoanToPreDisbursalState();
}
return actualChanges;
@@ -1749,6 +2111,7 @@ public class Loan extends AbstractPersistable {
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 {
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 {
public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, final List 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 {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List 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 {
return this.loanRepaymentScheduleDetail.getCurrency();
}
- public LoanTransaction closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
+ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds,
- final AppUser currentUser) {
+ final AppUser currentUser,final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
+ final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List 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 {
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 {
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 changes, final List existingTransactionIds, final List existingReversedTransactionIds) {
+ private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
+ final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+ allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+ for (final Map.Entry 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 changes, final List existingTransactionIds, final List existingReversedTransactionIds,
+ final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
+ final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays,
+ final WorkingDays workingDays) {
+
validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -2291,6 +2711,10 @@ public class Loan extends AbstractPersistable {
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 {
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 {
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 {
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 {
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 {
}
return disbursementDate;
}
+
+ public LocalDate getExpectedDisbursedOnLocalDateForTemplate(){
+
+ LocalDate expectedDisbursementDate = null;
+ if (this.expectedDisbursementDate != null) {
+ expectedDisbursementDate = new LocalDate(this.expectedDisbursementDate);
+ }
+
+ Collection 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 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 {
}
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 {
}
public Integer getLoanProductLoanCounter() {
+ if(this.loanProductCounter == null){
+ return 0;
+ }
return this.loanProductCounter;
}
@@ -3115,7 +3585,8 @@ public class Loan extends AbstractPersistable {
}
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 {
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 {
}
return list;
}
+
+ public Set getDisbursementDetails() {
+ return this.disbursementDetails;
+ }
+
+ public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails,final JsonCommand command,final List existingTransactionIds,
+ final List existingReversedTransactionIds, final Map actualChanges,
+ final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
+ final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List 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 allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
+ ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
+ allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
+ for (final Map.Entry 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;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
index 4b847b9e3..37d85bc61 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java
@@ -662,15 +662,14 @@ public class LoanCharge extends AbstractPersistable {
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 {
public boolean hasNoLoanInstallmentCharges(){
return this.loanInstallmentCharge.isEmpty();
}
+
+ public Set installmentCharges(){
+ return this.loanInstallmentCharge;
+ }
public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(final Money trasferedAmount){
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java
new file mode 100644
index 000000000..dc5b3324f
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java
@@ -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 {
+
+ @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;
+ }
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
index dc25aea65..185acf836 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanEvent.java
@@ -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;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
index 92e6fac95..10ff41c21 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
@@ -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));
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java
index 81e7af512..9c2c7fe43 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java
@@ -279,4 +279,9 @@ public final class LoanSummary {
public BigDecimal getTotalPrincipalDisbursed() {
return this.totalPrincipalDisbursed;
}
+
+
+ public BigDecimal getTotalPrincipalRepaid() {
+ return this.totalPrincipalRepaid;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java
new file mode 100644
index 000000000..2442e3dff
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java
@@ -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());
+ }
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariations.java
new file mode 100644
index 000000000..3701ea1f5
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariations.java
@@ -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 {
+
+ @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;
+ }
+
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
index ef4e44356..0d91e5232 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java
@@ -441,7 +441,7 @@ public final class LoanTransaction extends AbstractPersistable {
}
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 toMapData(final CurrencyData currencyData) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/ExceedingTrancheCountException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/ExceedingTrancheCountException.java
new file mode 100644
index 000000000..20ca80d3d
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/ExceedingTrancheCountException.java
@@ -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);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/LoanDisbursalException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/LoanDisbursalException.java
index 4f7d6fd25..d476dc52c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/LoanDisbursalException.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/LoanDisbursalException.java
@@ -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);
+ }
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.java
new file mode 100644
index 000000000..5d37381c0
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/exception/MultiDisbursementDataRequiredException.java
@@ -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);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java
new file mode 100644
index 000000000..767de6383
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/UpdateLoanDisbuseDateCommandHandler.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
index 27ee6fb82..259099a86 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java
@@ -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);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index 15c3be22e..181fee98e 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -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 holidays, final WorkingDays workingDays) {
// 1. generate list of proposed schedule due dates
- final List 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 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 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 createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
- final Collection periods = new ArrayList(numberOfRepayments + 1);
-
- final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
- loanApplicationTerms, chargesDueAtTimeOfDisbursement);
- periods.add(disbursementPeriod);
+ Collection periods = null ;
+ if(loanApplicationTerms.isMultiDisburseLoan()){
+ periods = new ArrayList(numberOfRepayments + loanApplicationTerms.getDisbursementDatas().size());
+ }else{
+ periods = new ArrayList(numberOfRepayments + 1);
+ final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+ loanApplicationTerms, chargesDueAtTimeOfDisbursement);
+ periods.add(disbursementPeriod);
+ }
return periods;
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 80e183142..aa233e515 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -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 generate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
+ public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
final List holidays, final WorkingDays workingDays) {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
- final List dueRepaymentPeriodDates = new ArrayList(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 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 scheduledDates) {
-
- final LocalDate firstRepaymentDate = scheduledDates.get(0);
+ final int repaidEvery, final LocalDate firstRepaymentDate) {
LocalDate idealDisbursementDate = null;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java
index 454d54129..9c78be4a4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FinanicalFunctions.java
@@ -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();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index e954102a0..49e2063ef 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -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 disbursementDatas;
+
+ private boolean multiDisburseLoan;
+
+ private BigDecimal fixedEmiAmount;
+
+ private BigDecimal maxOutstandingBalance;
+
+ private List 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 disbursementDatas, final BigDecimal maxOutstandingBalance,
+ final List 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 disbursementDatas,
+ final BigDecimal maxOutstandingBalance,final List 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 disbursementDatas,
+ final BigDecimal maxOutstandingBalance,final List 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 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();
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
index 61429b3ac..dbcab18b3 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
index 5d7780033..45d42cd57 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
@@ -29,4 +29,6 @@ public interface LoanScheduleModelPeriod {
BigDecimal feeChargesDue();
BigDecimal penaltyChargesDue();
+
+ void addLoanCharges(BigDecimal feeCharge,BigDecimal penaltyCharge);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
index b649da90f..9c8b1966c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
@@ -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);
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
index 4c6e4a76c..1a8010cc0 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -14,9 +14,14 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public interface ScheduledDateGenerator {
- List generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays,
+ LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays,
final WorkingDays workingDays);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
- List scheduledDates);
+ final LocalDate firstRepaymentDate);
+
+ LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms);
+
+ LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled,
+ List holidays, WorkingDays workingDays);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.java
new file mode 100644
index 000000000..98d6dee18
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementDisbursementDateException.java
@@ -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);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.java
new file mode 100644
index 000000000..946ab2dde
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementEmiAmountException.java
@@ -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);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.java
new file mode 100644
index 000000000..db8b2a7c7
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/exception/MultiDisbursementOutstandingAmoutException.java
@@ -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);
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index 5fb77f88c..e4464d319 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -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 disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
+
+ final List loanVariationTermsData = new ArrayList();
+ 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 fetchDisbursementData(final JsonObject command) {
+ final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
+ final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
+ List disbursementDatas = new ArrayList();
+ 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
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
index 1880ab1e3..5fa159c33 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
@@ -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;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
index 3fc5de7ae..b53561b36 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -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 expectedDisbursementDates = new ArrayList();
+ 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());
+ }
+
+ }
+
+
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
index a1131200f..ea9d93bc4 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java
@@ -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 disbursementParameters = new HashSet(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