accounting posting wip (only loan product mapping)

This commit is contained in:
Vishwas Babu AJ
2013-01-10 01:04:12 +05:30
parent 29af2d5999
commit 4d81b78d5d
24 changed files with 1096 additions and 33 deletions
@@ -0,0 +1,21 @@
ALTER TABLE `m_product_loan`
ADD COLUMN `accounting_type` SMALLINT(5) NOT NULL AFTER `amortization_method_enum`;
update m_product_loan set accounting_type=1;
DROP TABLE if exists `acc_product_mapping`;
CREATE TABLE `acc_product_mapping` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`gl_account_id` BIGINT(20) NULL DEFAULT NULL,
`product_id` BIGINT(20) NULL DEFAULT NULL,
`product_type` SMALLINT(5) NULL DEFAULT NULL,
`financial_account_type` SMALLINT(5) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -1,5 +1,8 @@
package org.mifosplatform.accounting;
import java.util.HashMap;
import java.util.Map;
public class AccountingConstants {
/*** Categories for Accounting **/
@@ -32,4 +35,128 @@ public class AccountingConstants {
}
}
/*** Types of portfolio products ***/
public static enum PORTFOLIO_PRODUCT_TYPE {
LOAN(1), SAVING(2);
private final Integer value;
private PORTFOLIO_PRODUCT_TYPE(final Integer value) {
this.value = value;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
private static final Map<Integer, PORTFOLIO_PRODUCT_TYPE> intToEnumMap = new HashMap<Integer, PORTFOLIO_PRODUCT_TYPE>();
static {
for (PORTFOLIO_PRODUCT_TYPE type : PORTFOLIO_PRODUCT_TYPE.values()) {
intToEnumMap.put(type.value, type);
}
}
public static PORTFOLIO_PRODUCT_TYPE fromInt(int i) {
PORTFOLIO_PRODUCT_TYPE type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
/*** Accounting placeholders for cash based accounting for loan products ***/
public static enum CASH_ACCOUNTS_FOR_LOAN {
FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6);
private final Integer value;
private CASH_ACCOUNTS_FOR_LOAN(final Integer value) {
this.value = value;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
private static final Map<Integer, CASH_ACCOUNTS_FOR_LOAN> intToEnumMap = new HashMap<Integer, CASH_ACCOUNTS_FOR_LOAN>();
static {
for (CASH_ACCOUNTS_FOR_LOAN type : CASH_ACCOUNTS_FOR_LOAN.values()) {
intToEnumMap.put(type.value, type);
}
}
public static CASH_ACCOUNTS_FOR_LOAN fromInt(int i) {
CASH_ACCOUNTS_FOR_LOAN type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
/*** Accounting placeholders for accrual based accounting for loan products ***/
public static enum ACCRUAL_ACCOUNTS_FOR_LOAN {
FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(
7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9);
private final Integer value;
private ACCRUAL_ACCOUNTS_FOR_LOAN(final Integer value) {
this.value = value;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
private static final Map<Integer, ACCRUAL_ACCOUNTS_FOR_LOAN> intToEnumMap = new HashMap<Integer, ACCRUAL_ACCOUNTS_FOR_LOAN>();
static {
for (ACCRUAL_ACCOUNTS_FOR_LOAN type : ACCRUAL_ACCOUNTS_FOR_LOAN.values()) {
intToEnumMap.put(type.value, type);
}
}
public static ACCRUAL_ACCOUNTS_FOR_LOAN fromInt(int i) {
ACCRUAL_ACCOUNTS_FOR_LOAN type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
/***
* Enum of all accounting related input parameter names used while
* creating/updating a loan product
***/
public static enum LOAN_PRODUCT_ACCOUNTING_PARAMS {
FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES(
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE(
"receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE(
"receivablePenaltyAccountId");
private final String value;
private LOAN_PRODUCT_ACCOUNTING_PARAMS(final String value) {
this.value = value;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,6 @@
package org.mifosplatform.accounting.api.commands;
public class ProductToGLAccountMappingCommand {
}
@@ -0,0 +1,40 @@
package org.mifosplatform.accounting.api.data;
public class ProductToGLAccountMappingData {
private final Long id;
private final Long glAccountId;
private final Long productId;
private final Integer productType;
private final Integer financialAccountType;
public ProductToGLAccountMappingData(Long id, Long glAccountId, Long productId, Integer productType, Integer financialAccountType) {
super();
this.id = id;
this.glAccountId = glAccountId;
this.productId = productId;
this.productType = productType;
this.financialAccountType = financialAccountType;
}
public Long getId() {
return this.id;
}
public Long getGlAccountId() {
return this.glAccountId;
}
public Long getProductId() {
return this.productId;
}
public Integer getProductType() {
return this.productType;
}
public Integer getFinancialAccountType() {
return this.financialAccountType;
}
}
@@ -0,0 +1,79 @@
package org.mifosplatform.accounting.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "acc_product_mapping", uniqueConstraints = { @UniqueConstraint(columnNames = { "product_id", "product_type",
"financial_account_type" }, name = "financial_action") })
public class ProductToGLAccountMapping extends AbstractPersistable<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "gl_account_id")
private GLAccount glAccount;
@Column(name = "product_id", nullable = false)
private Long productId;
@Column(name = "product_type", nullable = false)
private int productType;
@Column(name = "financial_account_type", nullable = false)
private int financialAccountType;
public static ProductToGLAccountMapping createNew(final GLAccount glAccount, final Long productId, final int productType,
final int financialAccountType) {
return new ProductToGLAccountMapping(glAccount, productId, productType, financialAccountType);
}
protected ProductToGLAccountMapping() {
//
}
public ProductToGLAccountMapping(GLAccount glAccount, Long productId, int productType, int financialAccountType) {
this.glAccount = glAccount;
this.productId = productId;
this.productType = productType;
this.financialAccountType = financialAccountType;
}
public GLAccount getGlAccount() {
return this.glAccount;
}
public void setGlAccount(GLAccount glAccount) {
this.glAccount = glAccount;
}
public Long getProductId() {
return this.productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public int getProductType() {
return this.productType;
}
public void setProductType(int productType) {
this.productType = productType;
}
public int getFinancialAccountType() {
return this.financialAccountType;
}
public void setFinancialAccountType(int financialAccountType) {
this.financialAccountType = financialAccountType;
}
}
@@ -0,0 +1,14 @@
package org.mifosplatform.accounting.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ProductToGLAccountMappingRepository extends JpaRepository<ProductToGLAccountMapping, Long>,
JpaSpecificationExecutor<ProductToGLAccountMapping> {
ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountType(Long productId, int productType, int financialAccountType);
List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType);
}
@@ -0,0 +1,16 @@
package org.mifosplatform.accounting.exceptions;
import org.mifosplatform.accounting.AccountingConstants.PORTFOLIO_PRODUCT_TYPE;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when product to GL account mapping are not
* found.
*/
public class ProductToGLAccountMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
public ProductToGLAccountMappingNotFoundException(final PORTFOLIO_PRODUCT_TYPE type, final Long productId, String accountType) {
super("error.msg.productTyAccountMapping.not.found", "Mapping for product of type" + type.toString() + " with Id " + productId
+ " does not exist for an account of type " + accountType, type.toString(), productId, accountType);
}
}
@@ -2,6 +2,7 @@ package org.mifosplatform.accounting.service;
import java.util.List;
import org.mifosplatform.accounting.AccountingConstants;
import org.mifosplatform.accounting.api.data.GLAccountData;
public interface GLAccountReadPlatformService {
@@ -11,4 +12,6 @@ public interface GLAccountReadPlatformService {
GLAccountData retrieveGLAccountById(long glAccountId);
List<GLAccountData> retrieveAllEnabledDetailGLAccounts(AccountingConstants.GL_ACCOUNT_CLASSIFICATION classification);
}
@@ -0,0 +1,9 @@
package org.mifosplatform.accounting.service;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
public interface ProductToGLAccountMappingReadPlatformService {
LoanProductData fetchAccountMappingDetailsForLoanProduct(LoanProductData loanProductData);
}
@@ -0,0 +1,15 @@
package org.mifosplatform.accounting.service;
import java.util.Map;
import org.mifosplatform.portfolio.loanproduct.command.LoanProductCommand;
public interface ProductToGLAccountMappingWritePlatformService {
void createLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command);
Map<String, Object> updateLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command, boolean accountingRuleChanged);
void deleteLoanProductToGLAccountMapping(Long loanProductId);
}
@@ -6,6 +6,7 @@ import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.AccountingConstants;
import org.mifosplatform.accounting.AccountingConstants.GL_ACCOUNT_CLASSIFICATION;
import org.mifosplatform.accounting.AccountingConstants.GL_ACCOUNT_USAGE;
import org.mifosplatform.accounting.api.data.GLAccountData;
@@ -151,6 +152,11 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
}
}
@Override
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION classification) {
return retrieveAllGLAccounts(classification.toString(), null, AccountingConstants.GL_ACCOUNT_USAGE.DETAIL.toString(), null, false);
}
private static boolean checkValidGLAccountClassification(final String entityType) {
for (GL_ACCOUNT_CLASSIFICATION classification : GL_ACCOUNT_CLASSIFICATION.values()) {
if (classification.name().toString().equalsIgnoreCase(entityType)) { return true; }
@@ -0,0 +1,104 @@
package org.mifosplatform.accounting.service.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.mifosplatform.accounting.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.api.data.ProductToGLAccountMappingData;
import org.mifosplatform.accounting.service.ProductToGLAccountMappingReadPlatformService;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public ProductToGLAccountMappingReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class LoanProductToGLAccountMappingMapper implements RowMapper<ProductToGLAccountMappingData> {
public String schema() {
return " id as id, gl_account_id as glAccountId,product_id as productId,product_type as productType,financial_account_type as financialAccountType"
+ " from acc_product_mapping " + " where product_type=1 ";
}
@Override
public ProductToGLAccountMappingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
Long id = rs.getLong("id");
Long glAccountId = rs.getLong("glAccountId");
Long productId = rs.getLong("productId");
Integer productType = rs.getInt("productType");
Integer financialAccountType = rs.getInt("financialAccountType");
return new ProductToGLAccountMappingData(id, glAccountId, productId, productType, financialAccountType);
}
}
@Override
public LoanProductData fetchAccountMappingDetailsForLoanProduct(LoanProductData loanProductData) {
LoanProductToGLAccountMappingMapper rm = new LoanProductToGLAccountMappingMapper();
String sql = "select " + rm.schema() + " and product_id = ?";
List<ProductToGLAccountMappingData> productToGLAccountMappingDatas = this.jdbcTemplate.query(sql, rm,
new Object[] { loanProductData.getId() });
if (loanProductData.getAccountingType() == AccountingRuleType.CASH_BASED.getValue()) {
for (ProductToGLAccountMappingData productToGLAccountMappingData : productToGLAccountMappingDatas) {
CASH_ACCOUNTS_FOR_LOAN glAccountForLoan = CASH_ACCOUNTS_FOR_LOAN.fromInt(productToGLAccountMappingData
.getFinancialAccountType());
if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE)) {
loanProductData.setFundSourceAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES)) {
loanProductData.setIncomeFromFeeAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES)) {
loanProductData.setIncomeFromPenaltyAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS)) {
loanProductData.setInterestOnLoanAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
loanProductData.setLoanPortfolioAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
loanProductData.setWriteOffAccountId(productToGLAccountMappingData.getGlAccountId());
}
}
} else if (loanProductData.getAccountingType() == AccountingRuleType.ACCRUAL_BASED.getValue()) {
for (ProductToGLAccountMappingData productToGLAccountMappingData : productToGLAccountMappingDatas) {
ACCRUAL_ACCOUNTS_FOR_LOAN glAccountForLoan = ACCRUAL_ACCOUNTS_FOR_LOAN.fromInt(productToGLAccountMappingData
.getFinancialAccountType());
if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE)) {
loanProductData.setFundSourceAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES)) {
loanProductData.setIncomeFromFeeAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES)) {
loanProductData.setIncomeFromPenaltyAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS)) {
loanProductData.setInterestOnLoanAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
loanProductData.setLoanPortfolioAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
loanProductData.setWriteOffAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE)) {
loanProductData.setReceivableInterestAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE)) {
loanProductData.setReceivableFeeAccountId(productToGLAccountMappingData.getGlAccountId());
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE)) {
loanProductData.setReceivablePenaltyAccountId(productToGLAccountMappingData.getGlAccountId());
}
}
}
return loanProductData;
}
}
@@ -0,0 +1,212 @@
package org.mifosplatform.accounting.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mifosplatform.accounting.AccountingConstants;
import org.mifosplatform.accounting.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN;
import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
import org.mifosplatform.accounting.AccountingConstants.PORTFOLIO_PRODUCT_TYPE;
import org.mifosplatform.accounting.domain.GLAccount;
import org.mifosplatform.accounting.domain.GLAccountRepository;
import org.mifosplatform.accounting.domain.ProductToGLAccountMapping;
import org.mifosplatform.accounting.domain.ProductToGLAccountMappingRepository;
import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException;
import org.mifosplatform.accounting.exceptions.ProductToGLAccountMappingNotFoundException;
import org.mifosplatform.accounting.service.ProductToGLAccountMappingWritePlatformService;
import org.mifosplatform.portfolio.loanproduct.command.LoanProductCommand;
import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductToGLAccountMappingWritePlatformServiceImpl implements ProductToGLAccountMappingWritePlatformService {
private final GLAccountRepository accountRepository;
private final ProductToGLAccountMappingRepository accountMappingRepository;
@Autowired
public ProductToGLAccountMappingWritePlatformServiceImpl(final GLAccountRepository glAccountRepository,
final ProductToGLAccountMappingRepository glAccountMappingRepository) {
this.accountRepository = glAccountRepository;
this.accountMappingRepository = glAccountMappingRepository;
}
@Override
@Transactional
public void createLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command) {
AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.getAccountingType());
if (accountingRuleType.equals(AccountingRuleType.NONE)) {
return;
} else if (accountingRuleType.equals(AccountingRuleType.CASH_BASED)) {
// asset
saveLoanToAccountMapping(command.getFundAccountId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue());
saveLoanToAccountMapping(command.getLoanPortfolioAccountId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue());
saveLoanToAccountMapping(command.getInterestOnLoanId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue());
// income
saveLoanToAccountMapping(command.getIncomeFromFeeId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue());
saveLoanToAccountMapping(command.getIncomeFromPenaltyId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
// expenses
saveLoanToAccountMapping(command.getWriteOffAccountId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
} else if (accountingRuleType.equals(AccountingRuleType.ACCRUAL_BASED)) {
// assets (including receivables)
saveLoanToAccountMapping(command.getFundAccountId(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue());
saveLoanToAccountMapping(command.getLoanPortfolioAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue());
saveLoanToAccountMapping(command.getInterestOnLoanId(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue());
saveLoanToAccountMapping(command.getReceivableInterestAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue());
saveLoanToAccountMapping(command.getReceivableFeeAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue());
saveLoanToAccountMapping(command.getReceivablePenaltyAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue());
// income
saveLoanToAccountMapping(command.getIncomeFromFeeId(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue());
saveLoanToAccountMapping(command.getIncomeFromPenaltyId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue());
// expenses
saveLoanToAccountMapping(command.getWriteOffAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue());
}
}
@Override
@Transactional
public Map<String, Object> updateLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command,
boolean accountingRuleChanged) {
Map<String, Object> changes = new HashMap<String, Object>();
AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.getAccountingType());
if (accountingRuleChanged) {
deleteLoanProductToGLAccountMapping(loanProductId);
createLoanProductToGLAccountMapping(loanProductId, command);
if (accountingRuleType.equals(AccountingRuleType.CASH_BASED)) {
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), command.getFundAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), command.getLoanPortfolioAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), command.getInterestOnLoanId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), command.getIncomeFromFeeId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), command.getIncomeFromPenaltyId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), command.getWriteOffAccountId());
} else if (accountingRuleType.equals(AccountingRuleType.ACCRUAL_BASED)) {
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), command.getFundAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), command.getLoanPortfolioAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), command.getInterestOnLoanId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), command.getIncomeFromFeeId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), command.getIncomeFromPenaltyId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), command.getWriteOffAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), command.getReceivableInterestAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), command.getReceivableFeeAccountId());
changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), command.getReceivablePenaltyAccountId());
}
} else {
if (accountingRuleType.equals(AccountingRuleType.NONE)) {
// do nothing
} else if (accountingRuleType.equals(AccountingRuleType.CASH_BASED)) {
// asset
updateLoanToAccountMapping(command.getFundAccountId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(),
CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), changes);
updateLoanToAccountMapping(command.getLoanPortfolioAccountId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), changes);
updateLoanToAccountMapping(command.getInterestOnLoanId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), changes);
// income
updateLoanToAccountMapping(command.getIncomeFromFeeId(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(),
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
changes);
updateLoanToAccountMapping(command.getIncomeFromPenaltyId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), changes);
// expenses
updateLoanToAccountMapping(command.getWriteOffAccountId(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), changes);
} else if (accountingRuleType.equals(AccountingRuleType.ACCRUAL_BASED)) {
// assets (including receivables)
updateLoanToAccountMapping(command.getFundAccountId(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), changes);
updateLoanToAccountMapping(command.getLoanPortfolioAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), changes);
updateLoanToAccountMapping(command.getInterestOnLoanId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), changes);
updateLoanToAccountMapping(command.getReceivableInterestAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), changes);
updateLoanToAccountMapping(command.getReceivableFeeAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), changes);
updateLoanToAccountMapping(command.getReceivablePenaltyAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), changes);
// income
updateLoanToAccountMapping(command.getIncomeFromFeeId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), changes);
updateLoanToAccountMapping(command.getIncomeFromPenaltyId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), changes);
// expenses
updateLoanToAccountMapping(command.getWriteOffAccountId(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), changes);
}
}
return changes;
}
private void saveLoanToAccountMapping(Long glAccountId, Long productId, int accountTypeId) {
GLAccount glAccount = accountRepository.findOne(glAccountId);
if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId,
AccountingConstants.PORTFOLIO_PRODUCT_TYPE.LOAN.getValue(), accountTypeId);
accountMappingRepository.save(accountMapping);
}
private void updateLoanToAccountMapping(Long glAccountId, Long productId, int accountTypeId, String accountTypeName,
String accountParamValue, Map<String, Object> changes) {
// get the existing product
ProductToGLAccountMapping accountMapping = accountMappingRepository.findByProductIdAndProductTypeAndFinancialAccountType(productId,
PORTFOLIO_PRODUCT_TYPE.LOAN.getValue(), accountTypeId);
if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PORTFOLIO_PRODUCT_TYPE.LOAN, productId,
accountTypeName); }
if (accountMapping.getGlAccount().getId() != glAccountId) {
GLAccount glAccount = accountRepository.findOne(glAccountId);
if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
changes.put(accountParamValue, glAccountId);
accountMapping.setGlAccount(glAccount);
accountMappingRepository.save(accountMapping);
}
}
@Override
public void deleteLoanProductToGLAccountMapping(Long loanProductId) {
List<ProductToGLAccountMapping> productToGLAccountMappings = accountMappingRepository.findByProductIdAndProductType(loanProductId,
PORTFOLIO_PRODUCT_TYPE.LOAN.getValue());
if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) {
accountMappingRepository.deleteInBatch(productToGLAccountMappings);
}
}
}
@@ -57,6 +57,6 @@ public class LoanProductRelatedDetailAssembler {
return new LoanProductRelatedDetail(currency, principalAmount, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance, null);
}
}
@@ -17,8 +17,16 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.accounting.AccountingConstants.GL_ACCOUNT_CLASSIFICATION;
import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
import org.mifosplatform.accounting.api.data.GLAccountData;
import org.mifosplatform.accounting.service.GLAccountReadPlatformService;
import org.mifosplatform.accounting.service.ProductToGLAccountMappingReadPlatformService;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
@@ -51,7 +59,10 @@ public class LoanProductsApiResource {
"interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "charges", "createdOn",
"lastModifedOn", "currencyOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions",
"repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "transactionProcessingStrategyOptions",
"chargeOptions"));
"chargeOptions", "assetAccountOptions", "incomeAccountOptions", "expenseAccountOptions", "accountingType" , LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()));
private final String resourceNameForPermissions = "LOANPRODUCT";
@@ -64,6 +75,8 @@ public class LoanProductsApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final LoanDropdownReadPlatformService dropdownReadPlatformService;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final GLAccountReadPlatformService accountReadPlatformService;
private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
@Autowired
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -71,7 +84,9 @@ public class LoanProductsApiResource {
final FundReadPlatformService fundReadPlatformService, final LoanDropdownReadPlatformService dropdownReadPlatformService,
final DefaultToApiJsonSerializer<LoanProductData> toApiJsonSerializer,
final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final GLAccountReadPlatformService accountReadPlatformService,
final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService) {
this.context = context;
this.loanProductReadPlatformService = readPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
@@ -81,6 +96,8 @@ public class LoanProductsApiResource {
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.accountReadPlatformService = accountReadPlatformService;
this.accountMappingReadPlatformService = accountMappingReadPlatformService;
}
@POST
@@ -137,8 +154,10 @@ public class LoanProductsApiResource {
if (settings.isTemplate()) {
loanProduct = handleTemplate(loanProduct);
}
LoanProductData loanProductDataUpdatedWithAccountingMapping=accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(loanProduct);
return this.toApiJsonSerializer.serialize(settings, loanProduct, LOAN_PRODUCT_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, loanProductDataUpdatedWithAccountingMapping, LOAN_PRODUCT_DATA_PARAMETERS);
}
@PUT
@@ -174,9 +193,14 @@ public class LoanProductsApiResource {
final Collection<FundData> fundOptions = this.fundReadPlatformService.retrieveAllFunds();
final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = this.dropdownReadPlatformService
.retreiveTransactionProcessingStrategies();
final List<GLAccountData> assetAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION.ASSET);
final List<GLAccountData> incomeAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION.INCOME);;
final List<GLAccountData> expenseAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION.EXPENSE);;
return new LoanProductData(productData, chargeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions);
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions,
assetAccountOptions, incomeAccountOptions, expenseAccountOptions);
}
}
@@ -4,9 +4,11 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType;
/**
* Immutable command for creating or updating details of a loan product.
@@ -31,11 +33,28 @@ public class LoanProductCommand {
private final Integer interestCalculationPeriodType;
private final String[] charges;
// accounting fields
private final Integer accountingType;
// accounting heads used for cash/accrual based accounting
private final Long loanPortfolioAccountId;
private final Long fundAccountId;
private final Long interestOnLoanId;
private final Long incomeFromFeeId;
private final Long incomeFromPenaltyId;
private final Long writeOffAccountId;
// accounting heads used only for accrual based accounting
private final Long receivableInterestAccountId;
private final Long receivableFeeAccountId;
private final Long receivablePenaltyAccountId;
public LoanProductCommand(final String name, final String description, final Long fundId, final Long transactionProcessingStrategyId,
final String currencyCode, final Integer digitsAfterDecimal, final BigDecimal principal, final BigDecimal inArrearsTolerance,
final Integer numberOfRepayments, final Integer repaymentEvery, final BigDecimal interestRatePerPeriod,
final Integer repaymentFrequencyType, final Integer interestRateFrequencyType, final Integer amortizationType,
final Integer interestType, final Integer interestCalculationPeriodType, final String[] charges) {
final Integer interestType, final Integer interestCalculationPeriodType, final String[] charges, final Integer accountingType,
final Long loanPortfolioAccountId, final Long fundAccountId, final Long interestOnLoanId, final Long incomeFromFeeId,
final Long incomeFromPenaltyId, final Long writeOffAccountId, final Long receivableInterestAccountId,
final Long receivableFeeAccountId, final Long receivablePenaltyAccountId) {
this.name = name;
this.description = description;
this.fundId = fundId;
@@ -57,6 +76,18 @@ public class LoanProductCommand {
this.interestType = interestType;
this.interestCalculationPeriodType = interestCalculationPeriodType;
this.charges = charges;
this.accountingType = accountingType;
// accounting heads used for cash/accrual based accounting
this.loanPortfolioAccountId = loanPortfolioAccountId;
this.fundAccountId = fundAccountId;
this.interestOnLoanId = interestOnLoanId;
this.incomeFromFeeId = incomeFromFeeId;
this.incomeFromPenaltyId = incomeFromPenaltyId;
this.writeOffAccountId = writeOffAccountId;
// accounting heads used only for accrual based accounting
this.receivableInterestAccountId = receivableInterestAccountId;
this.receivableFeeAccountId = receivableFeeAccountId;
this.receivablePenaltyAccountId = receivablePenaltyAccountId;
}
public String getName() {
@@ -127,6 +158,55 @@ public class LoanProductCommand {
return charges;
}
public Integer getAccountingType() {
return this.accountingType;
}
public Long getLoanPortfolioAccountId() {
return this.loanPortfolioAccountId;
}
public Long getFundAccountId() {
return this.fundAccountId;
}
public Long getInterestOnLoanId() {
return this.interestOnLoanId;
}
public Long getIncomeFromFeeId() {
return this.incomeFromFeeId;
}
public Long getIncomeFromPenaltyId() {
return this.incomeFromPenaltyId;
}
public Long getWriteOffAccountId() {
return this.writeOffAccountId;
}
public Long getReceivableInterestAccountId() {
return this.receivableInterestAccountId;
}
public Long getReceivableFeeAccountId() {
return this.receivableFeeAccountId;
}
public Long getReceivablePenaltyAccountId() {
return this.receivablePenaltyAccountId;
}
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -154,6 +234,38 @@ public class LoanProductCommand {
baseDataValidator.reset().parameter("interestCalculationPeriodType").value(this.interestCalculationPeriodType).notNull()
.inMinMaxRange(0, 1);
// validation for accounting rules
baseDataValidator.reset().parameter("accountingType").value(this.accountingType).notNull().inMinMaxRange(1,3);
if (this.getAccountingType() != null) {
/**
* validation for heads applicable for both cash and accrual based
* accounting
**/
if (this.getAccountingType().equals(AccountingRuleType.CASH_BASED.getValue())
|| this.getAccountingType().equals(AccountingRuleType.ACCRUAL_BASED.getValue())) {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue())
.value(this.loanPortfolioAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(this.fundAccountId)
.notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue())
.value(this.interestOnLoanId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(this.incomeFromFeeId)
.notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
.value(this.incomeFromPenaltyId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue())
.value(this.writeOffAccountId).notNull().integerGreaterThanZero();
}// validation for accrual based accounting specific heads
if (this.getAccountingType().equals(AccountingRuleType.ACCRUAL_BASED.getValue())) {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
.value(this.receivableInterestAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue())
.value(this.receivableFeeAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
.value(this.receivablePenaltyAccountId).notNull().integerGreaterThanZero();
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
@@ -182,20 +294,50 @@ public class LoanProductCommand {
.integerGreaterThanZero();
baseDataValidator.reset().parameter("interestRatePerPeriod").value(this.interestRatePerPeriod).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("interestRateFrequencyType").value(this.interestRateFrequencyType).ignoreIfNull()
.notNull().inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("interestRateFrequencyType").value(this.interestRateFrequencyType).ignoreIfNull().notNull()
.inMinMaxRange(0, 3);
baseDataValidator.reset().parameter("amortizationType").value(this.amortizationType).ignoreIfNull().notNull()
.inMinMaxRange(0, 1);
baseDataValidator.reset().parameter("amortizationType").value(this.amortizationType).ignoreIfNull().notNull().inMinMaxRange(0, 1);
baseDataValidator.reset().parameter("interestType").value(this.interestType).ignoreIfNull().notNull().inMinMaxRange(0, 1);
baseDataValidator.reset().parameter("interestCalculationPeriodType").value(this.interestCalculationPeriodType)
.ignoreIfNull().notNull().inMinMaxRange(0, 1);
baseDataValidator.reset().parameter("interestCalculationPeriodType").value(this.interestCalculationPeriodType).ignoreIfNull()
.notNull().inMinMaxRange(0, 1);
baseDataValidator.reset().anyOfNotNull(this.fundId, this.transactionProcessingStrategyId, this.name,
this.description, this.currencyCode, this.digitsAfterDecimal, this.principal,
this.inArrearsTolerance, this.repaymentFrequencyType, this.repaymentEvery,
this.numberOfRepayments, this.interestRatePerPeriod, this.interestRateFrequencyType,
this.amortizationType, this.interestType, this.interestCalculationPeriodType, this.charges);
// validation for accounting rules
baseDataValidator.reset().parameter("accountingType").value(this.accountingType).ignoreIfNull().inMinMaxRange(1, 3);
if (this.getAccountingType() != null) {
/**
* validation for heads applicable for both cash and accrual based
* accounting
**/
if (this.getAccountingType().equals(AccountingRuleType.CASH_BASED.getValue())
|| this.getAccountingType().equals(AccountingRuleType.ACCRUAL_BASED.getValue())) {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue())
.value(this.loanPortfolioAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(this.fundAccountId)
.notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue())
.value(this.interestOnLoanId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(this.incomeFromFeeId)
.notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
.value(this.incomeFromPenaltyId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue())
.value(this.writeOffAccountId).notNull().integerGreaterThanZero();
}// validation for accrual based accounting specific heads
if (this.getAccountingType().equals(AccountingRuleType.ACCRUAL_BASED.getValue())) {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue())
.value(this.receivableInterestAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue())
.value(this.receivableFeeAccountId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue())
.value(this.receivablePenaltyAccountId).notNull().integerGreaterThanZero();
}
}
baseDataValidator.reset().anyOfNotNull(this.fundId, this.transactionProcessingStrategyId, this.name, this.description,
this.currencyCode, this.digitsAfterDecimal, this.principal, this.inArrearsTolerance, this.repaymentFrequencyType,
this.repaymentEvery, this.numberOfRepayments, this.interestRatePerPeriod, this.interestRateFrequencyType,
this.amortizationType, this.interestType, this.interestCalculationPeriodType, this.charges, this.accountingType);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
@@ -6,6 +6,7 @@ import java.util.Collection;
import java.util.List;
import org.joda.time.DateTime;
import org.mifosplatform.accounting.api.data.GLAccountData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
@@ -40,6 +41,7 @@ public class LoanProductData {
private final Integer repaymentEvery;
private final BigDecimal interestRatePerPeriod;
private final BigDecimal annualInterestRate;
private final Integer accountingType;
private final EnumOptionData loanTermFrequencyType;
private final EnumOptionData repaymentFrequencyType;
@@ -64,6 +66,23 @@ public class LoanProductData {
private final List<EnumOptionData> loanTermFrequencyTypeOptions;
private final Collection<ChargeData> chargeOptions;
// accounting related template fields
private final List<GLAccountData> assetAccountOptions;
private final List<GLAccountData> incomeAccountOptions;
private final List<GLAccountData> expenseAccountOptions;
//accounting related mapping feilds
private Long loanPortfolioAccountId;
private Long fundSourceAccountId;
private Long interestOnLoanAccountId;
private Long incomeFromFeeAccountId;
private Long incomeFromPenaltyAccountId;
private Long writeOffAccountId;
// accounting heads used only for accrual based accounting
private Long receivableInterestAccountId;
private Long receivableFeeAccountId;
private Long receivablePenaltyAccountId;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -90,11 +109,12 @@ public class LoanProductData {
final Long transactionProcessingStrategyId = null;
final String transactionProcessingStrategyName = null;
final Collection<ChargeData> charges = null;
final Integer accountingType = null;
return new LoanProductData(createdOn, lastModifedOn, id, name, description, currency, principal, tolerance, numberOfRepayments,
loanTermFrequency, repaymentEvery, interestRatePerPeriod, annualInterestRate, loanTermFrequencyType,
repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId,
fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges);
fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges, accountingType);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -124,11 +144,12 @@ public class LoanProductData {
final Long transactionProcessingStrategyId = null;
final String transactionProcessingStrategyName = null;
final Collection<ChargeData> charges = null;
final Integer accountingType = 1;
return new LoanProductData(createdOn, lastModifedOn, id, name, description, currency, principal, tolerance, numberOfRepayments,
loanTermFrequency, repaymentEvery, interestRatePerPeriod, annualInterestRate, loanTermFrequencyType,
repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId,
fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges);
fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges, accountingType);
}
public LoanProductData(final DateTime createdOn, final DateTime lastModifedOn, final Long id, final String name,
@@ -138,7 +159,7 @@ public class LoanProductData {
final EnumOptionData repaymentFrequencyType, final EnumOptionData interestRateFrequencyType,
final EnumOptionData amortizationType, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType,
final Long fundId, final String fundName, final Long transactionProcessingStrategyId,
final String transactionProcessingStrategyName, final Collection<ChargeData> charges) {
final String transactionProcessingStrategyName, final Collection<ChargeData> charges, final Integer accountingType) {
this.createdOn = createdOn;
this.lastModifedOn = lastModifedOn;
this.id = id;
@@ -163,6 +184,7 @@ public class LoanProductData {
this.transactionProcessingStrategyId = transactionProcessingStrategyId;
this.transactionProcessingStrategyName = transactionProcessingStrategyName;
this.charges = charges;
this.accountingType = accountingType;
this.chargeOptions = null;
this.currencyOptions = null;
@@ -174,6 +196,10 @@ public class LoanProductData {
this.loanTermFrequencyTypeOptions = null;
this.repaymentFrequencyTypeOptions = null;
this.interestRateFrequencyTypeOptions = null;
this.assetAccountOptions = null;
this.incomeAccountOptions = null;
this.expenseAccountOptions = null;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -181,7 +207,9 @@ public class LoanProductData {
final List<EnumOptionData> interestTypeOptions, final List<EnumOptionData> interestCalculationPeriodTypeOptions,
final List<EnumOptionData> loanTermFrequencyTypeOptions, final List<EnumOptionData> repaymentFrequencyTypeOptions,
final List<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<FundData> fundOptions,
final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions) {
final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions,
final List<GLAccountData> assetAccountOptions, final List<GLAccountData> incomeAccountOptions,
final List<GLAccountData> expenseAccountOptions) {
this.createdOn = productData.createdOn;
this.lastModifedOn = productData.lastModifedOn;
this.id = productData.id;
@@ -205,6 +233,7 @@ public class LoanProductData {
this.transactionProcessingStrategyId = productData.transactionProcessingStrategyId;
this.transactionProcessingStrategyName = productData.transactionProcessingStrategyName;
this.charges = productData.charges();
this.accountingType = productData.accountingType;
this.chargeOptions = chargeOptions;
this.currencyOptions = currencyOptions;
@@ -221,6 +250,10 @@ public class LoanProductData {
this.loanTermFrequencyTypeOptions = loanTermFrequencyTypeOptions;
this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions;
this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
this.assetAccountOptions = assetAccountOptions;
this.incomeAccountOptions = incomeAccountOptions;
this.expenseAccountOptions = expenseAccountOptions;
}
public Collection<ChargeData> charges() {
@@ -362,4 +395,111 @@ public class LoanProductData {
public Collection<ChargeData> getChargeOptions() {
return chargeOptions;
}
public List<GLAccountData> getAssetAccountOptions() {
return this.assetAccountOptions;
}
public List<GLAccountData> getIncomeAccountOptions() {
return this.incomeAccountOptions;
}
public List<GLAccountData> getExpenseAccountOptions() {
return this.expenseAccountOptions;
}
public Long getLoanPortfolioAccountId() {
return this.loanPortfolioAccountId;
}
public void setLoanPortfolioAccountId(Long loanPortfolioAccountId) {
this.loanPortfolioAccountId = loanPortfolioAccountId;
}
public Long getFundSourceAccountId() {
return this.fundSourceAccountId;
}
public void setFundSourceAccountId(Long fundSourceAccountId) {
this.fundSourceAccountId = fundSourceAccountId;
}
public Long getInterestOnLoanAccountId() {
return this.interestOnLoanAccountId;
}
public void setInterestOnLoanAccountId(Long interestOnLoanAccountId) {
this.interestOnLoanAccountId = interestOnLoanAccountId;
}
public Long getIncomeFromFeeAccountId() {
return this.incomeFromFeeAccountId;
}
public void setIncomeFromFeeAccountId(Long incomeFromFeeAccountId) {
this.incomeFromFeeAccountId = incomeFromFeeAccountId;
}
public Long getIncomeFromPenaltyAccountId() {
return this.incomeFromPenaltyAccountId;
}
public void setIncomeFromPenaltyAccountId(Long incomeFromPenaltyAccountId) {
this.incomeFromPenaltyAccountId = incomeFromPenaltyAccountId;
}
public Long getWriteOffAccountId() {
return this.writeOffAccountId;
}
public void setWriteOffAccountId(Long writeOffAccountId) {
this.writeOffAccountId = writeOffAccountId;
}
public Long getReceivableInterestAccountId() {
return this.receivableInterestAccountId;
}
public void setReceivableInterestAccountId(Long receivableInterestAccountId) {
this.receivableInterestAccountId = receivableInterestAccountId;
}
public Long getReceivableFeeAccountId() {
return this.receivableFeeAccountId;
}
public void setReceivableFeeAccountId(Long receivableFeeAccountId) {
this.receivableFeeAccountId = receivableFeeAccountId;
}
public Long getReceivablePenaltyAccountId() {
return this.receivablePenaltyAccountId;
}
public void setReceivablePenaltyAccountId(Long receivablePenaltyAccountId) {
this.receivablePenaltyAccountId = receivablePenaltyAccountId;
}
public Integer getAccountingType() {
return this.accountingType;
}
}
@@ -0,0 +1,37 @@
package org.mifosplatform.portfolio.loanproduct.domain;
import java.util.HashMap;
import java.util.Map;
public enum AccountingRuleType {
NONE(1), CASH_BASED(2), ACCRUAL_BASED(3);
private final Integer value;
private AccountingRuleType(final Integer value) {
this.value = value;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public Integer getValue() {
return this.value;
}
private static final Map<Integer, AccountingRuleType> intToEnumMap = new HashMap<Integer, AccountingRuleType>();
static {
for (AccountingRuleType type : AccountingRuleType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static AccountingRuleType fromInt(int i) {
AccountingRuleType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
}
@@ -83,10 +83,12 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance");
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command
.integerValueOfParameterNamed("accountingType"));
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, interestRatePerPeriod,
interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery,
repaymentFrequencyType, numberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges);
repaymentFrequencyType, numberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType);
}
protected LoanProduct() {
@@ -99,7 +101,8 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Set<Charge> charges) {
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Set<Charge> charges,
final AccountingRuleType accountingRuleType) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -115,7 +118,7 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance, accountingRuleType);
}
public MonetaryCurrency getCurrency() {
@@ -73,7 +73,23 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal inArrearsTolerance;
@Column(name = "accounting_type", nullable = false)
private Integer accountingType;
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance,
final AccountingRuleType accountingRuleType) {
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
numberOfRepayments, amortizationMethod, inArrearsTolerance, accountingRuleType);
}
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -83,7 +99,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
numberOfRepayments, amortizationMethod, inArrearsTolerance);
numberOfRepayments, amortizationMethod, inArrearsTolerance, null);
}
protected LoanProductRelatedDetail() {
@@ -95,7 +111,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) {
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance,
final AccountingRuleType accountingRuleType) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -112,6 +129,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
} else {
this.inArrearsTolerance = inArrearsTolerance;
}
if(accountingRuleType !=null){
this.accountingType = accountingRuleType.getValue();
}
}
public MonetaryCurrency getCurrency() {
@@ -164,6 +184,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
public AmortizationMethod getAmortizationMethod() {
return amortizationMethod;
}
public Integer getAccountingType() {
return this.accountingType;
}
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
@@ -285,6 +310,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
actualChanges.put("locale", localeAsInput);
this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(newValue);
}
final String accountingTypeParamName = "accountingType";
if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingType)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName);
actualChanges.put(accountingTypeParamName, newValue);
this.accountingType = newValue;
}
return actualChanges;
}
@@ -8,6 +8,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer;
@@ -32,7 +33,10 @@ public final class LoanProductCommandFromApiJsonDeserializer extends AbstractFro
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("name", "description", "fundId",
"transactionProcessingStrategyId", "currencyCode", "digitsAfterDecimal", "principal", "inArrearsTolerance",
"interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "repaymentFrequencyType", "interestRateFrequencyType",
"amortizationType", "interestType", "interestCalculationPeriodType", "charges", "locale"));
"amortizationType", "interestType", "interestCalculationPeriodType", "charges", "locale", "accountingType" , LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue()));
private final FromJsonHelper fromApiJsonHelper;
@@ -71,9 +75,24 @@ public final class LoanProductCommandFromApiJsonDeserializer extends AbstractFro
final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
element);
final String[] charges = fromApiJsonHelper.extractArrayNamed("charges", element);
//extract accounting related fields
final Integer accountingType = fromApiJsonHelper.extractIntegerWithLocaleNamed("accountingType",
element);
final Long loanPortfolioAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element);
final Long fundAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element);
final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element);
final Long incomeFromPenaltyId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element);
final Long writeOffAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element);
final Long receivableInterestAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element);
final Long receivableFeeAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element);
final Long receivablePenaltyAccountId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element);
return new LoanProductCommand(name, description, fundId, transactionProcessingStrategyId, currencyCode, digitsAfterDecimal,
principal, inArrearsTolerance, numberOfRepayments, repaymentEvery, interestRatePerPeriod, repaymentFrequencyType,
interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, charges);
}
interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, charges, accountingType,
loanPortfolioAccountId, fundAccountId, incomeFromInterestId, incomeFromFeeId, incomeFromPenaltyId, writeOffAccountId,
receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId);
}
}
@@ -101,7 +101,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,"
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, "
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
+ "lp.created_date as createdon, lp.lastmodified_date as modifiedon, "
+ "lp.created_date as createdon, lp.lastmodified_date as modifiedon, lp.accounting_type as accountingType, "
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol "
+ " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id"
@@ -136,6 +136,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Integer numberOfRepayments = JdbcSupport.getInteger(rs,"numberOfRepayments");
final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery");
final Integer accountingType = JdbcSupport.getInteger(rs, "accountingType");
final Integer loanTermFrequency = repaymentEvery * numberOfRepayments;
final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod");
final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate");
@@ -165,7 +166,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
numberOfRepayments, loanTermFrequency, repaymentEvery, interestRatePerPeriod,
annualInterestRate, loanTermFrequencyType, repaymentFrequencyType,
interestRateFrequencyType, amortizationType, interestType,
interestCalculationPeriodType, fundId, fundName, transactionStrategyId, transactionStrategyName, this.charges);
interestCalculationPeriodType, fundId, fundName, transactionStrategyId, transactionStrategyName, this.charges, accountingType);
}
}
@@ -4,6 +4,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.mifosplatform.accounting.service.ProductToGLAccountMappingWritePlatformService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -40,13 +41,14 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final FundRepository fundRepository;
private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
private final ChargeRepository chargeRepository;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
@Autowired
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final LoanProductCommandFromApiJsonDeserializer fromApiJsonDeserializer, final LoanProductRepository loanProductRepository,
final AprCalculator aprCalculator, final FundRepository fundRepository,
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
final ChargeRepository chargeRepository) {
final ChargeRepository chargeRepository, final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductRepository = loanProductRepository;
@@ -54,6 +56,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.fundRepository = fundRepository;
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
this.chargeRepository = chargeRepository;
this.accountMappingWritePlatformService = accountMappingWritePlatformService;
}
@Transactional
@@ -75,6 +78,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.aprCalculator);
this.loanProductRepository.save(loanproduct);
//save accounting mappings
accountMappingWritePlatformService.createLoanProductToGLAccountMapping(loanproduct.getId(), loanProductCommand);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanproduct.getId()).build();
}
@@ -128,6 +134,12 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final Set<Charge> charges = this.assembleSetOfCharges(command, product.getCurrency().getCode());
product.update(charges);
}
// accounting related changes
if (loanProductCommand.getAccountingType() != null) {
boolean accountingTypeChanged = changes.containsKey("accountingType");
accountMappingWritePlatformService.updateLoanProductToGLAccountMapping(product.getId(), loanProductCommand, accountingTypeChanged);
}
if (!changes.isEmpty()) {
this.loanProductRepository.save(product);
@@ -164,4 +176,5 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
return charges;
}
}
@@ -140,6 +140,6 @@ public class LoanProductRelatedDetailTestHelper {
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance, null);
}
}