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