Merge branch 'develop' of git://github.com/pramodn02/mifosx into pramodn02-develop

Conflicts:
	mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java
This commit is contained in:
Vishwas Babu A J
2014-11-13 20:25:48 +05:30
28 changed files with 640 additions and 179 deletions
@@ -202,9 +202,10 @@ public class ClientSavingsIntegrationTest {
Assert.assertNotNull(clientID);
final String minBalanceForInterestCalculation = null;
final String minRequiredBalance = "1500.0";
final String openningBalance = "1600";
final String enforceMinRequiredBalance = "true";
final boolean allowOverdraft = false;
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, openningBalance ,
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
Assert.assertNotNull(savingsProductID);
@@ -227,7 +228,7 @@ public class ClientSavingsIntegrationTest {
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
Float balance = new Float(MINIMUM_OPENING_BALANCE);
Float balance = new Float(openningBalance);
Float chargeAmt = 100f;
balance -= chargeAmt;
assertEquals("Verifying opening Balance", balance, summary.get("accountBalance"));
@@ -10,7 +10,7 @@ import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.List;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
@@ -6,7 +6,6 @@
package org.mifosplatform.integrationtests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.List;
@@ -23,7 +22,6 @@ import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilde
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
import org.mifosplatform.integrationtests.common.loans.LoanStatusChecker;
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
import org.mifosplatform.integrationtests.common.savings.SavingsAccountHelper;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
@@ -136,7 +134,7 @@ public class MinimumDaysBetweenDisbursalAndFirstRepaymentTest {
List<HashMap> error = (List<HashMap>) this.loanTransactionHelper.createLoanAccount(loanApplicationJSON,
CommonConstants.RESPONSE_ERROR);
assertEquals("validation.msg.loan.days.between.first.repayment.and.disbursal.are.less than.minimum.number.of.days.required", error
assertEquals("error.msg.loan.days.between.first.repayment.and.disbursal.are.less.than.minimum.allowed.numbers", error
.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
}
@@ -121,7 +121,9 @@ public class LoanProductTestBuilder {
map.put("minPrincipal", this.minPrincipal);
map.put("maxPrincipal", this.maxPrincipal);
map.put("overdueDaysForNPA", this.overdueDaysForNPA);
map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment);
if (this.minimumDaysBetweenDisbursalAndFirstRepayment != null) {
map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment);
}
if (multiDisburseLoan) {
map.put("multiDisburseLoan", this.multiDisburseLoan);
map.put("maxTrancheCount", this.maxTrancheCount);
@@ -0,0 +1,60 @@
/**
* 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.account.domain;
/**
* Enum representation of loan status states.
*/
public enum AccountAssociationType {
INVALID(0, "accountAssociationType.invalid"), //
LINKED_ACCOUNT_ASSOCIATION(1, "accountAssociationType.loan.account.association"), //
GUARANTOR_ACCOUNT_ASSOCIATION(2, "accountAssociationType.guarantor.account.association"); //
private final Integer value;
private final String code;
public static AccountAssociationType fromInt(final Integer statusValue) {
AccountAssociationType enumeration = AccountAssociationType.INVALID;
switch (statusValue) {
case 1:
enumeration = AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION;
break;
case 2:
enumeration = AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION;
break;
}
return enumeration;
}
private AccountAssociationType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final AccountAssociationType state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isLinkedAccountAssociation() {
return this.value.equals(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
}
public boolean isGuarantorAccountAssociation() {
return this.value.equals(AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue());
}
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.account.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@@ -18,14 +19,17 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_account_associations")
public class AccountAssociations extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "loan_account_id", nullable = true)
private Loan loanAccount;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "savings_account_id", nullable = true)
private SavingsAccount savingsAccount;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "linked_loan_account_id", nullable = true)
private Loan linkedLoanAccount;
@@ -34,22 +38,29 @@ public class AccountAssociations extends AbstractPersistable<Long> {
@JoinColumn(name = "linked_savings_account_id", nullable = true)
private SavingsAccount linkedSavingsAccount;
@SuppressWarnings("unused")
@Column(name = "association_type_enum", nullable = false)
private Integer associationType;
protected AccountAssociations() {}
private AccountAssociations(final Loan loanAccount, final SavingsAccount savingsAccount, final Loan linkedLoanAccount,
final SavingsAccount linkedSavingsAccount) {
final SavingsAccount linkedSavingsAccount, final Integer associationType) {
this.loanAccount = loanAccount;
this.savingsAccount = savingsAccount;
this.linkedLoanAccount = linkedLoanAccount;
this.linkedSavingsAccount = linkedSavingsAccount;
this.associationType = associationType;
}
public static AccountAssociations associateSavingsAccount(final Loan loan, final SavingsAccount savingsAccount) {
return new AccountAssociations(loan, null, null, savingsAccount);
public static AccountAssociations associateSavingsAccount(final Loan loan, final SavingsAccount savingsAccount,
final Integer associationType) {
return new AccountAssociations(loan, null, null, savingsAccount, associationType);
}
public static AccountAssociations associateSavingsAccount(final SavingsAccount savingsAccount, final SavingsAccount linkedSavingsAccount) {
return new AccountAssociations(null, savingsAccount, null, linkedSavingsAccount);
public static AccountAssociations associateSavingsAccount(final SavingsAccount savingsAccount,
final SavingsAccount linkedSavingsAccount, final Integer associationType) {
return new AccountAssociations(null, savingsAccount, null, linkedSavingsAccount, associationType);
}
public SavingsAccount linkedSavingsAccount() {
@@ -5,13 +5,18 @@
*/
package org.mifosplatform.portfolio.account.service;
import java.util.Collection;
import org.mifosplatform.portfolio.account.data.AccountAssociationsData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
public interface AccountAssociationsReadPlatformService {
public PortfolioAccountData retriveLoanAssociation(final Long loanId);
public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId);
public boolean isLinkedWithAnyActiveAccount(final Long savingsId);
public PortfolioAccountData retriveSavingsAssociation(final Long savingsId);
public PortfolioAccountData retriveSavingsLinkedAssociation(final Long savingsId);
public Collection<AccountAssociationsData> retriveLoanAssociations(Long loanId, Integer associationType);
}
@@ -7,12 +7,14 @@ package org.mifosplatform.portfolio.account.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.account.data.AccountAssociationsData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.account.domain.AccountAssociationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.slf4j.Logger;
@@ -35,12 +37,13 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
}
@Override
public PortfolioAccountData retriveLoanAssociation(final Long loanId) {
public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId) {
PortfolioAccountData linkedAccount = null;
final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? ";
final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?";
try {
final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, loanId);
final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, loanId,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
if (accountAssociationsData != null) {
linkedAccount = accountAssociationsData.linkedAccount();
}
@@ -49,14 +52,27 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
}
return linkedAccount;
}
@Override
public PortfolioAccountData retriveSavingsAssociation(final Long savingsId) {
public Collection<AccountAssociationsData> retriveLoanAssociations(final Long loanId, final Integer associationType) {
final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?";
try {
return this.jdbcTemplate.query(sql, mapper, loanId, associationType);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
@Override
public PortfolioAccountData retriveSavingsLinkedAssociation(final Long savingsId) {
PortfolioAccountData linkedAccount = null;
final AccountAssociationsMapper mapper = new AccountAssociationsMapper();
final String sql = "select " + mapper.schema() + " where aa.savings_account_id = ? ";
final String sql = "select " + mapper.schema() + " where aa.savings_account_id = ? and aa.association_type_enum = ?";
try {
final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, savingsId);
final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, savingsId,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
if (accountAssociationsData != null) {
linkedAccount = accountAssociationsData.linkedAccount();
}
@@ -74,7 +90,7 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
final List<Integer> statusList = this.jdbcTemplate.queryForList(sql, Integer.class, savingsId);
for (final Integer status : statusList) {
if(status !=null){
if (status != null) {
final LoanStatus loanStatus = LoanStatus.fromInt(status);
if (loanStatus.isActiveOrAwaitingApprovalOrDisbursal() || loanStatus.isUnderTransfer()) { return true; }
}
@@ -86,7 +102,7 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
final List<Integer> savstatusList = this.jdbcTemplate.queryForList(savsql, Integer.class, savingsId);
for (final Integer status : savstatusList) {
if(status !=null){
if (status != null) {
final SavingsAccountStatusType saveStatus = SavingsAccountStatusType.fromInt(status);
if (saveStatus.isActiveOrAwaitingApprovalOrDisbursal() || saveStatus.isUnderTransfer()) { return true; }
}
@@ -405,7 +405,7 @@ public class LoansApiResource {
if (associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
}
}
@@ -464,7 +464,7 @@ public class LoansApiResource {
if (!associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
}
if (loanBasicDetails.groupId() != null) {
calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId());
@@ -26,6 +26,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityExce
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.account.domain.AccountAssociationType;
import org.mifosplatform.portfolio.account.domain.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
@@ -254,7 +255,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication,
savingsAccount);
savingsAccount, AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
this.accountAssociationsRepository.save(accountAssociations);
}
@@ -589,7 +590,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
if (accountAssociations == null) {
accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount);
accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
} else {
accountAssociations.updateLinkedSavingsAccount(savingsAccount);
}
@@ -379,7 +379,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
@@ -619,7 +619,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
.getId());
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
@@ -1371,7 +1371,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
.getId());
if (portfolioAccountData == null) {
final String errorMessage = loanCharge.name() + "Charge requires linked savings account for payment";
@@ -1638,7 +1638,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
amount = chargePerInstallment.getAmountOutstanding();
}
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
@@ -1668,7 +1668,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan.getId());
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan.getId());
if (portfolioAccountData == null) {
final String errorMessage = "Disburse Loan with id:" + loan.getId() + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, loan.getId());
@@ -1700,7 +1700,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
if (!installmentChargeData.getDueDate().isAfter(new LocalDate())) {
if (portfolioAccountData == null) {
portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(chargeData
portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(chargeData
.getLoanId());
}
final SavingsAccount fromSavingsAccount = null;
@@ -1715,7 +1715,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
} else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId());
.retriveLoanLinkedAssociation(chargeData.getLoanId());
final SavingsAccount fromSavingsAccount = null;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
@@ -2528,7 +2528,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
amount = chargePerInstallment.getAmountOutstanding();
}
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
@@ -72,4 +72,10 @@ public interface LoanProductConstants {
public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate";
// Guarantee related
public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
public static final String mandatoryGuaranteeParamName = "mandatoryGuarantee";
public static final String minimumGuaranteeFromOwnFundsParamName = "minimumGuaranteeFromOwnFunds";
public static final String minimumGuaranteeFromGuarantorParamName = "minimumGuaranteeFromGuarantor";
}
@@ -135,6 +135,9 @@ public class LoanProductData {
private final Integer maxTrancheCount;
private final BigDecimal outstandingLoanBalance;
private final Boolean holdGuaranteeFunds;
private final LoanProductGuaranteeData productGuaranteeData;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -182,6 +185,8 @@ public class LoanProductData {
final Boolean multiDisburseLoan = null;
final Integer maxTrancheCount = null;
final BigDecimal outstandingLoanBalance = null;
final LoanProductGuaranteeData productGuaranteeData = null;
final Boolean holdGuaranteeFunds = false;
final EnumOptionData daysInMonthType = null;
final EnumOptionData daysInYearType = null;
@@ -197,7 +202,7 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
}
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
@@ -251,6 +256,8 @@ public class LoanProductData {
final boolean isInterestRecalculationEnabled = false;
final LoanProductInterestRecalculationData interestRecalculationData = null;
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -260,7 +267,7 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -321,7 +328,8 @@ public class LoanProductData {
final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData
.sensibleDefaultsForNewLoanProductCreation();
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -331,7 +339,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId,
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuaranteeFunds, productGuaranteeData);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -362,7 +371,8 @@ public class LoanProductData {
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
LoanProductGuaranteeData loanProductGuaranteeData) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -433,12 +443,15 @@ public class LoanProductData {
this.daysInYearType = daysInYearType;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.interestRecalculationData = interestRecalculationData;
this.holdGuaranteeFunds = holdGuaranteeFunds;
this.productGuaranteeData = loanProductGuaranteeData;
this.daysInMonthTypeOptions = null;
this.daysInYearTypeOptions = null;
this.interestRecalculationCompoundingTypeOptions = null;
this.rescheduleStrategyTypeOptions = null;
this.interestRecalculationFrequencyTypeOptions = null;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -515,7 +528,7 @@ public class LoanProductData {
this.multiDisburseLoan = productData.multiDisburseLoan;
this.maxTrancheCount = productData.maxTrancheCount;
this.outstandingLoanBalance = productData.outstandingLoanBalance;
this.minimumDaysBetweenDisbursalAndFirstRepayment = productData.minimumDaysBetweenDisbursalAndFirstRepayment;
this.minimumDaysBetweenDisbursalAndFirstRepayment = productData.minimumDaysBetweenDisbursalAndFirstRepayment;
this.amortizationTypeOptions = amortizationTypeOptions;
this.interestTypeOptions = interestTypeOptions;
@@ -533,6 +546,8 @@ public class LoanProductData {
this.daysInYearType = productData.daysInYearType;
this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled;
this.interestRecalculationData = productData.interestRecalculationData;
this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
this.productGuaranteeData = productData.productGuaranteeData;
this.daysInMonthTypeOptions = daysInMonthTypeOptions;
this.daysInYearTypeOptions = daysInYearTypeOptions;
@@ -0,0 +1,41 @@
/**
* 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.loanproduct.data;
import java.math.BigDecimal;
public class LoanProductGuaranteeData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long productId;
@SuppressWarnings("unused")
private final BigDecimal mandatoryGuarantee;
@SuppressWarnings("unused")
private final BigDecimal minimumGuaranteeFromOwnFunds;
@SuppressWarnings("unused")
private final BigDecimal minimumGuaranteeFromGuarantor;
public static LoanProductGuaranteeData instance(final Long id, final Long productId, final BigDecimal mandatoryGuarantee,
final BigDecimal minimumGuaranteeFromOwnFunds, final BigDecimal minimumGuaranteeFromGuarantor) {
return new LoanProductGuaranteeData(id, productId, mandatoryGuarantee, minimumGuaranteeFromOwnFunds, minimumGuaranteeFromGuarantor);
}
public static LoanProductGuaranteeData sensibleDefaultsForNewLoanProductCreation() {
return new LoanProductGuaranteeData(null, null, null, null, null);
}
private LoanProductGuaranteeData(final Long id, final Long productId, final BigDecimal mandatoryGuarantee,
final BigDecimal minimumGuaranteeFromOwnFunds, final BigDecimal minimumGuaranteeFromGuarantor) {
this.id = id;
this.productId = productId;
this.mandatoryGuarantee = mandatoryGuarantee;
this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
}
}
@@ -129,6 +129,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductInterestRecalculationDetails productInterestRecalculationDetails;
@Column(name = "hold_guarantee_funds")
private boolean holdGuaranteeFunds;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductGuaranteeDetails loanProductGuaranteeDetails;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -211,6 +218,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
if (isInterestRecalculationEnabled) {
interestRecalculationSettings = LoanProductInterestRecalculationDetails.createFrom(command);
}
final boolean holdGuarantorFunds = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName);
LoanProductGuaranteeDetails loanProductGuaranteeDetails = null;
if (holdGuarantorFunds) {
loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
}
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
@@ -218,12 +232,18 @@ public class LoanProduct extends AbstractPersistable<Long> {
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle,
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment);
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuarantorFunds, loanProductGuaranteeDetails);
}
public void updateLoanProductForInterestRecalculationDetails() {
this.productInterestRecalculationDetails.updateProduct(this);
public void updateLoanProductInRelatedClasses() {
if (this.isInterestRecalculationEnabled()) {
this.productInterestRecalculationDetails.updateProduct(this);
}
if (this.holdGuaranteeFunds) {
this.loanProductGuaranteeDetails.updateProduct(this);
}
}
/**
@@ -437,7 +457,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
final boolean isInterestRecalculationEnabled,
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
final LoanProductGuaranteeDetails loanProductGuaranteeDetails) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -487,6 +508,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.overdueDaysForNPA = overdueDaysForNPA;
this.productInterestRecalculationDetails = productInterestRecalculationDetails;
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
this.holdGuaranteeFunds = holdGuarantorFunds;
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
}
public MonetaryCurrency getCurrency() {
@@ -690,6 +713,30 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.productInterestRecalculationDetails.update(command, actualChanges, localeAsInput);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName, this.holdGuaranteeFunds)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.holdGuaranteeFundsParamName);
actualChanges.put(LoanProductConstants.holdGuaranteeFundsParamName, newValue);
this.holdGuaranteeFunds = newValue;
}
if (actualChanges.containsKey(LoanProductConstants.holdGuaranteeFundsParamName)) {
if (this.holdGuaranteeFunds) {
this.loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
this.loanProductGuaranteeDetails.updateProduct(this);
actualChanges.put(LoanProductConstants.mandatoryGuaranteeParamName,
this.loanProductGuaranteeDetails.getMandatoryGuarantee());
actualChanges.put(LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
this.loanProductGuaranteeDetails.getMinimumGuaranteeFromGuarantor());
actualChanges.put(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName,
this.loanProductGuaranteeDetails.getMinimumGuaranteeFromOwnFunds());
} else {
this.loanProductGuaranteeDetails = null;
}
} else if (this.holdGuaranteeFunds) {
this.loanProductGuaranteeDetails.update(command, actualChanges);
}
return actualChanges;
}
@@ -929,4 +976,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
public LoanProductInterestRecalculationDetails getProductInterestRecalculationDetails() {
return this.productInterestRecalculationDetails;
}
public boolean isHoldGuaranteeFundsEnabled() {
return this.holdGuaranteeFunds;
}
public LoanProductGuaranteeDetails getLoanProductGuaranteeDetails() {
return this.loanProductGuaranteeDetails;
}
}
@@ -0,0 +1,111 @@
/**
* 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.loanproduct.domain;
import java.math.BigDecimal;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.springframework.data.jpa.domain.AbstractPersistable;
/**
* Entity for capturing interest recalculation settings
*
* @author conflux
*/
@Entity
@Table(name = "m_product_loan_guarantee_details")
public class LoanProductGuaranteeDetails extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@OneToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "mandatory_guarantee", scale = 6, precision = 19, nullable = false)
private BigDecimal mandatoryGuarantee;
@Column(name = "minimum_guarantee_from_own_funds", scale = 6, precision = 19, nullable = false)
private BigDecimal minimumGuaranteeFromOwnFunds;
@Column(name = "minimum_guarantee_from_guarantor_funds", scale = 6, precision = 19, nullable = false)
private BigDecimal minimumGuaranteeFromGuarantor;
protected LoanProductGuaranteeDetails() {
//
}
public static LoanProductGuaranteeDetails createFrom(final JsonCommand command) {
final BigDecimal mandatoryGuarantee = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
final BigDecimal minimumGuaranteeFromGuarantor = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
final BigDecimal minimumGuaranteeFromOwnFunds = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
return new LoanProductGuaranteeDetails(mandatoryGuarantee, minimumGuaranteeFromOwnFunds, minimumGuaranteeFromGuarantor);
}
private LoanProductGuaranteeDetails(final BigDecimal mandatoryGuarantee, final BigDecimal minimumGuaranteeFromOwnFunds,
final BigDecimal minimumGuaranteeFromGuarantor) {
this.mandatoryGuarantee = mandatoryGuarantee;
this.minimumGuaranteeFromGuarantor = minimumGuaranteeFromGuarantor;
this.minimumGuaranteeFromOwnFunds = minimumGuaranteeFromOwnFunds;
}
public void updateProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public void update(final JsonCommand command, final Map<String, Object> actualChanges) {
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName, this.mandatoryGuarantee)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.mandatoryGuaranteeParamName);
actualChanges.put(LoanProductConstants.mandatoryGuaranteeParamName, newValue);
this.mandatoryGuarantee = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
this.minimumGuaranteeFromGuarantor)) {
final BigDecimal newValue = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromGuarantorParamName);
actualChanges.put(LoanProductConstants.minimumGuaranteeFromGuarantorParamName, newValue);
this.minimumGuaranteeFromGuarantor = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName,
this.minimumGuaranteeFromOwnFunds)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName);
actualChanges.put(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, newValue);
this.minimumGuaranteeFromOwnFunds = newValue;
}
}
public BigDecimal getMandatoryGuarantee() {
return this.mandatoryGuarantee;
}
public BigDecimal getMinimumGuaranteeFromOwnFunds() {
return this.minimumGuaranteeFromOwnFunds;
}
public BigDecimal getMinimumGuaranteeFromGuarantor() {
return this.minimumGuaranteeFromGuarantor;
}
}
@@ -71,7 +71,9 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyDateParamName,
LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
LoanProductConstants.recalculationRestFrequencyTypeParameterName,
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment));
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName));
private final FromJsonHelper fromApiJsonHelper;
@@ -309,6 +311,20 @@ public final class LoanProductDataValidator {
}
}
// Guarantee Funds
Boolean holdGuaranteeFunds = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
holdGuaranteeFunds = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.holdGuaranteeFundsParamName, element);
baseDataValidator.reset().parameter(LoanProductConstants.holdGuaranteeFundsParamName).value(holdGuaranteeFunds).notNull()
.isOneOfTheseValues(true, false);
}
if (holdGuaranteeFunds != null) {
if (holdGuaranteeFunds) {
validateGuaranteeParams(element, baseDataValidator, null);
}
}
// accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
@@ -704,16 +720,31 @@ public final class LoanProductDataValidator {
}
// Interest recalculation settings
Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
.value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
}
if (isInterestRecalculationEnabled != null) {
if (isInterestRecalculationEnabled.booleanValue()) {
validateInterestRecalculationParams(element, baseDataValidator, loanProduct);
}
if (isInterestRecalculationEnabled != null) {
if (isInterestRecalculationEnabled) {
validateInterestRecalculationParams(element, baseDataValidator, loanProduct);
}
}
// Guarantee Funds
Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
holdGuaranteeFunds = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.holdGuaranteeFundsParamName, element);
baseDataValidator.reset().parameter(LoanProductConstants.holdGuaranteeFundsParamName).value(holdGuaranteeFunds).notNull()
.isOneOfTheseValues(true, false);
}
if (holdGuaranteeFunds != null) {
if (holdGuaranteeFunds) {
validateGuaranteeParams(element, baseDataValidator, null);
}
}
@@ -1279,4 +1310,49 @@ public final class LoanProductDataValidator {
}
private void validateGuaranteeParams(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final LoanProduct loanProduct) {
BigDecimal mandatoryGuarantee = BigDecimal.ZERO;
BigDecimal minimumGuaranteeFromOwnFunds = BigDecimal.ZERO;
BigDecimal minimumGuaranteeFromGuarantor = BigDecimal.ZERO;
if (loanProduct != null) {
mandatoryGuarantee = loanProduct.getLoanProductGuaranteeDetails().getMandatoryGuarantee();
minimumGuaranteeFromOwnFunds = loanProduct.getLoanProductGuaranteeDetails().getMinimumGuaranteeFromOwnFunds();
minimumGuaranteeFromGuarantor = loanProduct.getLoanProductGuaranteeDetails().getMinimumGuaranteeFromGuarantor();
}
if (loanProduct == null || this.fromApiJsonHelper.parameterExists(LoanProductConstants.mandatoryGuaranteeParamName, element)) {
mandatoryGuarantee = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanProductConstants.mandatoryGuaranteeParamName,
element);
baseDataValidator.reset().parameter(LoanProductConstants.mandatoryGuaranteeParamName).value(mandatoryGuarantee).notNull();
if (mandatoryGuarantee == null) {
mandatoryGuarantee = BigDecimal.ZERO;
}
}
if (loanProduct == null
|| this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGuaranteeFromGuarantorParamName, element)) {
minimumGuaranteeFromGuarantor = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
LoanProductConstants.minimumGuaranteeFromGuarantorParamName, element);
if (minimumGuaranteeFromGuarantor == null) {
minimumGuaranteeFromGuarantor = BigDecimal.ZERO;
}
}
if (loanProduct == null
|| this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, element)) {
minimumGuaranteeFromOwnFunds = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, element);
if (minimumGuaranteeFromOwnFunds == null) {
minimumGuaranteeFromOwnFunds = BigDecimal.ZERO;
}
}
if (mandatoryGuarantee.compareTo(minimumGuaranteeFromOwnFunds.add(minimumGuaranteeFromGuarantor)) == -1) {
baseDataValidator.parameter(LoanProductConstants.mandatoryGuaranteeParamName).failWithCode(
"must.be.greter.than.sum.of.min.funds");
}
}
}
@@ -24,6 +24,7 @@ import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.common.service.CommonEnumerations;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductGuaranteeData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductInterestRecalculationData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
@@ -128,10 +129,14 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
+ "lpr.rest_freqency_date as restFrequencyDate, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, "
+ "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, "
+ "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 "
+ " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
+ " left join m_product_loan_guarantee_details lpg on lpg.loan_product_id=lp.id "
+ " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id"
+ " join m_currency curr on curr.code = lp.currency_code";
}
@@ -173,7 +178,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing");
final Integer overdueDaysForNPA = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overdueDaysForNPA");
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = JdbcSupport.getInteger(rs,"minimumDaysBetweenDisbursalAndFirstRepayment");
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = JdbcSupport.getInteger(rs,
"minimumDaysBetweenDisbursalAndFirstRepayment");
final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
@@ -256,6 +262,17 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
restFrequencyDate);
}
final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds");
LoanProductGuaranteeData loanProductGuaranteeData = null;
if (holdGuaranteeFunds) {
final Long lpgId = JdbcSupport.getLong(rs, "lpgId");
final BigDecimal mandatoryGuarantee = rs.getBigDecimal("mandatoryGuarantee");
final BigDecimal minimumGuaranteeFromOwnFunds = rs.getBigDecimal("minimumGuaranteeFromOwnFunds");
final BigDecimal minimumGuaranteeFromGuarantor = rs.getBigDecimal("minimumGuaranteeFromGuarantor");
loanProductGuaranteeData = LoanProductGuaranteeData.instance(lpgId, id, mandatoryGuarantee, minimumGuaranteeFromOwnFunds,
minimumGuaranteeFromGuarantor);
}
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
@@ -265,7 +282,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData);
}
}
@@ -92,9 +92,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
this.aprCalculator);
if (loanproduct.isInterestRecalculationEnabled()) {
loanproduct.updateLoanProductForInterestRecalculationDetails();
}
loanproduct.updateLoanProductInRelatedClasses();
this.loanProductRepository.save(loanproduct);
@@ -209,7 +209,7 @@ public class FixedDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.linkedAccount)) {
mandatoryResponseParameters.add(SavingsApiConstants.linkedAccount);
linkedAccount = this.accountAssociationsReadPlatformService.retriveSavingsAssociation(accountId);
linkedAccount = this.accountAssociationsReadPlatformService.retriveSavingsLinkedAssociation(accountId);
}
}
@@ -249,6 +249,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredBalance;
@Column(name = "on_hold_funds_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal onHoldFunds;
@Temporal(TemporalType.DATE)
@Column(name = "start_interest_calculation_date")
protected Date startInterestCalculationDate;
@@ -854,6 +857,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isWithdrawBalance) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
@@ -866,21 +870,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final BigDecimal withdrawalFee = null;
// deal with potential minRequiredBalance and
// enforceMinRequiredBalance
if (!isWithdrawBalance && this.minRequiredBalance != null && this.enforceMinRequiredBalance && transaction.isWithdrawal()) {
if (runningBalance.minus(this.minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException(
if (!isWithdrawBalance && transaction.canProcessBalanceCheck()) {
if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException(
"transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); }
}
if (runningBalance.isLessThanZero()) {
Money limit = runningBalance.zero();
if (this.allowOverdraft) {
if (this.overdraftLimit != null) {
limit = limit.plus(this.overdraftLimit);
}
}
if (limit.plus(runningBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException("transactionAmount",
getAccountBalance(), withdrawalFee, transactionAmount); }
}
}
}
@@ -888,7 +882,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
@@ -896,24 +890,20 @@ public class SavingsAccount extends AbstractPersistable<Long> {
runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
}
if (runningBalance.isLessThanZero()) {
//
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType()
.resourceName() + transactionAction);
if (this.allowOverdraft) {
Money limit = runningBalance.zero();
if (this.overdraftLimit != null) {
limit = limit.plus(this.overdraftLimit);
}
if (limit.plus(runningBalance).isLessThanZero()) {
// enforceMinRequiredBalance
if (transaction.canProcessBalanceCheck()) {
if (runningBalance.minus(minRequiredBalance).isLessThanZero()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(depositAccountType().resourceName() + transactionAction);
if (this.allowOverdraft) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.exceeding.overdraft.limit");
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
}
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
}
@@ -2430,8 +2420,19 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
return savingsAccountCharge;
}
public String getAccountNumber() {
return this.accountNumber;
}
private Money minRequiredBalanceDerived(final MonetaryCurrency currency) {
Money minReqBalance = Money.of(currency, this.onHoldFunds);
if (this.enforceMinRequiredBalance) {
minReqBalance = minReqBalance.plus(this.minRequiredBalance);
}
if (this.allowOverdraft) {
minReqBalance = minReqBalance.minus(this.overdraftLimit);
}
return minReqBalance;
}
}
@@ -231,7 +231,7 @@ public class SavingsAccountAssembler {
}
BigDecimal minRequiredBalance = null;
if (command.parameterExists(overdraftLimitParamName)) {
if (command.parameterExists(minRequiredBalanceParamName)) {
minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
} else {
minRequiredBalance = product.minRequiredBalance();
@@ -40,6 +40,10 @@ import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeWithoutMandatoryFieldException;
import org.springframework.data.jpa.domain.AbstractPersistable;
/**
* @author dv6
*
*/
@Entity
@Table(name = "m_savings_account_charge")
public class SavingsAccountCharge extends AbstractPersistable<Long> {
@@ -823,4 +827,16 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
public boolean isNotActive() {
return !isActive();
}
/**
* This method is to identify the charges which can override the savings
* rules(for example if there is a minimum enforced balance of 1000 on
* savings account with account balance of 1000, still these charges can be
* collected as these charges are initiated by system and it can bring down
* the balance below the enforced minimum balance).
*
*/
public boolean canOverriteSavingAccountRules() {
return !(this.isSavingsActivation() || this.isWithdrawalFee());
}
}
@@ -77,4 +77,8 @@ public class SavingsAccountChargePaidBy extends AbstractPersistable<Long> {
public boolean isPenaltyCharge() {
return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.isPenaltyCharge();
}
public boolean canOverriteSavingAccountRules() {
return (this.savingsAccountCharge == null) ? false : this.savingsAccountCharge.canOverriteSavingAccountRules();
}
}
@@ -514,6 +514,15 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return SavingsAccountTransactionType.fromInt(this.typeOf).isWaiveCharge();
}
private boolean canOverriteSavingAccountRules() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isChargeTransaction() && chargePaidBy != null) ? chargePaidBy.canOverriteSavingAccountRules() : false;
}
public boolean canProcessBalanceCheck() {
return isDebit() && !canOverriteSavingAccountRules();
}
public boolean isFeeCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
@@ -196,7 +196,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (amountForDeposit.isGreaterThanZero()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveSavingsAssociation(savingsId);
.retriveSavingsLinkedAssociation(savingsId);
if (portfolioAccountData == null) {
final PaymentDetail paymentDetail = null;
@@ -274,7 +274,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
Money amountForDeposit = account.activateWithBalance();
if (amountForDeposit.isGreaterThanZero()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveSavingsAssociation(savingsId);
.retriveSavingsLinkedAssociation(savingsId);
if (portfolioAccountData == null) {
this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(),
amountForDeposit.getAmount(), null, isRegularTransaction);
@@ -33,6 +33,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.account.domain.AccountAssociationType;
import org.mifosplatform.portfolio.account.domain.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
@@ -115,8 +116,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final FixedDepositAccountRepository fixedDepositAccountRepository,
final RecurringDepositAccountRepository recurringDepositAccountRepository,
final AccountAssociationsRepository accountAssociationsRepository, final FromJsonHelper fromJsonHelper,
final CalendarInstanceRepository calendarInstanceRepository,
final ConfigurationDomainService configurationDomainService) {
final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.depositAccountAssembler = depositAccountAssembler;
@@ -171,11 +171,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
try {
this.depositAccountDataValidator.validateFixedDepositForSubmit(command.json());
final AppUser submittedBy = this.context.authenticatedUser();
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService
.retrieveFinancialYearBeginningMonth();
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(command, submittedBy,
DepositAccountType.FIXED_DEPOSIT);
@@ -183,8 +182,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final MathContext mc = MathContext.DECIMAL64;
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth);
this.fixedDepositAccountRepository.save(account);
if (account.isAccountNumberRequiresAutoGeneration()) {
@@ -201,7 +200,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId,
DepositAccountType.SAVINGS_DEPOSIT);
this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account);
final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount);
final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
this.accountAssociationsRepository.save(accountAssociations);
}
@@ -227,11 +227,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
try {
this.depositAccountDataValidator.validateRecurringDepositForSubmit(command.json());
final AppUser submittedBy = this.context.authenticatedUser();
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService
.retrieveFinancialYearBeginningMonth();
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(command,
submittedBy, DepositAccountType.RECURRING_DEPOSIT);
@@ -257,8 +256,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
frequency = frequency == -1 ? 1 : frequency;
account.generateSchedule(frequencyType, frequency, calendar);
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth);
account.validateApplicableInterestRate();
this.savingAccountRepository.save(account);
@@ -334,11 +333,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
public CommandProcessingResult modifyFDApplication(final Long accountId, final JsonCommand command) {
try {
this.depositAccountDataValidator.validateFixedDepositForUpdate(command.json());
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService
.retrieveFinancialYearBeginningMonth();
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final Map<String, Object> changes = new LinkedHashMap<>(20);
@@ -353,79 +351,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final MathContext mc = MathContext.DECIMAL64;
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
this.savingAccountRepository.save(account);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(accountId) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(accountId) //
.with(changes) //
.build();
} catch (final DataAccessException dve) {
handleDataIntegrityIssues(command, dve);
return new CommandProcessingResult(Long.valueOf(-1));
}
}
@Transactional
@Override
public CommandProcessingResult modifyRDApplication(final Long accountId, final JsonCommand command) {
try {
this.depositAccountDataValidator.validateRecurringDepositForUpdate(command.json());
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService
.retrieveFinancialYearBeginningMonth();
final Map<String, Object> changes = new LinkedHashMap<>(20);
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(accountId,
DepositAccountType.RECURRING_DEPOSIT);
checkClientOrGroupActive(account);
account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), DepositAccountType.RECURRING_DEPOSIT.resourceName());
if (!changes.isEmpty()) {
updateFDAndRDCommonChanges(changes, command, account);
final MathContext mc = MathContext.DECIMAL64;
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
final Calendar calendar = calendarInstance.getCalendar();
final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence());
frequency = frequency == -1 ? 1 : frequency;
account.generateSchedule(frequencyType, frequency, calendar);
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure,
isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth);
account.validateApplicableInterestRate();
this.savingAccountRepository.save(account);
}
// update calendar details
if (!account.isCalendarInherited()) {
final LocalDate calendarStartDate = account.depositStartDate();
final Integer frequencyType = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyTypeParamName);
final PeriodFrequencyType periodFrequencyType = PeriodFrequencyType.fromInt(frequencyType);
final Integer frequency = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyParamName);
final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
Calendar calendar = calendarInstance.getCalendar();
calendar.updateRepeatingCalendar(calendarStartDate, CalendarFrequencyType.from(periodFrequencyType), frequency,
repeatsOnDay);
this.calendarInstanceRepository.save(calendarInstance);
}
boolean isLinkedAccRequired = command.booleanPrimitiveValueOfParameterNamed(transferInterestToSavingsParamName);
// Save linked account information
@@ -458,7 +387,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
DepositAccountType.SAVINGS_DEPOSIT);
this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account);
if (accountAssociations == null) {
accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount);
accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
} else {
accountAssociations.updateLinkedSavingsAccount(savingsAccount);
}
@@ -482,6 +412,74 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
}
}
@Transactional
@Override
public CommandProcessingResult modifyRDApplication(final Long accountId, final JsonCommand command) {
try {
this.depositAccountDataValidator.validateRecurringDepositForUpdate(command.json());
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final Map<String, Object> changes = new LinkedHashMap<>(20);
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(accountId,
DepositAccountType.RECURRING_DEPOSIT);
checkClientOrGroupActive(account);
account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), DepositAccountType.RECURRING_DEPOSIT.resourceName());
if (!changes.isEmpty()) {
updateFDAndRDCommonChanges(changes, command, account);
final MathContext mc = MathContext.DECIMAL64;
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
final Calendar calendar = calendarInstance.getCalendar();
final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence());
frequency = frequency == -1 ? 1 : frequency;
account.generateSchedule(frequencyType, frequency, calendar);
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth);
account.validateApplicableInterestRate();
this.savingAccountRepository.save(account);
}
// update calendar details
if (!account.isCalendarInherited()) {
final LocalDate calendarStartDate = account.depositStartDate();
final Integer frequencyType = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyTypeParamName);
final PeriodFrequencyType periodFrequencyType = PeriodFrequencyType.fromInt(frequencyType);
final Integer frequency = command.integerValueSansLocaleOfParameterNamed(recurringFrequencyParamName);
final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue());
Calendar calendar = calendarInstance.getCalendar();
calendar.updateRepeatingCalendar(calendarStartDate, CalendarFrequencyType.from(periodFrequencyType), frequency,
repeatsOnDay);
this.calendarInstanceRepository.save(calendarInstance);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(accountId) //
.withOfficeId(account.officeId()) //
.withClientId(account.clientId()) //
.withGroupId(account.groupId()) //
.withSavingsId(accountId) //
.with(changes) //
.build();
} catch (final DataAccessException dve) {
handleDataIntegrityIssues(command, dve);
return new CommandProcessingResult(Long.valueOf(-1));
}
}
private void updateFDAndRDCommonChanges(final Map<String, Object> changes, final JsonCommand command, final SavingsAccount account) {
if (changes.containsKey(SavingsApiConstants.clientIdParamName)) {
@@ -0,0 +1,18 @@
ALTER TABLE `m_savings_account`
ADD COLUMN `on_hold_funds_derived` DECIMAL(19,6) NULL DEFAULT NULL;
ALTER TABLE `m_portfolio_account_associations`
ADD COLUMN `association_type_enum` SMALLINT(1) NOT NULL DEFAULT '1';
CREATE TABLE `m_product_loan_guarantee_details` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_product_id` BIGINT(20) NOT NULL,
`mandatory_guarantee` DECIMAL(19,5) NOT NULL,
`minimum_guarantee_from_own_funds` DECIMAL(19,5) NULL,
`minimum_guarantee_from_guarantor_funds` DECIMAL(19,5) NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_guarantee_details_loan_product` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
);
ALTER TABLE `m_product_loan`
ADD COLUMN `hold_guarantee_funds` TINYINT(1) NOT NULL DEFAULT '0';