MIFOSX-1641: Added capture of guarantee details for loan product and added constrain on savings withdraw

This commit is contained in:
Pramod Nuthakki
2014-11-07 13:15:27 +05:30
parent 99b3bf1b4d
commit 2418c09701
21 changed files with 602 additions and 171 deletions
@@ -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; package org.mifosplatform.portfolio.account.domain;
import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
@@ -18,14 +19,17 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_portfolio_account_associations") @Table(name = "m_portfolio_account_associations")
public class AccountAssociations extends AbstractPersistable<Long> { public class AccountAssociations extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "loan_account_id", nullable = true) @JoinColumn(name = "loan_account_id", nullable = true)
private Loan loanAccount; private Loan loanAccount;
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "savings_account_id", nullable = true) @JoinColumn(name = "savings_account_id", nullable = true)
private SavingsAccount savingsAccount; private SavingsAccount savingsAccount;
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "linked_loan_account_id", nullable = true) @JoinColumn(name = "linked_loan_account_id", nullable = true)
private Loan linkedLoanAccount; private Loan linkedLoanAccount;
@@ -34,22 +38,29 @@ public class AccountAssociations extends AbstractPersistable<Long> {
@JoinColumn(name = "linked_savings_account_id", nullable = true) @JoinColumn(name = "linked_savings_account_id", nullable = true)
private SavingsAccount linkedSavingsAccount; private SavingsAccount linkedSavingsAccount;
@SuppressWarnings("unused")
@Column(name = "association_type_enum", nullable = false)
private Integer associationType;
protected AccountAssociations() {} protected AccountAssociations() {}
private AccountAssociations(final Loan loanAccount, final SavingsAccount savingsAccount, final Loan linkedLoanAccount, private AccountAssociations(final Loan loanAccount, final SavingsAccount savingsAccount, final Loan linkedLoanAccount,
final SavingsAccount linkedSavingsAccount) { final SavingsAccount linkedSavingsAccount, final Integer associationType) {
this.loanAccount = loanAccount; this.loanAccount = loanAccount;
this.savingsAccount = savingsAccount; this.savingsAccount = savingsAccount;
this.linkedLoanAccount = linkedLoanAccount; this.linkedLoanAccount = linkedLoanAccount;
this.linkedSavingsAccount = linkedSavingsAccount; this.linkedSavingsAccount = linkedSavingsAccount;
this.associationType = associationType;
} }
public static AccountAssociations associateSavingsAccount(final Loan loan, final SavingsAccount savingsAccount) { public static AccountAssociations associateSavingsAccount(final Loan loan, final SavingsAccount savingsAccount,
return new AccountAssociations(loan, null, null, savingsAccount); final Integer associationType) {
return new AccountAssociations(loan, null, null, savingsAccount, associationType);
} }
public static AccountAssociations associateSavingsAccount(final SavingsAccount savingsAccount, final SavingsAccount linkedSavingsAccount) { public static AccountAssociations associateSavingsAccount(final SavingsAccount savingsAccount,
return new AccountAssociations(null, savingsAccount, null, linkedSavingsAccount); final SavingsAccount linkedSavingsAccount, final Integer associationType) {
return new AccountAssociations(null, savingsAccount, null, linkedSavingsAccount, associationType);
} }
public SavingsAccount linkedSavingsAccount() { public SavingsAccount linkedSavingsAccount() {
@@ -5,13 +5,18 @@
*/ */
package org.mifosplatform.portfolio.account.service; package org.mifosplatform.portfolio.account.service;
import java.util.Collection;
import org.mifosplatform.portfolio.account.data.AccountAssociationsData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData; import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
public interface AccountAssociationsReadPlatformService { public interface AccountAssociationsReadPlatformService {
public PortfolioAccountData retriveLoanAssociation(final Long loanId); public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId);
public boolean isLinkedWithAnyActiveAccount(final Long savingsId); 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.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.account.data.AccountAssociationsData; import org.mifosplatform.portfolio.account.data.AccountAssociationsData;
import org.mifosplatform.portfolio.account.data.PortfolioAccountData; 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.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType; import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -35,12 +37,13 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
} }
@Override @Override
public PortfolioAccountData retriveLoanAssociation(final Long loanId) { public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId) {
PortfolioAccountData linkedAccount = null; PortfolioAccountData linkedAccount = null;
final AccountAssociationsMapper mapper = new AccountAssociationsMapper(); 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 { 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) { if (accountAssociationsData != null) {
linkedAccount = accountAssociationsData.linkedAccount(); linkedAccount = accountAssociationsData.linkedAccount();
} }
@@ -51,12 +54,25 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
} }
@Override @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; PortfolioAccountData linkedAccount = null;
final AccountAssociationsMapper mapper = new AccountAssociationsMapper(); 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 { 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) { if (accountAssociationsData != null) {
linkedAccount = accountAssociationsData.linkedAccount(); linkedAccount = accountAssociationsData.linkedAccount();
} }
@@ -74,7 +90,7 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
final List<Integer> statusList = this.jdbcTemplate.queryForList(sql, Integer.class, savingsId); final List<Integer> statusList = this.jdbcTemplate.queryForList(sql, Integer.class, savingsId);
for (final Integer status : statusList) { for (final Integer status : statusList) {
if(status !=null){ if (status != null) {
final LoanStatus loanStatus = LoanStatus.fromInt(status); final LoanStatus loanStatus = LoanStatus.fromInt(status);
if (loanStatus.isActiveOrAwaitingApprovalOrDisbursal() || loanStatus.isUnderTransfer()) { return true; } 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); final List<Integer> savstatusList = this.jdbcTemplate.queryForList(savsql, Integer.class, savingsId);
for (final Integer status : savstatusList) { for (final Integer status : savstatusList) {
if(status !=null){ if (status != null) {
final SavingsAccountStatusType saveStatus = SavingsAccountStatusType.fromInt(status); final SavingsAccountStatusType saveStatus = SavingsAccountStatusType.fromInt(status);
if (saveStatus.isActiveOrAwaitingApprovalOrDisbursal() || saveStatus.isUnderTransfer()) { return true; } if (saveStatus.isActiveOrAwaitingApprovalOrDisbursal() || saveStatus.isUnderTransfer()) { return true; }
} }
@@ -405,7 +405,7 @@ public class LoansApiResource {
if (associationParameters.contains("linkedAccount")) { if (associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount"); mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId); linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
} }
} }
@@ -460,7 +460,7 @@ public class LoansApiResource {
if (!associationParameters.contains("linkedAccount")) { if (!associationParameters.contains("linkedAccount")) {
mandatoryResponseParameters.add("linkedAccount"); mandatoryResponseParameters.add("linkedAccount");
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId); linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
} }
if (loanBasicDetails.groupId() != null) { if (loanBasicDetails.groupId() != null) {
calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId()); 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.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff; 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.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository; import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
@@ -254,7 +255,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication); this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication, final AccountAssociations accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication,
savingsAccount); savingsAccount, AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
this.accountAssociationsRepository.save(accountAssociations); this.accountAssociationsRepository.save(accountAssociations);
} }
@@ -589,7 +590,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication); this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
if (accountAssociations == null) { if (accountAssociations == null) {
accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount); accountAssociations = AccountAssociations.associateSavingsAccount(existingLoanApplication, savingsAccount,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
} else { } else {
accountAssociations.updateLinkedSavingsAccount(savingsAccount); accountAssociations.updateLinkedSavingsAccount(savingsAccount);
} }
@@ -361,7 +361,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) { 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 SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true; final boolean isRegularTransaction = true;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
@@ -560,7 +560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) { for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
.getId()); .getId());
final SavingsAccount fromSavingsAccount = null; final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true; final boolean isRegularTransaction = true;
@@ -1312,7 +1312,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loan
.getId()); .getId());
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
final String errorMessage = loanCharge.name() + "Charge requires linked savings account for payment"; final String errorMessage = loanCharge.name() + "Charge requires linked savings account for payment";
@@ -1579,7 +1579,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
amount = chargePerInstallment.getAmountOutstanding(); amount = chargePerInstallment.getAmountOutstanding();
} }
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId); final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment"; final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId); throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
@@ -1609,7 +1609,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); 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) { if (portfolioAccountData == null) {
final String errorMessage = "Disburse Loan with id:" + loan.getId() + " requires linked savings account for payment"; 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()); throw new LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, loan.getId());
@@ -1641,7 +1641,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) { for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
if (!installmentChargeData.getDueDate().isAfter(new LocalDate())) { if (!installmentChargeData.getDueDate().isAfter(new LocalDate())) {
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(chargeData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(chargeData
.getLoanId()); .getLoanId());
} }
final SavingsAccount fromSavingsAccount = null; final SavingsAccount fromSavingsAccount = null;
@@ -1656,7 +1656,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) { } else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(new LocalDate())) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId()); .retriveLoanLinkedAssociation(chargeData.getLoanId());
final SavingsAccount fromSavingsAccount = null; final SavingsAccount fromSavingsAccount = null;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(new LocalDate(),
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
@@ -2469,7 +2469,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
amount = chargePerInstallment.getAmountOutstanding(); amount = chargePerInstallment.getAmountOutstanding();
} }
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId); final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment"; final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId); throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
@@ -72,4 +72,10 @@ public interface LoanProductConstants {
public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval"; public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval";
public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate"; 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 Integer maxTrancheCount;
private final BigDecimal outstandingLoanBalance; private final BigDecimal outstandingLoanBalance;
private final Boolean holdGuaranteeFunds;
private final LoanProductGuaranteeData productGuaranteeData;
/** /**
* Used when returning lookup information about loan product for dropdowns. * Used when returning lookup information about loan product for dropdowns.
*/ */
@@ -182,6 +185,8 @@ public class LoanProductData {
final Boolean multiDisburseLoan = null; final Boolean multiDisburseLoan = null;
final Integer maxTrancheCount = null; final Integer maxTrancheCount = null;
final BigDecimal outstandingLoanBalance = null; final BigDecimal outstandingLoanBalance = null;
final LoanProductGuaranteeData productGuaranteeData = null;
final Boolean holdGuaranteeFunds = false;
final EnumOptionData daysInMonthType = null; final EnumOptionData daysInMonthType = null;
final EnumOptionData daysInYearType = null; final EnumOptionData daysInYearType = null;
@@ -197,7 +202,7 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment); interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
} }
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { 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 boolean isInterestRecalculationEnabled = false;
final LoanProductInterestRecalculationData interestRecalculationData = null; final LoanProductInterestRecalculationData interestRecalculationData = null;
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = 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, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -260,7 +267,7 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment); interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
} }
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -321,7 +328,8 @@ public class LoanProductData {
final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData
.sensibleDefaultsForNewLoanProductCreation(); .sensibleDefaultsForNewLoanProductCreation();
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = 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, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -331,7 +339,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId,
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, 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, 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, Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData, final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
LoanProductGuaranteeData loanProductGuaranteeData) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -433,12 +443,15 @@ public class LoanProductData {
this.daysInYearType = daysInYearType; this.daysInYearType = daysInYearType;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.interestRecalculationData = interestRecalculationData; this.interestRecalculationData = interestRecalculationData;
this.holdGuaranteeFunds = holdGuaranteeFunds;
this.productGuaranteeData = loanProductGuaranteeData;
this.daysInMonthTypeOptions = null; this.daysInMonthTypeOptions = null;
this.daysInYearTypeOptions = null; this.daysInYearTypeOptions = null;
this.interestRecalculationCompoundingTypeOptions = null; this.interestRecalculationCompoundingTypeOptions = null;
this.rescheduleStrategyTypeOptions = null; this.rescheduleStrategyTypeOptions = null;
this.interestRecalculationFrequencyTypeOptions = null; this.interestRecalculationFrequencyTypeOptions = null;
} }
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions, public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -533,6 +546,8 @@ public class LoanProductData {
this.daysInYearType = productData.daysInYearType; this.daysInYearType = productData.daysInYearType;
this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled; this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled;
this.interestRecalculationData = productData.interestRecalculationData; this.interestRecalculationData = productData.interestRecalculationData;
this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
this.productGuaranteeData = productData.productGuaranteeData;
this.daysInMonthTypeOptions = daysInMonthTypeOptions; this.daysInMonthTypeOptions = daysInMonthTypeOptions;
this.daysInYearTypeOptions = daysInYearTypeOptions; 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) @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductInterestRecalculationDetails productInterestRecalculationDetails; 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, public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) { final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -211,6 +218,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
if (isInterestRecalculationEnabled) { if (isInterestRecalculationEnabled) {
interestRecalculationSettings = LoanProductInterestRecalculationDetails.createFrom(command); 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, return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
@@ -218,12 +232,18 @@ public class LoanProduct extends AbstractPersistable<Long> {
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle,
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment); isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuarantorFunds, loanProductGuaranteeDetails);
} }
public void updateLoanProductForInterestRecalculationDetails() { public void updateLoanProductInRelatedClasses() {
this.productInterestRecalculationDetails.updateProduct(this); 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 Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
final boolean isInterestRecalculationEnabled, final boolean isInterestRecalculationEnabled,
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
final LoanProductGuaranteeDetails loanProductGuaranteeDetails) {
this.fund = fund; this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy; this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim(); this.name = name.trim();
@@ -487,6 +508,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.overdueDaysForNPA = overdueDaysForNPA; this.overdueDaysForNPA = overdueDaysForNPA;
this.productInterestRecalculationDetails = productInterestRecalculationDetails; this.productInterestRecalculationDetails = productInterestRecalculationDetails;
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment; this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
this.holdGuaranteeFunds = holdGuarantorFunds;
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
} }
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
@@ -690,6 +713,30 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.productInterestRecalculationDetails.update(command, actualChanges, localeAsInput); 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; return actualChanges;
} }
@@ -929,4 +976,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
public LoanProductInterestRecalculationDetails getProductInterestRecalculationDetails() { public LoanProductInterestRecalculationDetails getProductInterestRecalculationDetails() {
return this.productInterestRecalculationDetails; 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.recalculationRestFrequencyDateParamName,
LoanProductConstants.recalculationRestFrequencyIntervalParameterName, LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
LoanProductConstants.recalculationRestFrequencyTypeParameterName, LoanProductConstants.recalculationRestFrequencyTypeParameterName,
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment)); LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName));
private final FromJsonHelper fromApiJsonHelper; 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 // accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
@@ -704,16 +720,31 @@ public final class LoanProductDataValidator {
} }
// Interest recalculation settings // Interest recalculation settings
Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed( isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
LoanProductConstants.isInterestRecalculationEnabledParameterName, element); LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName) baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
.value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false); .value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
}
if (isInterestRecalculationEnabled != null) { if (isInterestRecalculationEnabled != null) {
if (isInterestRecalculationEnabled.booleanValue()) { if (isInterestRecalculationEnabled) {
validateInterestRecalculationParams(element, baseDataValidator, loanProduct); 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.common.service.CommonEnumerations;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; 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.data.LoanProductInterestRecalculationData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; 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.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_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
+ "lpr.rest_freqency_date as restFrequencyDate, " + "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 " + "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 " + " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id " + " 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_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" + " 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"; + " 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 graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing"); final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing");
final Integer overdueDaysForNPA = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overdueDaysForNPA"); 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 Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId); final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
@@ -256,6 +262,17 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
restFrequencyDate); 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, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
@@ -265,7 +282,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, 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, final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
this.aprCalculator); this.aprCalculator);
if (loanproduct.isInterestRecalculationEnabled()) { loanproduct.updateLoanProductInRelatedClasses();
loanproduct.updateLoanProductForInterestRecalculationDetails();
}
this.loanProductRepository.save(loanproduct); this.loanProductRepository.save(loanproduct);
@@ -209,7 +209,7 @@ public class FixedDepositAccountsApiResource {
if (associationParameters.contains(SavingsApiConstants.linkedAccount)) { if (associationParameters.contains(SavingsApiConstants.linkedAccount)) {
mandatoryResponseParameters.add(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) @Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredBalance; private BigDecimal minRequiredBalance;
@Column(name = "on_hold_funds_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal onHoldFunds;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "start_interest_calculation_date") @Column(name = "start_interest_calculation_date")
protected Date startInterestCalculationDate; protected Date startInterestCalculationDate;
@@ -854,6 +857,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isWithdrawBalance) { public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isWithdrawBalance) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions(); final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
Money runningBalance = Money.zero(this.currency); Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isCredit()) { if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
@@ -866,21 +870,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final BigDecimal withdrawalFee = null; final BigDecimal withdrawalFee = null;
// deal with potential minRequiredBalance and // deal with potential minRequiredBalance and
// enforceMinRequiredBalance // enforceMinRequiredBalance
if (!isWithdrawBalance && this.minRequiredBalance != null && this.enforceMinRequiredBalance && transaction.isWithdrawal()) { if (!isWithdrawBalance && transaction.isDebit()) {
if (runningBalance.minus(this.minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException( if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { throw new InsufficientAccountBalanceException(
"transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); } "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(); final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
Money runningBalance = Money.zero(this.currency); Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isCredit()) { if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
@@ -896,24 +890,20 @@ public class SavingsAccount extends AbstractPersistable<Long> {
runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
} }
if (runningBalance.isLessThanZero()) { // enforceMinRequiredBalance
// if (transaction.isDebit()) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); if (runningBalance.minus(minRequiredBalance).isLessThanZero()) {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(depositAccountType() final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
.resourceName() + transactionAction); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(depositAccountType().resourceName() + transactionAction);
if (this.allowOverdraft) { if (this.allowOverdraft) {
Money limit = runningBalance.zero();
if (this.overdraftLimit != null) {
limit = limit.plus(this.overdraftLimit);
}
if (limit.plus(runningBalance).isLessThanZero()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.exceeding.overdraft.limit"); baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.exceeding.overdraft.limit");
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
} }
} else { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("results.in.balance.going.negative");
} }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
} }
} }
@@ -2430,4 +2420,15 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
return savingsAccountCharge; return savingsAccountCharge;
} }
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; BigDecimal minRequiredBalance = null;
if (command.parameterExists(overdraftLimitParamName)) { if (command.parameterExists(minRequiredBalanceParamName)) {
minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName); minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
} else { } else {
minRequiredBalance = product.minRequiredBalance(); minRequiredBalance = product.minRequiredBalance();
@@ -196,7 +196,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (amountForDeposit.isGreaterThanZero()) { if (amountForDeposit.isGreaterThanZero()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveSavingsAssociation(savingsId); .retriveSavingsLinkedAssociation(savingsId);
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
@@ -274,7 +274,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
Money amountForDeposit = account.activateWithBalance(); Money amountForDeposit = account.activateWithBalance();
if (amountForDeposit.isGreaterThanZero()) { if (amountForDeposit.isGreaterThanZero()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveSavingsAssociation(savingsId); .retriveSavingsLinkedAssociation(savingsId);
if (portfolioAccountData == null) { if (portfolioAccountData == null) {
this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(), this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationLocalDate(),
amountForDeposit.getAmount(), null, isRegularTransaction); 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.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper; 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.AccountAssociations;
import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository; import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
@@ -115,8 +116,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final FixedDepositAccountRepository fixedDepositAccountRepository, final FixedDepositAccountRepository fixedDepositAccountRepository,
final RecurringDepositAccountRepository recurringDepositAccountRepository, final RecurringDepositAccountRepository recurringDepositAccountRepository,
final AccountAssociationsRepository accountAssociationsRepository, final FromJsonHelper fromJsonHelper, final AccountAssociationsRepository accountAssociationsRepository, final FromJsonHelper fromJsonHelper,
final CalendarInstanceRepository calendarInstanceRepository, final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService) {
final ConfigurationDomainService configurationDomainService) {
this.context = context; this.context = context;
this.savingAccountRepository = savingAccountRepository; this.savingAccountRepository = savingAccountRepository;
this.depositAccountAssembler = depositAccountAssembler; this.depositAccountAssembler = depositAccountAssembler;
@@ -173,9 +173,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final AppUser submittedBy = this.context.authenticatedUser(); final AppUser submittedBy = this.context.authenticatedUser();
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
.retrieveFinancialYearBeginningMonth();
final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(command, submittedBy, final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(command, submittedBy,
DepositAccountType.FIXED_DEPOSIT); DepositAccountType.FIXED_DEPOSIT);
@@ -183,8 +182,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
final boolean isPreMatureClosure = false; final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); financialYearBeginningMonth);
this.fixedDepositAccountRepository.save(account); this.fixedDepositAccountRepository.save(account);
if (account.isAccountNumberRequiresAutoGeneration()) { if (account.isAccountNumberRequiresAutoGeneration()) {
@@ -201,7 +200,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId, final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId,
DepositAccountType.SAVINGS_DEPOSIT); DepositAccountType.SAVINGS_DEPOSIT);
this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account); 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); this.accountAssociationsRepository.save(accountAssociations);
} }
@@ -229,9 +229,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final AppUser submittedBy = this.context.authenticatedUser(); final AppUser submittedBy = this.context.authenticatedUser();
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
.retrieveFinancialYearBeginningMonth();
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(command, final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(command,
submittedBy, DepositAccountType.RECURRING_DEPOSIT); submittedBy, DepositAccountType.RECURRING_DEPOSIT);
@@ -257,8 +256,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
frequency = frequency == -1 ? 1 : frequency; frequency = frequency == -1 ? 1 : frequency;
account.generateSchedule(frequencyType, frequency, calendar); account.generateSchedule(frequencyType, frequency, calendar);
final boolean isPreMatureClosure = false; final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); financialYearBeginningMonth);
account.validateApplicableInterestRate(); account.validateApplicableInterestRate();
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);
@@ -336,9 +335,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.depositAccountDataValidator.validateFixedDepositForUpdate(command.json()); this.depositAccountDataValidator.validateFixedDepositForUpdate(command.json());
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
.retrieveFinancialYearBeginningMonth();
final Map<String, Object> changes = new LinkedHashMap<>(20); final Map<String, Object> changes = new LinkedHashMap<>(20);
@@ -353,79 +351,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
final boolean isPreMatureClosure = false; final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
this.savingAccountRepository.save(account); 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); boolean isLinkedAccRequired = command.booleanPrimitiveValueOfParameterNamed(transferInterestToSavingsParamName);
// Save linked account information // Save linked account information
@@ -458,7 +387,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
DepositAccountType.SAVINGS_DEPOSIT); DepositAccountType.SAVINGS_DEPOSIT);
this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account); this.depositAccountDataValidator.validatelinkedSavingsAccount(savingsAccount, account);
if (accountAssociations == null) { if (accountAssociations == null) {
accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount); accountAssociations = AccountAssociations.associateSavingsAccount(account, savingsAccount,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
} else { } else {
accountAssociations.updateLinkedSavingsAccount(savingsAccount); 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) { private void updateFDAndRDCommonChanges(final Map<String, Object> changes, final JsonCommand command, final SavingsAccount account) {
if (changes.containsKey(SavingsApiConstants.clientIdParamName)) { 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';