From 4d81b78d5dfef0bea1e8d09c7d9a3683a90d8a15 Mon Sep 17 00:00:00 2001 From: Vishwas Babu AJ Date: Thu, 10 Jan 2013 00:58:22 +0530 Subject: [PATCH] accounting posting wip (only loan product mapping) --- ...ch-07-map-loan-products-to-gl-accounts.sql | 21 ++ .../accounting/AccountingConstants.java | 127 +++++++++++ .../ProductToGLAccountMappingCommand.java | 6 + .../data/ProductToGLAccountMappingData.java | 40 ++++ .../domain/ProductToGLAccountMapping.java | 79 +++++++ .../ProductToGLAccountMappingRepository.java | 14 ++ ...ctToGLAccountMappingNotFoundException.java | 16 ++ .../service/GLAccountReadPlatformService.java | 3 + ...ToGLAccountMappingReadPlatformService.java | 9 + ...oGLAccountMappingWritePlatformService.java | 15 ++ .../GLAccountReadPlatformServiceImpl.java | 6 + ...AccountMappingReadPlatformServiceImpl.java | 104 +++++++++ ...ccountMappingWritePlatformServiceImpl.java | 212 ++++++++++++++++++ .../LoanProductRelatedDetailAssembler.java | 2 +- .../api/LoanProductsApiResource.java | 32 ++- .../command/LoanProductCommand.java | 166 +++++++++++++- .../loanproduct/data/LoanProductData.java | 148 +++++++++++- .../domain/AccountingRuleType.java | 37 +++ .../loanproduct/domain/LoanProduct.java | 9 +- .../domain/LoanProductRelatedDetail.java | 36 ++- ...ProductCommandFromApiJsonDeserializer.java | 25 ++- .../LoanProductReadPlatformServiceImpl.java | 5 +- ...WritePlatformServiceJpaRepositoryImpl.java | 15 +- .../LoanProductRelatedDetailTestHelper.java | 2 +- 24 files changed, 1096 insertions(+), 33 deletions(-) create mode 100755 mifosng-db/patches/patch-07-map-loan-products-to-gl-accounts.sql create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/api/commands/ProductToGLAccountMappingCommand.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/ProductToGLAccountMappingData.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMapping.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMappingRepository.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingNotFoundException.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingReadPlatformService.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingReadPlatformServiceImpl.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java diff --git a/mifosng-db/patches/patch-07-map-loan-products-to-gl-accounts.sql b/mifosng-db/patches/patch-07-map-loan-products-to-gl-accounts.sql new file mode 100755 index 000000000..39d0b3aa6 --- /dev/null +++ b/mifosng-db/patches/patch-07-map-loan-products-to-gl-accounts.sql @@ -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; + + + + \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/AccountingConstants.java index 6b4522b5b..7f2c8f87b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/AccountingConstants.java @@ -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 intToEnumMap = new HashMap(); + 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 intToEnumMap = new HashMap(); + 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 intToEnumMap = new HashMap(); + 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; + } + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/commands/ProductToGLAccountMappingCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/commands/ProductToGLAccountMappingCommand.java new file mode 100755 index 000000000..dad49dd5b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/commands/ProductToGLAccountMappingCommand.java @@ -0,0 +1,6 @@ +package org.mifosplatform.accounting.api.commands; + + +public class ProductToGLAccountMappingCommand { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/ProductToGLAccountMappingData.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/ProductToGLAccountMappingData.java new file mode 100755 index 000000000..ef216fd53 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/api/data/ProductToGLAccountMappingData.java @@ -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; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMapping.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMapping.java new file mode 100755 index 000000000..6fe765141 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMapping.java @@ -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 { + + @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; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMappingRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMappingRepository.java new file mode 100755 index 000000000..f747e98e6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/domain/ProductToGLAccountMappingRepository.java @@ -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, + JpaSpecificationExecutor { + + ProductToGLAccountMapping findByProductIdAndProductTypeAndFinancialAccountType(Long productId, int productType, int financialAccountType); + + List findByProductIdAndProductType(Long productId, int productType); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingNotFoundException.java new file mode 100755 index 000000000..8ca7fcabc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/exceptions/ProductToGLAccountMappingNotFoundException.java @@ -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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLAccountReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLAccountReadPlatformService.java index eb450020a..7730b3490 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLAccountReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/GLAccountReadPlatformService.java @@ -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 retrieveAllEnabledDetailGLAccounts(AccountingConstants.GL_ACCOUNT_CLASSIFICATION classification); + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingReadPlatformService.java new file mode 100755 index 000000000..5501a034a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingReadPlatformService.java @@ -0,0 +1,9 @@ +package org.mifosplatform.accounting.service; + +import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; + +public interface ProductToGLAccountMappingReadPlatformService { + + LoanProductData fetchAccountMappingDetailsForLoanProduct(LoanProductData loanProductData); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java new file mode 100755 index 000000000..37d591c54 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/ProductToGLAccountMappingWritePlatformService.java @@ -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 updateLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command, boolean accountingRuleChanged); + + void deleteLoanProductToGLAccountMapping(Long loanProductId); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLAccountReadPlatformServiceImpl.java index 7b2b797f5..116a6336b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/GLAccountReadPlatformServiceImpl.java @@ -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 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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingReadPlatformServiceImpl.java new file mode 100755 index 000000000..2ffd882ee --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -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 { + + 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 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; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java new file mode 100755 index 000000000..f49946c84 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/service/impl/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -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 updateLoanProductToGLAccountMapping(Long loanProductId, LoanProductCommand command, + boolean accountingRuleChanged) { + Map changes = new HashMap(); + 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 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 productToGLAccountMappings = accountMappingRepository.findByProductIdAndProductType(loanProductId, + PORTFOLIO_PRODUCT_TYPE.LOAN.getValue()); + if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) { + accountMappingRepository.deleteInBatch(productToGLAccountMappings); + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java index 6ba2b9909..f84b80acc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java @@ -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); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index 668a65718..798f5aef8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -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 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 fundOptions = this.fundReadPlatformService.retrieveAllFunds(); final Collection transactionProcessingStrategyOptions = this.dropdownReadPlatformService .retreiveTransactionProcessingStrategies(); + + final List assetAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION.ASSET); + final List incomeAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GL_ACCOUNT_CLASSIFICATION.INCOME);; + final List 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); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/command/LoanProductCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/command/LoanProductCommand.java index f97598401..2da6efe30 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/command/LoanProductCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/command/LoanProductCommand.java @@ -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 dataValidationErrors = new ArrayList(); @@ -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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index a0ab7ed75..04875308b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -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 loanTermFrequencyTypeOptions; private final Collection chargeOptions; + // accounting related template fields + private final List assetAccountOptions; + private final List incomeAccountOptions; + private final List 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 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 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 charges) { + final String transactionProcessingStrategyName, final Collection 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 chargeOptions, @@ -181,7 +207,9 @@ public class LoanProductData { final List interestTypeOptions, final List interestCalculationPeriodTypeOptions, final List loanTermFrequencyTypeOptions, final List repaymentFrequencyTypeOptions, final List interestRateFrequencyTypeOptions, final Collection fundOptions, - final Collection transactionProcessingStrategyOptions) { + final Collection transactionProcessingStrategyOptions, + final List assetAccountOptions, final List incomeAccountOptions, + final List 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 charges() { @@ -362,4 +395,111 @@ public class LoanProductData { public Collection getChargeOptions() { return chargeOptions; } + + public List getAssetAccountOptions() { + return this.assetAccountOptions; + } + + public List getIncomeAccountOptions() { + return this.incomeAccountOptions; + } + + public List 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; + } + + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java new file mode 100755 index 000000000..3490b58f6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java @@ -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 intToEnumMap = new HashMap(); + 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; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 7e411f004..4ae641c43 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -83,10 +83,12 @@ public class LoanProduct extends AbstractAuditableCustom { 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 { 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 charges) { + final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Set charges, + final AccountingRuleType accountingRuleType) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -115,7 +118,7 @@ public class LoanProduct extends AbstractAuditableCustom { this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, - repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance); + repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance, accountingRuleType); } public MonetaryCurrency getCurrency() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 7edc826ac..0d48abf90 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -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 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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java index bdc86ee34..1919ac1c4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java @@ -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 supportedParameters = new HashSet(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); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 10b7c9f93..0c54bc210 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -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); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index b83596a4f..c8ccfdda8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -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 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; } + } \ No newline at end of file diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java index 78c927389..03392f210 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java @@ -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); } } \ No newline at end of file