diff --git a/mifosng-db/migrations/core_db/V43__accounting-for-savings.sql b/mifosng-db/migrations/core_db/V43__accounting-for-savings.sql new file mode 100755 index 000000000..10cdcc046 --- /dev/null +++ b/mifosng-db/migrations/core_db/V43__accounting-for-savings.sql @@ -0,0 +1,12 @@ +/*add accounting type field to savings product*/ +ALTER TABLE `m_savings_product` + ADD COLUMN `accounting_type` SMALLINT(5) NOT NULL AFTER `lockin_period_frequency_enum`; + +/*update existing savings products to have "No" accounting*/ +update m_savings_product set accounting_type=1; + +/*track payment details for savings transactions*/ +ALTER TABLE `m_savings_account_transaction` +ADD COLUMN `payment_detail_id` BIGINT(20) NULL DEFAULT NULL AFTER `savings_account_id`, +ADD CONSTRAINT `FK_m_savings_account_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`); + diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java index 6aacb3cb0..6fc68c29c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java @@ -103,6 +103,63 @@ public class AccountingConstants { } } + /*** Accounting placeholders for cash based accounting for loan products ***/ + public static enum CASH_ACCOUNTS_FOR_SAVINGS { + SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4); + + private final Integer value; + + private CASH_ACCOUNTS_FOR_SAVINGS(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 (final CASH_ACCOUNTS_FOR_SAVINGS type : CASH_ACCOUNTS_FOR_SAVINGS.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static CASH_ACCOUNTS_FOR_SAVINGS fromInt(final int i) { + final CASH_ACCOUNTS_FOR_SAVINGS type = intToEnumMap.get(Integer.valueOf(i)); + return type; + } + } + + /*** + * Enum of all accounting related input parameter names used while + * creating/updating a savings product + ***/ + public static enum SAVINGS_PRODUCT_ACCOUNTING_PARAMS { + SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES( + "incomeFromFeeAccountId"), INTEREST_ON_SAVINGS("interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( + "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FUND_SOURCE("fundSourceAccountId"); + + private final String value; + + private SAVINGS_PRODUCT_ACCOUNTING_PARAMS(final String value) { + this.value = value; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public String getValue() { + return this.value; + } + } + public static final String ASSESTS_TAG_OPTION_CODE_NAME = "AssetAccountTags"; public static final String LIABILITIES_TAG_OPTION_CODE_NAME = "LiabilityAccountTags"; public static final String EQUITY_TAG_OPTION_CODE_NAME = "EquityAccountTags"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java index 0111f5368..06eae08f0 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformService.java @@ -6,7 +6,9 @@ package org.mifosplatform.accounting.common; import java.util.List; +import java.util.Map; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; public interface AccountingDropdownReadPlatformService { @@ -16,4 +18,11 @@ public interface AccountingDropdownReadPlatformService { public List retrieveGLAccountUsageOptions(); public List retrieveJournalEntryTypeOptions(); + + public List retrieveAccountingRuleTypeOptions(); + + public Map> retrieveAccountMappingOptionsForLoanProducts(); + + public Map> retrieveAccountMappingOptionsForSavingsProducts(); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java index 25c8109d5..2b73027ea 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingDropdownReadPlatformServiceImpl.java @@ -5,17 +5,29 @@ */ package org.mifosplatform.accounting.common; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.accounting.glaccount.domain.GLAccountType; import org.mifosplatform.accounting.glaccount.domain.GLAccountUsage; +import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService; import org.mifosplatform.accounting.journalentry.domain.JournalEntryType; import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountingDropdownReadPlatformServiceImpl implements AccountingDropdownReadPlatformService { + private final GLAccountReadPlatformService accountReadPlatformService; + + @Autowired + public AccountingDropdownReadPlatformServiceImpl(final GLAccountReadPlatformService accountReadPlatformService) { + this.accountReadPlatformService = accountReadPlatformService; + } + @Override public List retrieveGLAccountTypeOptions() { return AccountingEnumerations.gLAccountType(GLAccountType.values()); @@ -31,4 +43,44 @@ public class AccountingDropdownReadPlatformServiceImpl implements AccountingDrop return AccountingEnumerations.journalEntryTypes(JournalEntryType.values()); } + @Override + public List retrieveAccountingRuleTypeOptions() { + return AccountingEnumerations.accountingRuleTypes(AccountingRuleType.values()); + } + + @Override + public Map> retrieveAccountMappingOptionsForLoanProducts() { + Map> accountOptions = new HashMap>(); + List assetAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET); + if (assetAccountOptions.isEmpty()) { + assetAccountOptions = null; + } + accountOptions.put("assetAccountOptions", assetAccountOptions); + + List incomeAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.INCOME); + if (incomeAccountOptions.isEmpty()) { + incomeAccountOptions = null; + } + accountOptions.put("incomeAccountOptions", incomeAccountOptions); + + List expenseAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE); + if (expenseAccountOptions.isEmpty()) { + expenseAccountOptions = null; + } + accountOptions.put("expenseAccountOptions", expenseAccountOptions); + return accountOptions; + } + + @Override + public Map> retrieveAccountMappingOptionsForSavingsProducts() { + Map> accountOptions = retrieveAccountMappingOptionsForLoanProducts(); + List liabilityAccountOptions = accountReadPlatformService + .retrieveAllEnabledDetailGLAccounts(GLAccountType.LIABILITY); + if (liabilityAccountOptions.isEmpty()) { + liabilityAccountOptions = null; + } + accountOptions.put("liabilityAccountOptions", liabilityAccountOptions); + return accountOptions; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingEnumerations.java index 748a8cb41..7a7f09bba 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingEnumerations.java @@ -80,4 +80,21 @@ public class AccountingEnumerations { return optionData; } + public static EnumOptionData accountingRuleType(final int id) { + return accountingRuleType(AccountingRuleType.fromInt(id)); + } + + public static EnumOptionData accountingRuleType(final AccountingRuleType type) { + EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString()); + return optionData; + } + + public static List accountingRuleTypes(final AccountingRuleType[] accountingRuleTypes) { + final List optionDatas = new ArrayList(); + for (final AccountingRuleType accountingRuleType : accountingRuleTypes) { + optionDatas.add(accountingRuleType(accountingRuleType)); + } + return optionDatas; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingRuleType.java similarity index 95% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java rename to mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingRuleType.java index 19adb1079..cf961a690 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AccountingRuleType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingRuleType.java @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -package org.mifosplatform.portfolio.loanproduct.domain; +package org.mifosplatform.accounting.common; import java.util.HashMap; import java.util.Map; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccount.java index 106c6c56c..22b3959a2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccount.java @@ -30,10 +30,10 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "acc_gl_account", uniqueConstraints = { @UniqueConstraint(columnNames = { "gl_code" }, name = "acc_gl_code") }) public class GLAccount extends AbstractPersistable { - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private GLAccount parent; - + @Column(name = "hierarchy", nullable = true, length = 50) private String hierarchy; @@ -61,8 +61,8 @@ public class GLAccount extends AbstractPersistable { @Column(name = "description", nullable = true, length = 500) private String description; - - @ManyToOne(fetch = FetchType.LAZY) + + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tag_id") private CodeValue tagId; @@ -105,7 +105,8 @@ public class GLAccount extends AbstractPersistable { handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.PARENT_ID.getValue(), 0L); handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TYPE.getValue(), this.type, true); handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.USAGE.getValue(), this.usage, true); - handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TAGID.getValue(), this.tagId==null?0L:this.tagId.getId()); + handlePropertyUpdate(command, actualChanges, GLAccountJsonInputParams.TAGID.getValue(), + this.tagId == null ? 0L : this.tagId.getId()); return actualChanges; } @@ -207,7 +208,7 @@ public class GLAccount extends AbstractPersistable { public Integer getType() { return this.type; } - + public void generateHierarchy() { if (parent != null) { @@ -216,21 +217,21 @@ public class GLAccount extends AbstractPersistable { this.hierarchy = "."; } } - + private String hierarchyOf(final Long id) { return this.hierarchy + id.toString() + "."; } - public boolean isDetailAccount() { - return GLAccountUsage.DETAIL.getValue().equals(this.usage); - } + public boolean isDetailAccount() { + return GLAccountUsage.DETAIL.getValue().equals(this.usage); + } - public void updateTagId(CodeValue tagID) { - this.tagId = tagID; - } + public void updateTagId(CodeValue tagID) { + this.tagId = tagID; + } - public void updateParentAccount(GLAccount parentAccount) { - this.parent=parentAccount; - this.generateHierarchy(); - } + public void updateParentAccount(GLAccount parentAccount) { + this.parent = parentAccount; + this.generateHierarchy(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccountType.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccountType.java index 07dbe5258..62e13fc6c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccountType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/domain/GLAccountType.java @@ -66,24 +66,24 @@ public enum GLAccountType { return name().toString(); } - public boolean isAssetType() { - return this.value.equals(GLAccountType.ASSET.getValue()); - } + public boolean isAssetType() { + return this.value.equals(GLAccountType.ASSET.getValue()); + } - public boolean isLiabilityType() { - return this.value.equals(GLAccountType.LIABILITY.getValue()); - } + public boolean isLiabilityType() { + return this.value.equals(GLAccountType.LIABILITY.getValue()); + } - public boolean isEquityType() { - return this.value.equals(GLAccountType.EQUITY.getValue()); - } + public boolean isEquityType() { + return this.value.equals(GLAccountType.EQUITY.getValue()); + } - public boolean isIncomeType() { - return this.value.equals(GLAccountType.INCOME.getValue()); - } + public boolean isIncomeType() { + return this.value.equals(GLAccountType.INCOME.getValue()); + } - public boolean isExpenseType() { - return this.value.equals(GLAccountType.EXPENSE.getValue()); - } + public boolean isExpenseType() { + return this.value.equals(GLAccountType.EXPENSE.getValue()); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsDTO.java new file mode 100755 index 000000000..b6c399d3f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsDTO.java @@ -0,0 +1,77 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.data; + +import java.util.List; + +public class SavingsDTO { + + private Long savingsId; + private Long savingsProductId; + private Long officeId; + private boolean cashBasedAccountingEnabled; + private boolean accrualBasedAccountingEnabled; + private List newSavingsTransactions; + + public SavingsDTO(final Long savingsId, final Long savingsProductId, final Long officeId, final boolean cashBasedAccountingEnabled, + final boolean accrualBasedAccountingEnabled, final List newSavingsTransactions) { + this.savingsId = savingsId; + this.savingsProductId = savingsProductId; + this.officeId = officeId; + this.cashBasedAccountingEnabled = cashBasedAccountingEnabled; + this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled; + this.newSavingsTransactions = newSavingsTransactions; + } + + public Long getSavingsId() { + return this.savingsId; + } + + public void setSavingsId(Long savingsId) { + this.savingsId = savingsId; + } + + public Long getSavingsProductId() { + return this.savingsProductId; + } + + public void setSavingsProductId(Long savingsProductId) { + this.savingsProductId = savingsProductId; + } + + public Long getOfficeId() { + return this.officeId; + } + + public void setOfficeId(Long officeId) { + this.officeId = officeId; + } + + public boolean isCashBasedAccountingEnabled() { + return this.cashBasedAccountingEnabled; + } + + public void setCashBasedAccountingEnabled(boolean cashBasedAccountingEnabled) { + this.cashBasedAccountingEnabled = cashBasedAccountingEnabled; + } + + public boolean isAccrualBasedAccountingEnabled() { + return this.accrualBasedAccountingEnabled; + } + + public void setAccrualBasedAccountingEnabled(boolean accrualBasedAccountingEnabled) { + this.accrualBasedAccountingEnabled = accrualBasedAccountingEnabled; + } + + public List getNewSavingsTransactions() { + return this.newSavingsTransactions; + } + + public void setNewSavingsTransactions(List newSavingsTransactions) { + this.newSavingsTransactions = newSavingsTransactions; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java new file mode 100755 index 000000000..b6fbf420c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/data/SavingsTransactionDTO.java @@ -0,0 +1,59 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.data; + +import java.math.BigDecimal; +import java.util.Date; + +import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; + +public class SavingsTransactionDTO { + + private final String transactionId; + private final Date transactionDate; + private final Long paymentTypeId; + private final SavingsAccountTransactionEnumData transactionType; + + private final BigDecimal amount; + + /*** Boolean values determines if the transaction is reversed ***/ + private final boolean reversed; + + public SavingsTransactionDTO(final Long paymentTypeId, final String transactionId, final Date transactionDate, + final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed) { + this.paymentTypeId = paymentTypeId; + this.transactionId = transactionId; + this.transactionDate = transactionDate; + this.amount = amount; + this.reversed = reversed; + this.transactionType = transactionType; + } + + public String getTransactionId() { + return this.transactionId; + } + + public Date getTransactionDate() { + return this.transactionDate; + } + + public Long getPaymentTypeId() { + return this.paymentTypeId; + } + + public SavingsAccountTransactionEnumData getTransactionType() { + return this.transactionType; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public boolean isReversed() { + return this.reversed; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavings.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavings.java new file mode 100755 index 000000000..aa02ee271 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavings.java @@ -0,0 +1,14 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.service; + +import org.mifosplatform.accounting.journalentry.data.SavingsDTO; + +public interface AccountingProcessorForSavings { + + void createJournalEntriesForSavings(SavingsDTO savingsDTO); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java new file mode 100755 index 000000000..19f157344 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java @@ -0,0 +1,40 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.service; + +import org.mifosplatform.accounting.journalentry.data.SavingsDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class AccountingProcessorForSavingsFactory { + + private ApplicationContext applicationContext; + + @Autowired + public AccountingProcessorForSavingsFactory(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /*** + * Looks like overkill for now, but wanted to keep the savings side of + * accounting identical to that of Loans (would we need an Accrual based + * accounting in the future?) + ***/ + public AccountingProcessorForSavings determineProcessor(final SavingsDTO savingsDTO) { + + AccountingProcessorForSavings accountingProcessorForSavings = null; + + if (savingsDTO.isCashBasedAccountingEnabled()) { + accountingProcessorForSavings = applicationContext.getBean("cashBasedAccountingProcessorForSavings", + AccountingProcessorForSavings.class); + } + + return accountingProcessorForSavings; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java index ef28e4f47..dc23811a0 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/AccountingProcessorHelper.java @@ -16,9 +16,12 @@ import org.mifosplatform.accounting.closure.domain.GLClosure; import org.mifosplatform.accounting.closure.domain.GLClosureRepository; import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.journalentry.data.LoanDTO; import org.mifosplatform.accounting.journalentry.data.LoanTransactionDTO; +import org.mifosplatform.accounting.journalentry.data.SavingsDTO; +import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO; import org.mifosplatform.accounting.journalentry.domain.JournalEntry; import org.mifosplatform.accounting.journalentry.domain.JournalEntryRepository; import org.mifosplatform.accounting.journalentry.domain.JournalEntryType; @@ -30,6 +33,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAc import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; +import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -83,6 +87,35 @@ public class AccountingProcessorHelper { return new LoanDTO(loanId, loanProductId, officeId, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, newLoanTransactions); } + public SavingsDTO populateSavingsDtoFromMap(final Map accountingBridgeData, final boolean cashBasedAccountingEnabled, + final boolean accrualBasedAccountingEnabled) { + final Long loanId = (Long) accountingBridgeData.get("savingsId"); + final Long loanProductId = (Long) accountingBridgeData.get("savingsProductId"); + final Long officeId = (Long) accountingBridgeData.get("officeId"); + final List newSavingsTransactions = new ArrayList(); + + @SuppressWarnings("unchecked") + final List> newTransactionsMap = (List>) accountingBridgeData.get("newSavingsTransactions"); + + for (final Map map : newTransactionsMap) { + final String transactionId = ((Long) map.get("id")).toString(); + final Date transactionDate = ((LocalDate) map.get("date")).toDate(); + final SavingsAccountTransactionEnumData transactionType = (SavingsAccountTransactionEnumData) map.get("type"); + final BigDecimal amount = (BigDecimal) map.get("amount"); + final boolean reversed = (Boolean) map.get("reversed"); + final Long paymentTypeId = (Long) map.get("paymentTypeId"); + + final SavingsTransactionDTO transaction = new SavingsTransactionDTO(paymentTypeId, transactionId, transactionDate, + transactionType, amount, reversed); + + newSavingsTransactions.add(transaction); + + } + + return new SavingsDTO(loanId, loanProductId, officeId, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, + newSavingsTransactions); + } + /** * Convenience method that creates a pair of related Debits and Credits for * Accrual Based accounting. @@ -118,6 +151,41 @@ public class AccountingProcessorHelper { transactionId, transactionDate, amount); } + /** + * Convenience method that creates a pair of related Debits and Credits for + * Cash Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * @param paymentTypeId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createCashBasedJournalEntriesAndReversalsForSavings(final Office office, + final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeDebited, final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeCredited, + final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId, + final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + int accountTypeToDebitId = accountTypeToBeDebited.getValue(); + int accountTypeToCreditId = accountTypeToBeCredited.getValue(); + // reverse debits and credits for reversals + if (isReversal) { + accountTypeToDebitId = accountTypeToBeCredited.getValue(); + accountTypeToCreditId = accountTypeToBeDebited.getValue(); + } + createJournalEntriesForSavings(office, accountTypeToDebitId, accountTypeToCreditId, savingsProductId, paymentTypeId, loanId, + transactionId, transactionDate, amount); + } + /** * Convenience method that creates a pair of related Debits and Credits for * Cash Based accounting. @@ -216,6 +284,15 @@ public class AccountingProcessorHelper { createCreditJournalEntryForLoan(office, creditAccount, loanId, transactionId, transactionDate, amount); } + private void createJournalEntriesForSavings(final Office office, final int accountTypeToDebitId, final int accountTypeToCreditId, + final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, + final Date transactionDate, final BigDecimal amount) { + GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToDebitId, paymentTypeId); + GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToCreditId, paymentTypeId); + createDebitJournalEntryForSavings(office, debitAccount, savingsId, transactionId, transactionDate, amount); + createCreditJournalEntryForSavings(office, creditAccount, savingsId, transactionId, transactionDate, amount); + } + private void createDebitJournalEntryOrReversalForLoan(final Office office, final int accountMappingTypeId, final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { @@ -246,6 +323,14 @@ public class AccountingProcessorHelper { this.glJournalEntryRepository.saveAndFlush(journalEntry); } + private void createCreditJournalEntryForSavings(final Office office, final GLAccount account, final Long savingsId, + final String transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, + JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + private void createDebitJournalEntryForLoan(final Office office, final GLAccount account, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) { final boolean manualEntry = false; @@ -254,6 +339,14 @@ public class AccountingProcessorHelper { this.glJournalEntryRepository.saveAndFlush(journalEntry); } + private void createDebitJournalEntryForSavings(final Office office, final GLAccount account, final Long savingsId, + final String transactionId, final Date transactionDate, final BigDecimal amount) { + final boolean manualEntry = false; + final JournalEntry journalEntry = JournalEntry.createNew(office, account, transactionId, manualEntry, transactionDate, + JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null); + this.glJournalEntryRepository.saveAndFlush(journalEntry); + } + private GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId, final Long paymentTypeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId, PortfolioProductType.LOAN.getValue(), accountMappingTypeId); @@ -272,4 +365,24 @@ public class AccountingProcessorHelper { } return accountMapping.getGlAccount(); } + + private GLAccount getLinkedGLAccountForSavingsProduct(final Long savingsProductId, final int accountMappingTypeId, + final Long paymentTypeId) { + ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId); + /**** + * Get more specific mapping for FUND source accounts (based on payment + * channels). Note that fund source placeholder ID would be same for + * both cash and accrual accounts + ***/ + if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()) { + ProductToGLAccountMapping paymentChannelSpecificAccountMapping = this.accountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(savingsProductId, + PortfolioProductType.SAVING.getValue(), accountMappingTypeId, paymentTypeId); + if (paymentChannelSpecificAccountMapping != null) { + accountMapping = paymentChannelSpecificAccountMapping; + } + } + return accountMapping.getGlAccount(); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java new file mode 100755 index 000000000..676a7035e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java @@ -0,0 +1,77 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.accounting.journalentry.service; + +import java.math.BigDecimal; +import java.util.Date; + +import org.mifosplatform.accounting.closure.domain.GLClosure; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; +import org.mifosplatform.accounting.journalentry.data.SavingsDTO; +import org.mifosplatform.accounting.journalentry.data.SavingsTransactionDTO; +import org.mifosplatform.organisation.office.domain.Office; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class CashBasedAccountingProcessorForSavings implements AccountingProcessorForSavings { + + private final AccountingProcessorHelper helper; + + @Autowired + public CashBasedAccountingProcessorForSavings(final AccountingProcessorHelper accountingProcessorHelper) { + this.helper = accountingProcessorHelper; + } + + @Override + public void createJournalEntriesForSavings(SavingsDTO savingsDTO) { + final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId()); + final Office office = this.helper.getOfficeById(savingsDTO.getOfficeId()); + final Long savingsProductId = savingsDTO.getSavingsProductId(); + final Long savingsId = savingsDTO.getSavingsId(); + for (final SavingsTransactionDTO savingsTransactionDTO : savingsDTO.getNewSavingsTransactions()) { + final Date transactionDate = savingsTransactionDTO.getTransactionDate(); + final String transactionId = savingsTransactionDTO.getTransactionId(); + final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId(); + final boolean isReversal = savingsTransactionDTO.isReversed(); + final BigDecimal amount = savingsTransactionDTO.getAmount(); + + helper.checkForBranchClosures(latestGLClosure, transactionDate); + + /** Handle Deposits and reversals of deposits **/ + if (savingsTransactionDTO.getTransactionType().isDeposit()) { + helper.createCashBasedJournalEntriesAndReversalsForSavings(office, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, savingsProductId, paymentTypeId, savingsId, transactionId, + transactionDate, amount, isReversal); + } + + /** Handle withdrawals and reversals of withdrawals **/ + else if (savingsTransactionDTO.getTransactionType().isWithdrawal()) { + helper.createCashBasedJournalEntriesAndReversalsForSavings(office, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE, savingsProductId, paymentTypeId, savingsId, transactionId, + transactionDate, amount, isReversal); + } + + /** + * Handle Interest Applications and reversals of Interest + * Applications + **/ + else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) { + helper.createCashBasedJournalEntriesAndReversalsForSavings(office, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, savingsProductId, paymentTypeId, savingsId, transactionId, + transactionDate, amount, isReversal); + } + + /** Handle Fees Deductions and reversals of Fees Deductions **/ + else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { + helper.createCashBasedJournalEntriesAndReversalsForSavings(office, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES, savingsProductId, paymentTypeId, savingsId, transactionId, + transactionDate, amount, isReversal); + } + + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java index 0f8b9fbf0..ebb3055db 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformService.java @@ -17,4 +17,6 @@ public interface JournalEntryWritePlatformService { CommandProcessingResult revertJournalEntry(JsonCommand command); void createJournalEntriesForLoan(Map accountingBridgeData); + + void createJournalEntriesForSavings(Map accountingBridgeData); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index b4169a565..4f04299cf 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -22,6 +22,7 @@ import org.mifosplatform.accounting.journalentry.api.JournalEntryJsonInputParams import org.mifosplatform.accounting.journalentry.command.JournalEntryCommand; import org.mifosplatform.accounting.journalentry.command.SingleDebitOrCreditEntryCommand; import org.mifosplatform.accounting.journalentry.data.LoanDTO; +import org.mifosplatform.accounting.journalentry.data.SavingsDTO; import org.mifosplatform.accounting.journalentry.domain.JournalEntry; import org.mifosplatform.accounting.journalentry.domain.JournalEntryRepository; import org.mifosplatform.accounting.journalentry.domain.JournalEntryType; @@ -56,6 +57,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa private final JournalEntryRepository glJournalEntryRepository; private final OfficeRepository officeRepository; private final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory; + private final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory; private final AccountingProcessorHelper helper; private final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final AccountingRuleRepository accountingRuleRepository; @@ -65,13 +67,15 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final JournalEntryRepository glJournalEntryRepository, final OfficeRepository officeRepository, final GLAccountRepository glAccountRepository, final JournalEntryCommandFromApiJsonDeserializer fromApiJsonDeserializer, final AccountingProcessorHelper accountingProcessorHelper, final AccountingRuleRepository accountingRuleRepository, - final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory) { + final AccountingProcessorForLoanFactory accountingProcessorForLoanFactory, + final AccountingProcessorForSavingsFactory accountingProcessorForSavingsFactory) { this.glClosureRepository = glClosureRepository; this.officeRepository = officeRepository; this.glJournalEntryRepository = glJournalEntryRepository; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.glAccountRepository = glAccountRepository; this.accountingProcessorForLoanFactory = accountingProcessorForLoanFactory; + this.accountingProcessorForSavingsFactory = accountingProcessorForSavingsFactory; this.helper = accountingProcessorHelper; this.accountingRuleRepository = accountingRuleRepository; } @@ -87,9 +91,9 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final Long officeId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.OFFICE_ID.getValue()); final Office office = this.officeRepository.findOne(officeId); if (office == null) { throw new OfficeNotFoundException(officeId); } - + final Long accountRuleId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue()); - + validateBusinessRulesForJournalEntries(journalEntryCommand); /** Set a transaction Id and save these Journal entries **/ @@ -99,7 +103,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final boolean manualEntry = true; if (accountRuleId != null) { - + final Long accountingRuleId = command.longValueOfParameterNamed(JournalEntryJsonInputParams.ACCOUNTING_RULE.getValue()); final BigDecimal amount = command.bigDecimalValueOfParameterNamed(JournalEntryJsonInputParams.AMOUNT.getValue()); final AccountingRule accountingRule = this.accountingRuleRepository.findOne(accountingRuleId); @@ -112,24 +116,23 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa if (StringUtils.isNotBlank(journalEntryCommand.getComments())) { description = journalEntryCommand.getComments(); } - - JournalEntry creditEntry = JournalEntry.createNew(office, creditAccountHead, transactionId, manualEntry, transactionDate, + + JournalEntry creditEntry = JournalEntry.createNew(office, creditAccountHead, transactionId, manualEntry, transactionDate, JournalEntryType.CREDIT, amount, description, null, null, referenceNumber); this.glJournalEntryRepository.saveAndFlush(creditEntry); - - JournalEntry debitEntry = JournalEntry.createNew(office, debitAccountHead, transactionId, manualEntry, transactionDate, + + JournalEntry debitEntry = JournalEntry.createNew(office, debitAccountHead, transactionId, manualEntry, transactionDate, JournalEntryType.DEBIT, amount, description, null, null, referenceNumber); this.glJournalEntryRepository.saveAndFlush(debitEntry); - - + } else { saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getDebits(), transactionId, JournalEntryType.DEBIT, referenceNumber); - + saveAllDebitOrCreditEntries(journalEntryCommand, office, transactionDate, journalEntryCommand.getCredits(), transactionId, JournalEntryType.CREDIT, referenceNumber); - + } return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withOfficeId(officeId) @@ -146,8 +149,8 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa * disabled **/ if (creditOrDebitAccountHead.isDisabled()) { - throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_DISABLED, null, creditOrDebitAccountHead.getName(), - creditOrDebitAccountHead.getGlCode()); + throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_DISABLED, null, + creditOrDebitAccountHead.getName(), creditOrDebitAccountHead.getGlCode()); } else if (!creditOrDebitAccountHead.isManualEntriesAllowed()) { throw new JournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.GL_ACCOUNT_MANUAL_ENTRIES_NOT_PERMITTED, null, creditOrDebitAccountHead.getName(), creditOrDebitAccountHead.getGlCode()); } @@ -204,6 +207,23 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } } + @Transactional + @Override + public void createJournalEntriesForSavings(Map accountingBridgeData) { + + final boolean cashBasedAccountingEnabled = (Boolean) accountingBridgeData.get("cashBasedAccountingEnabled"); + final boolean accrualBasedAccountingEnabled = (Boolean) accountingBridgeData.get("accrualBasedAccountingEnabled"); + + if (cashBasedAccountingEnabled || accrualBasedAccountingEnabled) { + final SavingsDTO savingsDTO = helper.populateSavingsDtoFromMap(accountingBridgeData, cashBasedAccountingEnabled, + accrualBasedAccountingEnabled); + final AccountingProcessorForSavings accountingProcessorForSavings = this.accountingProcessorForSavingsFactory + .determineProcessor(savingsDTO); + accountingProcessorForSavings.createJournalEntriesForSavings(savingsDTO); + } + + } + private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) { /** check if date of Journal entry is valid ***/ final LocalDate entryLocalDate = command.getTransactionDate(); @@ -218,15 +238,15 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } - if(command.getAccountingRuleId() == null) { + if (command.getAccountingRuleId() == null) { /*** check if credits and debits are valid **/ final SingleDebitOrCreditEntryCommand[] credits = command.getCredits(); final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); - + // atleast one debit or credit must be present if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new JournalEntryInvalidException( GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); } - + // sum of all debits must be = sum of all credits BigDecimal creditsSum = BigDecimal.ZERO; BigDecimal debitsSum = BigDecimal.ZERO; @@ -259,7 +279,6 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) { comments = singleDebitOrCreditEntryCommand.getComments(); } - final JournalEntry glJournalEntry = JournalEntry.createNew(office, glAccount, transactionId, manualEntry, transactionDate, type, singleDebitOrCreditEntryCommand.getAmount(), comments, null, null, referenceNumber); this.glJournalEntryRepository.saveAndFlush(glJournalEntry); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index cb708f7e5..620d163d7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -10,7 +10,9 @@ import java.util.List; import java.util.Locale; import org.apache.commons.lang.StringUtils; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.glaccount.domain.GLAccount; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.mifosplatform.infrastructure.core.data.ApiParameterError; @@ -18,10 +20,10 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.*; import com.google.gson.JsonElement; @@ -41,9 +43,9 @@ import com.google.gson.JsonElement; * till we have access to the domain object it would not be possible to detect * if an accounting rule has actually been changed * - * Hence, method {@link #validateForCreate(String)} from this class is called - * separately for validation only if an accounting rule change is detected by - * {@link ProductToGLAccountMappingWritePlatformService} + * Hence, method {@link #validateForLoanProductCreate(String)} from this class + * is called separately for validation only if an accounting rule change is + * detected by {@link ProductToGLAccountMappingWritePlatformService} * * Also, the class is probably named wrong (*FromApiJsonDeserializer) should * probably be named as (*Validator) instead @@ -59,7 +61,7 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { this.fromApiJsonHelper = fromApiJsonHelper; } - public void validateForCreate(final String json) { + public void validateForLoanProductCreate(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final List dataValidationErrors = new ArrayList(); @@ -126,6 +128,47 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validateForSavingsProductCreate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + // accounting related data validation + final Integer accountingRuleType = this.fromApiJsonHelper + .extractIntegerNamed(accountingRuleParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3); + + if (isCashBasedAccounting(accountingRuleType)) { + + final Long savingsControlAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue()) + .value(savingsControlAccountId).notNull().integerGreaterThanZero(); + + final Long savingsReferenceAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue()) + .value(savingsReferenceAccountId).notNull().integerGreaterThanZero(); + + final Long interestOnSavingsAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue()) + .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero(); + + final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) + .notNull().integerGreaterThanZero(); + + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + private boolean isCashBasedAccounting(final Integer accountingRuleType) { return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java new file mode 100755 index 000000000..95f224e3f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -0,0 +1,203 @@ +package org.mifosplatform.accounting.producttoaccountmapping.service; + +import java.util.HashMap; +import java.util.Map; + +import org.mifosplatform.accounting.common.AccountingRuleType; +import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; +import org.mifosplatform.accounting.glaccount.domain.GLAccountType; +import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; +import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +@Component +public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper { + + @Autowired + public LoanProductToGLAccountMappingHelper(GLAccountRepository glAccountRepository, + ProductToGLAccountMappingRepository glAccountMappingRepository, FromJsonHelper fromApiJsonHelper, + CodeValueRepositoryWrapper codeValueRepositoryWrapper) { + super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper); + } + + /*** Set of abstractions for saving Loan Products to GL Account Mappings ***/ + + public void saveLoanToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.LOAN); + } + + public void saveLoanToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.LOAN); + } + + public void saveLoanToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.LOAN); + } + + /*** Set of abstractions for merging Savings Products to GL Account Mappings ***/ + public void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET, + PortfolioProductType.LOAN); + } + + public void mergeLoanToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME, + PortfolioProductType.LOAN); + } + + public void mergeLoanToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE, + PortfolioProductType.LOAN); + } + + /*** Abstractions for payments channel related to loan products ***/ + + public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes) { + savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN); + } + + public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes) { + updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.LOAN); + } + + public Map populateChangesForNewLoanProductToGLAccountMappingCreation(final JsonElement element, + final AccountingRuleType accountingRuleType) { + final Map changes = new HashMap(); + + final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); + final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); + final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); + final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); + final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); + + final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); + final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); + final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); + + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); + break; + case ACCRUAL_BASED: + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId); + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); + break; + } + + return changes; + } + + /** + * Examines and updates each account mapping for given loan product with + * changes passed in from the Json element + * + * @param loanProductId + * @param changes + * @param element + * @param accountingRuleType + */ + public void handleChangesToLoanProductToGLAccountMappings(final Long loanProductId, final Map changes, + final JsonElement element, final AccountingRuleType accountingRuleType) { + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + // asset + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + + // income + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), + CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); + + // expenses + mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), + CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + break; + case ACCRUAL_BASED: + // assets (including receivables) + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(), changes); + + // income + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), + changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), + changes); + mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); + + // expenses + mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), + loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), + ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); + break; + } + } + + public void deleteLoanProductToGLAccountMapping(final Long loanProductId) { + deleteProductToGLAccountMapping(loanProductId, PortfolioProductType.LOAN); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java new file mode 100755 index 000000000..021c5f136 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -0,0 +1,229 @@ +package org.mifosplatform.accounting.producttoaccountmapping.service; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.glaccount.domain.GLAccount; +import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; +import org.mifosplatform.accounting.glaccount.domain.GLAccountType; +import org.mifosplatform.accounting.glaccount.exception.GLAccountNotFoundException; +import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; +import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; +import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException; +import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; +import org.mifosplatform.infrastructure.codes.domain.CodeValue; +import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Component +public class ProductToGLAccountMappingHelper { + + protected final GLAccountRepository accountRepository; + protected final ProductToGLAccountMappingRepository accountMappingRepository; + protected final FromJsonHelper fromApiJsonHelper; + private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; + + @Autowired + public ProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository, + final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper, + final CodeValueRepositoryWrapper codeValueRepositoryWrapper) { + this.accountRepository = glAccountRepository; + this.accountMappingRepository = glAccountMappingRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; + + } + + public void saveProductToAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId, final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) { + final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + + final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, + portfolioProductType.getValue(), placeHolderTypeId); + this.accountMappingRepository.save(accountMapping); + } + + public void mergeProductToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes, + final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) { + final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); + + // get the existing product + if (accountId != null) { + final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId, + portfolioProductType.getValue(), accountTypeId); + if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId, + accountTypeName); } + if (accountMapping.getGlAccount().getId() != accountId) { + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + changes.put(paramName, accountId); + accountMapping.setGlAccount(glAccount); + this.accountMappingRepository.save(accountMapping); + } + } + } + + /** + * Saves the payment type to Fund source mappings for a particular + * product/product type (also populates the changes array if passed in) + * + * @param command + * @param element + * @param productId + * @param changes + */ + public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes, final PortfolioProductType portfolioProductType) { + JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); + if (paymentChannelMappingArray != null) { + if (changes != null) { + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), + command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue())); + } + for (int i = 0; i < paymentChannelMappingArray.size(); i++) { + final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); + final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong(); + final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong(); + savePaymentChannelToFundSourceMapping(productId, paymentTypeId, paymentSpecificFundAccountId, portfolioProductType); + } + } + } + + /** + * @param command + * @param element + * @param productId + * @param changes + */ + public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes, final PortfolioProductType portfolioProductType) { + // find all existing payment Channel to Fund source Mappings + List existingPaymentChannelToFundSourceMappings = accountMappingRepository + .findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue()); + JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); + /** + * Variable stores a map representation of Payment channels (key) and + * their fund sources (value) extracted from the passed in Jsoncommand + **/ + Map inputPaymentChannelFundSourceMap = new HashMap(); + /*** + * Variable stores all payment types which have already been mapped to + * Fund Sources in the system + **/ + Set existingPaymentTypes = new HashSet(); + if (paymentChannelMappingArray != null) { + if (changes != null) { + changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), + command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue())); + } + + for (int i = 0; i < paymentChannelMappingArray.size(); i++) { + final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); + final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong(); + final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong(); + inputPaymentChannelFundSourceMap.put(paymentTypeId, paymentSpecificFundAccountId); + } + + // If input map is empty, delete all existing mappings + if (inputPaymentChannelFundSourceMap.size() == 0) { + accountMappingRepository.deleteInBatch(existingPaymentChannelToFundSourceMappings); + }/** + * Else,
+ * update existing mappings OR
+ * delete old mappings (which re already present, but not passed in + * as a part of Jsoncommand)
+ * Create new mappings for payment types that are passed in as a + * part of the Jsoncommand but not already present + * + **/ + else { + for (ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) { + Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId(); + existingPaymentTypes.add(currentPaymentChannelId); + // update existing mappings (if required) + if (inputPaymentChannelFundSourceMap.containsKey(currentPaymentChannelId)) { + Long newGLAccountId = inputPaymentChannelFundSourceMap.get(currentPaymentChannelId); + if (newGLAccountId != existingPaymentChannelToFundSourceMapping.getGlAccount().getId()) { + final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), + GLAccountType.ASSET, newGLAccountId); + existingPaymentChannelToFundSourceMapping.setGlAccount(glAccount); + accountMappingRepository.save(existingPaymentChannelToFundSourceMapping); + } + }// deleted payment type + else { + accountMappingRepository.delete(existingPaymentChannelToFundSourceMapping); + } + } + // create new mappings + Set incomingPaymentTypes = inputPaymentChannelFundSourceMap.keySet(); + incomingPaymentTypes.removeAll(existingPaymentTypes); + // incomingPaymentTypes now only contains the newly added + // payment Type mappings + for (Long newPaymentType : incomingPaymentTypes) { + Long newGLAccountId = inputPaymentChannelFundSourceMap.get(newPaymentType); + savePaymentChannelToFundSourceMapping(productId, newPaymentType, newGLAccountId, portfolioProductType); + } + } + } + } + + /** + * @param productId + * @param jsonObject + */ + private void savePaymentChannelToFundSourceMapping(final Long productId, final Long paymentTypeId, + final Long paymentTypeSpecificFundAccountId, final PortfolioProductType portfolioProductType) { + CodeValue paymentType = codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + PaymentDetailConstants.paymentTypeCodeName, paymentTypeId); + final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), GLAccountType.ASSET, + paymentTypeSpecificFundAccountId); + final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, + portfolioProductType.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), paymentType); + this.accountMappingRepository.save(accountMapping); + } + + /** + * Fetches account with a particular Id and throws and Exception it is not + * of the expected Account Category ('ASSET','liability' etc) + * + * @param paramName + * @param expectedAccountType + * @param accountId + * @return + */ + public GLAccount getAccountByIdAndType(final String paramName, final GLAccountType expectedAccountType, final Long accountId) { + final GLAccount glAccount = this.accountRepository.findOne(accountId); + if (glAccount == null) { throw new GLAccountNotFoundException(accountId); } + + // validate account is of the expected Type + if (glAccount.getType().intValue() != expectedAccountType.getValue()) { throw new ProductToGLAccountMappingInvalidException( + paramName, glAccount.getName(), accountId, GLAccountType.fromInt(glAccount.getType()).toString(), + expectedAccountType.toString()); } + return glAccount; + } + + public void deleteProductToGLAccountMapping(final Long loanProductId, final PortfolioProductType portfolioProductType) { + final List productToGLAccountMappings = this.accountMappingRepository.findByProductIdAndProductType( + loanProductId, portfolioProductType.getValue()); + if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) { + this.accountMappingRepository.deleteInBatch(productToGLAccountMappings); + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java index 74ced327f..389bf6904 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java @@ -12,7 +12,11 @@ import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGL public interface ProductToGLAccountMappingReadPlatformService { - Map fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType); + public Map fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType); - List fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId); + public List fetchPaymentTypeToFundSourceMappingsForLoanProduct(final Long loanProductId); + + public Map fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType); + + public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(final Long savingsProductId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index e5e59f8e1..6a069ebc2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -14,10 +14,13 @@ import java.util.Map; import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; +import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; -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; @@ -33,11 +36,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product this.jdbcTemplate = new JdbcTemplate(dataSource); } - private static final class LoanProductToGLAccountMappingMapper implements RowMapper> { + private static final class ProductToGLAccountMappingMapper 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," - + " payment_type as paymentTypeId from acc_product_mapping " + " where product_type=1 "; + + " payment_type as paymentTypeId from acc_product_mapping " + " where product_type= ? "; } @Override @@ -67,10 +70,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product final Map accountMappingDetails = new LinkedHashMap(8); - final LoanProductToGLAccountMappingMapper rm = new LoanProductToGLAccountMappingMapper(); + final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null "; - final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId }); + final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] { + PortfolioProductType.LOAN.getValue(), loanProductId }); if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) { @@ -129,15 +133,63 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product return accountMappingDetails; } + @Override + public Map fetchAccountMappingDetailsForSavingsProduct(Long savingsProductId, Integer accountingType) { + final Map accountMappingDetails = new LinkedHashMap(8); + + final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); + final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null "; + + final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, new Object[] { + PortfolioProductType.SAVING.getValue(), savingsProductId }); + + if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) { + + for (final Map productToGLAccountMap : listOfProductToGLAccountMaps) { + + final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); + final CASH_ACCOUNTS_FOR_SAVINGS glAccountForSavings = CASH_ACCOUNTS_FOR_SAVINGS.fromInt(financialAccountType); + + final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); + + if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), glAccountId); + } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), glAccountId); + } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), glAccountId); + } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS)) { + accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), glAccountId); + } + } + } + return accountMappingDetails; + } + @Override public List fetchPaymentTypeToFundSourceMappingsForLoanProduct(Long loanProductId) { List paymentTypeToGLAccountMappers = null; + return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.LOAN, loanProductId, paymentTypeToGLAccountMappers); + } - final LoanProductToGLAccountMappingMapper rm = new LoanProductToGLAccountMappingMapper(); + @Override + public List fetchPaymentTypeToFundSourceMappingsForSavingsProduct(Long savingsProductId) { + List paymentTypeToGLAccountMappers = null; + return fetchPaymentTypeToFundSourceMappings(PortfolioProductType.SAVING, savingsProductId, paymentTypeToGLAccountMappers); + } + + /** + * @param loanProductId + * @param paymentTypeToGLAccountMappers + * @return + */ + private List fetchPaymentTypeToFundSourceMappings(PortfolioProductType portfolioProductType, + Long loanProductId, List paymentTypeToGLAccountMappers) { + final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null"; - final List> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, - new Object[] { loanProductId }); + final List> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, new Object[] { + portfolioProductType.getValue(), loanProductId }); for (final Map productToGLAccountMap : paymentTypeToFundSourceMappingsList) { if (paymentTypeToGLAccountMappers == null) { @@ -150,4 +202,5 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product } return paymentTypeToGLAccountMappers; } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java index 5af18a8eb..45fe3a200 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformService.java @@ -13,8 +13,12 @@ public interface ProductToGLAccountMappingWritePlatformService { void createLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command); + void createSavingProductToGLAccountMapping(Long savingProductId, JsonCommand command); + Map updateLoanProductToGLAccountMapping(Long loanProductId, JsonCommand command, boolean accountingRuleChanged, int accountingRuleTypeId); - void deleteLoanProductToGLAccountMapping(Long loanProductId); + Map updateSavingsProductToGLAccountMapping(Long savingsProductId, JsonCommand command, boolean accountingRuleChanged, + int accountingRuleTypeId); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index aca8c9713..2c6743231 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -6,57 +6,42 @@ package org.mifosplatform.accounting.producttoaccountmapping.service; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; -import org.mifosplatform.accounting.glaccount.domain.GLAccount; -import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; -import org.mifosplatform.accounting.glaccount.domain.GLAccountType; -import org.mifosplatform.accounting.glaccount.exception.GLAccountNotFoundException; -import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; -import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; -import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; -import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingInvalidException; -import org.mifosplatform.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.accounting.producttoaccountmapping.serialization.ProductToGLAccountMappingFromApiJsonDeserializer; -import org.mifosplatform.infrastructure.codes.domain.CodeValue; -import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; -import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.*; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; @Service public class ProductToGLAccountMappingWritePlatformServiceImpl implements ProductToGLAccountMappingWritePlatformService { - private final GLAccountRepository accountRepository; - private final ProductToGLAccountMappingRepository accountMappingRepository; private final FromJsonHelper fromApiJsonHelper; private final ProductToGLAccountMappingFromApiJsonDeserializer deserializer; - private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; + private final LoanProductToGLAccountMappingHelper loanProductToGLAccountMappingHelper; + private final SavingsProductToGLAccountMappingHelper savingsProductToGLAccountMappingHelper; @Autowired - public ProductToGLAccountMappingWritePlatformServiceImpl(final GLAccountRepository glAccountRepository, - final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper, - final ProductToGLAccountMappingFromApiJsonDeserializer deserializer, final CodeValueRepositoryWrapper codeValueRepositoryWrapper) { - this.accountRepository = glAccountRepository; - this.accountMappingRepository = glAccountMappingRepository; + public ProductToGLAccountMappingWritePlatformServiceImpl(final FromJsonHelper fromApiJsonHelper, + final ProductToGLAccountMappingFromApiJsonDeserializer deserializer, + LoanProductToGLAccountMappingHelper loanProductToGLAccountMappingHelper, + SavingsProductToGLAccountMappingHelper savingsProductToGLAccountMappingHelper) { this.fromApiJsonHelper = fromApiJsonHelper; this.deserializer = deserializer; - this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; + this.loanProductToGLAccountMappingHelper = loanProductToGLAccountMappingHelper; + this.savingsProductToGLAccountMappingHelper = savingsProductToGLAccountMappingHelper; } @Override @@ -71,58 +56,115 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc break; case CASH_BASED: // asset - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + loanProductToGLAccountMappingHelper + .saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); // income - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); // expenses - saveLoanToExpenseAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); // advanced accounting mappings - savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); + loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); break; case ACCRUAL_BASED: // assets (including receivables) - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue()); - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue()); - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue()); - saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue()); // income - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue()); - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue()); - saveLoanToIncomeAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue()); // expenses - saveLoanToExpenseAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, + loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue()); // advanced accounting mappings - savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); + loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, null); break; } } + @Override + @Transactional + public void createSavingProductToGLAccountMapping(Long savingProductId, JsonCommand command) { + final JsonElement element = this.fromApiJsonHelper.parse(command.json()); + final Integer accountingRuleTypeId = this.fromApiJsonHelper.extractIntegerNamed(accountingRuleParamName, element, + Locale.getDefault()); + final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId); + + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + // asset + savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue()); + + // income + savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue()); + + // expenses + savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue()); + + // liability + savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), savingProductId, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue()); + + // advanced accounting mappings + savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); + break; + /*** Do not have accrual based accounting for now ***/ + case ACCRUAL_BASED: + break; + } + + } + @Override @Transactional public Map updateLoanProductToGLAccountMapping(final Long loanProductId, final JsonCommand command, @@ -131,7 +173,7 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc * Variable tracks all accounting mapping properties that have been * updated ***/ - final Map changes = new HashMap(); + Map changes = new HashMap(); final JsonElement element = this.fromApiJsonHelper.parse(command.json()); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId); @@ -140,433 +182,49 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc * for the product and recreate a new set of mappings ***/ if (accountingRuleChanged) { - this.deserializer.validateForCreate(command.json()); - handleChangesToAccountingRuleType(loanProductId, command, changes, element, accountingRuleType); - savePaymentChannelToFundSourceMappings(command, element, loanProductId, changes); + this.deserializer.validateForLoanProductCreate(command.json()); + loanProductToGLAccountMappingHelper.deleteLoanProductToGLAccountMapping(loanProductId); + createLoanProductToGLAccountMapping(loanProductId, command); + changes = loanProductToGLAccountMappingHelper.populateChangesForNewLoanProductToGLAccountMappingCreation(element, + accountingRuleType); + loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, loanProductId, changes); }/*** else examine and update individual changes ***/ else { - handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element, accountingRuleType); - updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes); + loanProductToGLAccountMappingHelper.handleChangesToLoanProductToGLAccountMappings(loanProductId, changes, element, + accountingRuleType); + loanProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, loanProductId, changes); } return changes; } @Override - public void deleteLoanProductToGLAccountMapping(final Long loanProductId) { - final List productToGLAccountMappings = this.accountMappingRepository.findByProductIdAndProductType( - loanProductId, PortfolioProductType.LOAN.getValue()); - if (productToGLAccountMappings != null && productToGLAccountMappings.size() > 0) { - this.accountMappingRepository.deleteInBatch(productToGLAccountMappings); - } - } - - /** - * Saves payment type to Fund source mappings (also populates the changes - * array if passed in) - * - * @param command - * @param element - * @param productId - * @param changes - */ - private void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, - Map changes) { - JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); - if (paymentChannelMappingArray != null) { - if (changes != null) { - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), - command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue())); - } - for (int i = 0; i < paymentChannelMappingArray.size(); i++) { - final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); - final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong(); - final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong(); - savePaymentChannelToFundSourceMapping(productId, paymentTypeId, paymentSpecificFundAccountId); - } - } - } - - /** - * - * - * @param command - * @param element - * @param productId - * @param changes - */ - private void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, - Map changes) { - // find all existing payment Channel to Fund source Mappings - List existingPaymentChannelToFundSourceMappings = accountMappingRepository - .findAllPaymentTypeToFundSourceMappings(productId, PortfolioProductType.LOAN.getValue()); - JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); - /** - * Variable stores a map representation of Payment channels (key) and - * their fund sources (value) extracted from the passed in Jsoncommand - **/ - Map inputPaymentChannelFundSourceMap = new HashMap(); + public Map updateSavingsProductToGLAccountMapping(Long savingsProductId, JsonCommand command, + boolean accountingRuleChanged, int accountingRuleTypeId) { /*** - * Variable stores all payment types which hae already been mapped to - * Fund Sources in the system - **/ - Set existingPaymentTypes = new HashSet(); - if (paymentChannelMappingArray != null) { - if (changes != null) { - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), - command.jsonFragment(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue())); - } + * Variable tracks all accounting mapping properties that have been + * updated + ***/ + Map changes = new HashMap(); + final JsonElement element = this.fromApiJsonHelper.parse(command.json()); + final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountingRuleTypeId); - for (int i = 0; i < paymentChannelMappingArray.size(); i++) { - final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); - final Long paymentTypeId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong(); - final Long paymentSpecificFundAccountId = jsonObject.get(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).getAsLong(); - inputPaymentChannelFundSourceMap.put(paymentTypeId, paymentSpecificFundAccountId); - } - - // If input map is empty, delete all existing mappings - if (inputPaymentChannelFundSourceMap.size() == 0) { - accountMappingRepository.deleteInBatch(existingPaymentChannelToFundSourceMappings); - }/** - * Else,
- * update existing mappings OR
- * delete old mappings (which re already present, but not passed in - * as a part of Jsoncommand)
- * Create new mappings for payment types that are passed in as a - * part of the Jsoncommand but not already present - * - **/ - else { - for (ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) { - Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId(); - existingPaymentTypes.add(currentPaymentChannelId); - // update existing mappings (if required) - if (inputPaymentChannelFundSourceMap.containsKey(currentPaymentChannelId)) { - Long newGLAccountId = inputPaymentChannelFundSourceMap.get(currentPaymentChannelId); - if (newGLAccountId != existingPaymentChannelToFundSourceMapping.getGlAccount().getId()) { - final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), - GLAccountType.ASSET, newGLAccountId); - existingPaymentChannelToFundSourceMapping.setGlAccount(glAccount); - accountMappingRepository.save(existingPaymentChannelToFundSourceMapping); - } - }// deleted payment type - else { - accountMappingRepository.delete(existingPaymentChannelToFundSourceMapping); - } - } - // create new mappings - Set incomingPaymentTypes = inputPaymentChannelFundSourceMap.keySet(); - incomingPaymentTypes.removeAll(existingPaymentTypes); - // incomingPaymentTypes now only contains the newly added - // payment Type mappings - for (Long newPaymentType : incomingPaymentTypes) { - Long newGLAccountId = inputPaymentChannelFundSourceMap.get(newPaymentType); - savePaymentChannelToFundSourceMapping(productId, newPaymentType, newGLAccountId); - } - } + /*** + * If the accounting rule has been changed, delete all existing mapping + * for the product and recreate a new set of mappings + ***/ + if (accountingRuleChanged) { + this.deserializer.validateForSavingsProductCreate(command.json()); + savingsProductToGLAccountMappingHelper.deleteSavingsProductToGLAccountMapping(savingsProductId); + createSavingProductToGLAccountMapping(savingsProductId, command); + changes = savingsProductToGLAccountMappingHelper.populateChangesForNewSavingsProductToGLAccountMappingCreation(element, + accountingRuleType); + savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingsProductId, changes); + }/*** else examine and update individual changes ***/ + else { + savingsProductToGLAccountMappingHelper.handleChangesToSavingsProductToGLAccountMappings(savingsProductId, changes, element, + accountingRuleType); + savingsProductToGLAccountMappingHelper.updatePaymentChannelToFundSourceMappings(command, element, savingsProductId, changes); } + return changes; } - - /** - * @param productId - * @param jsonObject - */ - private void savePaymentChannelToFundSourceMapping(final Long productId, final Long paymentTypeId, - final Long paymentTypeSpecificFundAccountId) { - CodeValue paymentType = codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - PaymentDetailConstants.paymentTypeCodeName, paymentTypeId); - final GLAccount glAccount = getAccountByIdAndType(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), GLAccountType.ASSET, - paymentTypeSpecificFundAccountId); - final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, - PortfolioProductType.LOAN.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), paymentType); - this.accountMappingRepository.save(accountMapping); - } - - /** - * Examines and updates each account mapping for given loan product with - * changes passed in from the Json element - * - * @param loanProductId - * @param changes - * @param element - * @param accountingRuleType - */ - private void handleChangesToLoanProductToGLAccountMappings(final Long loanProductId, final Map changes, - final JsonElement element, final AccountingRuleType accountingRuleType) { - switch (accountingRuleType) { - case NONE: - break; - case CASH_BASED: - // asset - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); - - // income - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), changes); - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), changes); - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), - loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), - CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); - - // expenses - mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), - CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); - break; - case ACCRUAL_BASED: - // assets (including receivables) - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), - loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes); - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.toString(), changes); - mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), - loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.toString(), changes); - - // income - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS.toString(), - changes); - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), loanProductId, - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.toString(), - changes); - mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), - loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.toString(), changes); - - // expenses - mergeLoanToExpenseAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.getValue(), - ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF.toString(), changes); - break; - } - } - - /** - * delete all existing mapping for a product and recreates a new set of - * mappings (to be used exclusively when the selected accounting rule has - * changed) - * - * @param loanProductId - * @param command - * @param changes - * @param element - * @param accountingRuleType - */ - private void handleChangesToAccountingRuleType(final Long loanProductId, final JsonCommand command, final Map changes, - final JsonElement element, final AccountingRuleType accountingRuleType) { - deleteLoanProductToGLAccountMapping(loanProductId); - createLoanProductToGLAccountMapping(loanProductId, command); - final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), element); - final Long loanPortfolioAccountId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), element); - final Long incomeFromInterestId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); - final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), - element); - final Long incomeFromPenaltyId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), element); - final Long writeOffAccountId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), element); - - final Long receivableInterestAccountId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), element); - final Long receivableFeeAccountId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), element); - final Long receivablePenaltyAccountId = this.fromApiJsonHelper.extractLongNamed( - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), element); - - switch (accountingRuleType) { - case NONE: - break; - case CASH_BASED: - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); - break; - case ACCRUAL_BASED: - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), fundAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanPortfolioAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), incomeFromInterestId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeeId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltyId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), writeOffAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), receivableInterestAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), receivableFeeAccountId); - changes.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), receivablePenaltyAccountId); - break; - } - } - - /** - * Utility wrapper for - * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param placeHolderTypeId - */ - private void saveLoanToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId, - final int placeHolderTypeId) { - saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET); - } - - /** - * Utility wrapper for - * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param placeHolderTypeId - */ - private void saveLoanToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId, - final int placeHolderTypeId) { - saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME); - } - - /** - * Utility wrapper for - * {@link #saveLoanToAccountMapping(JsonElement, String, Long, int, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param placeHolderTypeId - */ - private void saveLoanToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId, - final int placeHolderTypeId) { - saveLoanToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE); - } - - /** - * @param element - * @param paramName - * @param productId - * @param placeHolderTypeId - * @param expectedAccountType - */ - private void saveLoanToAccountMapping(final JsonElement element, final String paramName, final Long productId, - final int placeHolderTypeId, final GLAccountType expectedAccountType) { - final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); - final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); - - final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, - PortfolioProductType.LOAN.getValue(), placeHolderTypeId); - this.accountMappingRepository.save(accountMapping); - } - - /** - * Utility wrapper for - * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param accountTypeId - * @param accountTypeName - * @param changes - */ - private void mergeLoanToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, - final int accountTypeId, final String accountTypeName, final Map changes) { - mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET); - } - - /** - * Utility wrapper for - * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param accountTypeId - * @param accountTypeName - * @param changes - */ - private void mergeLoanToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, - final int accountTypeId, final String accountTypeName, final Map changes) { - mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME); - } - - /** - * Utility wrapper for - * {@link #mergeLoanToAccountMappingChanges(JsonElement, String, Long, int, String, Map, GLAccountType)} - * - * @param element - * @param paramName - * @param productId - * @param accountTypeId - * @param accountTypeName - * @param changes - */ - private void mergeLoanToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, - final int accountTypeId, final String accountTypeName, final Map changes) { - mergeLoanToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE); - } - - /** - * Method merges changes passed in through JSON element - * - * @param element - * @param paramName - * @param productId - * @param accountTypeId - * @param accountTypeName - * @param changes - * @param expectedAccountType - */ - private void mergeLoanToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, - final int accountTypeId, final String accountTypeName, final Map changes, - final GLAccountType expectedAccountType) { - final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); - - // get the existing product - if (accountId != null) { - final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId, - PortfolioProductType.LOAN.getValue(), accountTypeId); - if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, productId, - accountTypeName); } - if (accountMapping.getGlAccount().getId() != accountId) { - final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); - changes.put(paramName, accountId); - accountMapping.setGlAccount(glAccount); - this.accountMappingRepository.save(accountMapping); - } - } - } - - /** - * Fetches account with a particular Id and throws and Exception it is not - * of the expected Account Category ('ASSET','liability' etc) - * - * @param paramName - * @param expectedAccountType - * @param accountId - * @return - */ - private GLAccount getAccountByIdAndType(final String paramName, final GLAccountType expectedAccountType, final Long accountId) { - final GLAccount glAccount = this.accountRepository.findOne(accountId); - if (glAccount == null) { throw new GLAccountNotFoundException(accountId); } - - // validate account is of the expected Type - if (glAccount.getType().intValue() != expectedAccountType.getValue()) { throw new ProductToGLAccountMappingInvalidException( - paramName, glAccount.getName(), accountId, GLAccountType.fromInt(glAccount.getType()).toString(), - expectedAccountType.toString()); } - return glAccount; - } - } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java new file mode 100755 index 000000000..a5055f201 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -0,0 +1,162 @@ +package org.mifosplatform.accounting.producttoaccountmapping.service; + +import java.util.HashMap; +import java.util.Map; + +import org.mifosplatform.accounting.common.AccountingRuleType; +import org.mifosplatform.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_SAVINGS; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.glaccount.domain.GLAccountRepository; +import org.mifosplatform.accounting.glaccount.domain.GLAccountType; +import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType; +import org.mifosplatform.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +@Component +public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper { + + @Autowired + public SavingsProductToGLAccountMappingHelper(GLAccountRepository glAccountRepository, + ProductToGLAccountMappingRepository glAccountMappingRepository, FromJsonHelper fromApiJsonHelper, + CodeValueRepositoryWrapper codeValueRepositoryWrapper) { + super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, codeValueRepositoryWrapper); + } + + /*** Set of abstractions for saving Saving Products to GL Account Mappings ***/ + + public void saveSavingsToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.SAVING); + } + + public void saveSavingsToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.SAVING); + } + + public void saveSavingsToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.SAVING); + } + + public void saveSavingsToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId, + final int placeHolderTypeId) { + saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.SAVING); + } + + /*** Set of abstractions for merging Savings Products to GL Account Mappings ***/ + + public void mergeSavingsToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET, + PortfolioProductType.SAVING); + } + + public void mergeSavingsToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME, + PortfolioProductType.SAVING); + } + + public void mergeSavingsToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE, + PortfolioProductType.SAVING); + } + + public void mergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, + final int accountTypeId, final String accountTypeName, final Map changes) { + mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, + GLAccountType.LIABILITY, PortfolioProductType.SAVING); + } + + /*** Abstractions for payments channel related to savings products ***/ + + public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes) { + savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING); + } + + public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId, + Map changes) { + updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING); + } + + public Map populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element, + final AccountingRuleType accountingRuleType) { + final Map changes = new HashMap(); + + final Long savingsReferenceId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); + final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), element); + final Long interestOnSavingsId = this.fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); + final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), + element); + + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), savingsControlId); + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), savingsReferenceId); + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId); + changes.put(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), incomeFromFeesId); + break; + case ACCRUAL_BASED: + break; + } + return changes; + } + + /** + * Examines and updates each account mapping for given loan product with + * changes passed in from the Json element + * + * @param savingsProductId + * @param changes + * @param element + * @param accountingRuleType + */ + public void handleChangesToSavingsProductToGLAccountMappings(final Long savingsProductId, final Map changes, + final JsonElement element, final AccountingRuleType accountingRuleType) { + switch (accountingRuleType) { + case NONE: + break; + case CASH_BASED: + // asset + mergeSavingsToAssetAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_REFERENCE.toString(), changes); + + // income + mergeSavingsToIncomeAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.toString(), changes); + + // expenses + mergeSavingsToExpenseAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.INTEREST_ON_SAVINGS.toString(), changes); + + // liability + mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), + savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.toString(), changes); + break; + case ACCRUAL_BASED: + break; + } + } + + public void deleteSavingsProductToGLAccountMapping(final Long savingsProductId) { + deleteProductToGLAccountMapping(savingsProductId, PortfolioProductType.SAVING); + } +} 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 dec4f9151..32f0b7e45 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 @@ -24,9 +24,8 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; +import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService; import org.mifosplatform.accounting.glaccount.data.GLAccountData; -import org.mifosplatform.accounting.glaccount.domain.GLAccountType; -import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService; import org.mifosplatform.commands.domain.CommandWrapper; @@ -81,9 +80,9 @@ public class LoanProductsApiResource { private final ApiRequestParameterHelper apiRequestParameterHelper; private final LoanDropdownReadPlatformService dropdownReadPlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final GLAccountReadPlatformService accountReadPlatformService; private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService; private final CodeValueReadPlatformService codeValueReadPlatformService; + private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; @Autowired public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService, @@ -92,9 +91,9 @@ public class LoanProductsApiResource { final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final GLAccountReadPlatformService accountReadPlatformService, final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService, - final CodeValueReadPlatformService codeValueReadPlatformService) { + final CodeValueReadPlatformService codeValueReadPlatformService, + final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { this.context = context; this.loanProductReadPlatformService = readPlatformService; this.chargeReadPlatformService = chargeReadPlatformService; @@ -104,9 +103,9 @@ public class LoanProductsApiResource { this.toApiJsonSerializer = toApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; - this.accountReadPlatformService = accountReadPlatformService; this.accountMappingReadPlatformService = accountMappingReadPlatformService; this.codeValueReadPlatformService = codeValueReadPlatformService; + this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; } @POST @@ -218,26 +217,10 @@ public class LoanProductsApiResource { final Collection transactionProcessingStrategyOptions = this.dropdownReadPlatformService .retreiveTransactionProcessingStrategies(); - Map> accountOptions = new HashMap>(); - List assetAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.ASSET); - if (assetAccountOptions.isEmpty()) { - assetAccountOptions = null; - } - accountOptions.put("assetAccountOptions", assetAccountOptions); + Map> accountOptions = accountingDropdownReadPlatformService + .retrieveAccountMappingOptionsForLoanProducts(); - List incomeAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.INCOME); - if (incomeAccountOptions.isEmpty()) { - incomeAccountOptions = null; - } - accountOptions.put("incomeAccountOptions", incomeAccountOptions); - - List expenseAccountOptions = accountReadPlatformService.retrieveAllEnabledDetailGLAccounts(GLAccountType.EXPENSE); - if (expenseAccountOptions.isEmpty()) { - expenseAccountOptions = null; - } - accountOptions.put("expenseAccountOptions", expenseAccountOptions); - - List accountingRuleTypeOptions = dropdownReadPlatformService.retrieveAccountingRuleTypeOptions(); + List accountingRuleTypeOptions = accountingDropdownReadPlatformService.retrieveAccountingRuleTypeOptions(); return new LoanProductData(productData, chargeOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, 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 dabd53945..1f91573e9 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 @@ -11,6 +11,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.mifosplatform.accounting.common.AccountingEnumerations; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.glaccount.data.GLAccountData; import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; import org.mifosplatform.infrastructure.codes.data.CodeValueData; @@ -18,7 +20,6 @@ import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.fund.data.FundData; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -182,7 +183,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId = null; final String transactionProcessingStrategyName = null; final Collection charges = null; - final EnumOptionData accountingType = LoanEnumerations.accountingRuleType(AccountingRuleType.NONE); + final EnumOptionData accountingType = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE); return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, @@ -346,7 +347,7 @@ public class LoanProductData { } public boolean hasAccountingEnabled() { - return this.accountingRule.getId() > 1; + return this.accountingRule.getId() > AccountingRuleType.NONE.getValue(); } public Long getId() { 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 821d7554b..1779a2f47 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 @@ -22,6 +22,7 @@ import javax.persistence.Table; import javax.persistence.UniqueConstraint; import org.apache.commons.lang.StringUtils; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; 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 a62dd348e..9cbd5520b 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 @@ -16,13 +16,13 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.accounting.common.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.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java index b9324e277..2ba79ab97 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java @@ -27,6 +27,4 @@ public interface LoanDropdownReadPlatformService { Collection retreiveTransactionProcessingStrategies(); - List retrieveAccountingRuleTypeOptions(); - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java index 9453f3282..96918f64f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java @@ -5,7 +5,6 @@ */ package org.mifosplatform.portfolio.loanproduct.service; -import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.accountingRuleType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.amortizationType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestCalculationPeriodType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRateFrequencyType; @@ -21,7 +20,6 @@ import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -103,11 +101,4 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat return strategyOptions; } - @Override - public List retrieveAccountingRuleTypeOptions() { - List accountingRuleTypeOptions = Arrays.asList(accountingRuleType(AccountingRuleType.NONE), - accountingRuleType(AccountingRuleType.CASH_BASED), accountingRuleType(AccountingRuleType.ACCRUAL_BASED)); - return accountingRuleTypeOptions; - } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index d65ee98e9..ee5ba9673 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -11,7 +11,6 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.LoanType; -import org.mifosplatform.portfolio.loanproduct.domain.AccountingRuleType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -45,8 +44,6 @@ public class LoanEnumerations { return interestType(id); } else if (typeName.equals(INTEREST_CALCULATION_PERIOD_TYPE)) { return interestCalculationPeriodType(id); - } else if (typeName.equals(ACCOUNTING_RULE_TYPE)) { - return accountingRuleType(id); } else if (typeName.equals(LOAN_TYPE)) { return loanType(id); } return null; } @@ -337,15 +334,6 @@ public class LoanEnumerations { return optionData; } - public static EnumOptionData accountingRuleType(final int id) { - return accountingRuleType(AccountingRuleType.fromInt(id)); - } - - public static EnumOptionData accountingRuleType(final AccountingRuleType type) { - EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), type.getCode(), type.toString()); - return optionData; - } - public static EnumOptionData loanType(final Integer loanTypeId) { return loanType(LoanType.fromInt(loanTypeId)); } 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 118c8001c..71dad3c11 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 @@ -10,6 +10,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; @@ -140,7 +141,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery"); final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType"); - final EnumOptionData accountingRuleType = LoanEnumerations.accountingRuleType(accountingRuleId); + final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId); final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); final BigDecimal minInterestRatePerPeriod = rs.getBigDecimal("minInterestRatePerPeriod"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index bc682f004..00c2746d2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.api; +import java.util.Collection; + import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -20,12 +22,15 @@ import org.apache.commons.lang.StringUtils; import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.codes.data.CodeValueData; +import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData; import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; @@ -42,18 +47,21 @@ public class SavingsAccountTransactionsApiResource { private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final ApiRequestParameterHelper apiRequestParameterHelper; private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; + private final CodeValueReadPlatformService codeValueReadPlatformService; @Autowired public SavingsAccountTransactionsApiResource(final PlatformSecurityContext context, final DefaultToApiJsonSerializer toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final ApiRequestParameterHelper apiRequestParameterHelper, - final SavingsAccountReadPlatformService savingsAccountReadPlatformService) { + final SavingsAccountReadPlatformService savingsAccountReadPlatformService, + final CodeValueReadPlatformService codeValueReadPlatformService) { this.context = context; this.toApiJsonSerializer = toApiJsonSerializer; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.apiRequestParameterHelper = apiRequestParameterHelper; this.savingsAccountReadPlatformService = savingsAccountReadPlatformService; + this.codeValueReadPlatformService = codeValueReadPlatformService; } private boolean is(final String commandParam, final String commandValue) { @@ -71,8 +79,10 @@ public class SavingsAccountTransactionsApiResource { // FIXME - KW - for now just send back generic default information for // both deposit/withdrawal templates - final SavingsAccountTransactionData savingsAccount = this.savingsAccountReadPlatformService - .retrieveDepositTransactionTemplate(savingsId); + SavingsAccountTransactionData savingsAccount = this.savingsAccountReadPlatformService.retrieveDepositTransactionTemplate(savingsId); + final Collection paymentTypeOptions = codeValueReadPlatformService + .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); + savingsAccount = SavingsAccountTransactionData.templateOnTop(savingsAccount, paymentTypeOptions); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, savingsAccount, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java index 88c45ede5..f49d0d677 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsApiConstants.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsProductData; @@ -49,6 +50,13 @@ public class SavingsApiConstants { public static final String withdrawalFeeTypeParamName = "withdrawalFeeType"; public static final String annualFeeAmountParamName = "annualFeeAmount"; public static final String annualFeeOnMonthDayParamName = "annualFeeOnMonthDay"; + public static final String accountingRuleParamName = "accountingRule"; + public static final String paymentTypeIdParamName = "paymentTypeId"; + public static final String accountNumberParamName = "accountNumber"; + public static final String checkNumberParamName = "checkNumber"; + public static final String routingCodeParamName = "routingCode"; + public static final String receiptNumberParamName = "receiptNumber"; + public static final String bankNumberParamName = "bankNumber"; // transaction parameters public static final String transactionDateParamName = "transactionDate"; @@ -59,7 +67,11 @@ public class SavingsApiConstants { nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, - annualFeeAmountParamName, annualFeeOnMonthDayParamName)); + annualFeeAmountParamName, annualFeeOnMonthDayParamName, accountingRuleParamName, + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue())); /** * These parameters will match the class level parameters of @@ -99,7 +111,8 @@ public class SavingsApiConstants { "withdrawalFeeTypeOptions")); public static final Set SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList( - localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName)); + localeParamName, dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName, + accountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); public static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java index 510e1fd29..9c562c2b0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/api/SavingsProductsApiResource.java @@ -7,6 +7,9 @@ package org.mifosplatform.portfolio.savings.api; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -20,9 +23,17 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; +import org.mifosplatform.accounting.common.AccountingDropdownReadPlatformService; +import org.mifosplatform.accounting.common.AccountingEnumerations; +import org.mifosplatform.accounting.common.AccountingRuleType; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; +import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService; import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.codes.data.CodeValueData; +import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService; import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -31,6 +42,7 @@ import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSeria import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService; +import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants; import org.mifosplatform.portfolio.savings.data.SavingsProductData; import org.mifosplatform.portfolio.savings.domain.SavingsCompoundingInterestPeriodType; import org.mifosplatform.portfolio.savings.domain.SavingsInterestCalculationDaysInYearType; @@ -55,6 +67,9 @@ public class SavingsProductsApiResource { private final DefaultToApiJsonSerializer toApiJsonSerializer; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final ApiRequestParameterHelper apiRequestParameterHelper; + private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; + private final CodeValueReadPlatformService codeValueReadPlatformService; + private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService; @Autowired public SavingsProductsApiResource(final SavingsProductReadPlatformService savingProductReadPlatformService, @@ -62,7 +77,9 @@ public class SavingsProductsApiResource { final CurrencyReadPlatformService currencyReadPlatformService, final PlatformSecurityContext context, final DefaultToApiJsonSerializer toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final ApiRequestParameterHelper apiRequestParameterHelper) { + final ApiRequestParameterHelper apiRequestParameterHelper, final CodeValueReadPlatformService codeValueReadPlatformService, + final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService, + final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService) { this.savingProductReadPlatformService = savingProductReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; this.currencyReadPlatformService = currencyReadPlatformService; @@ -70,6 +87,9 @@ public class SavingsProductsApiResource { this.toApiJsonSerializer = toApiJsonSerializer; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.apiRequestParameterHelper = apiRequestParameterHelper; + this.codeValueReadPlatformService = codeValueReadPlatformService; + this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; + this.accountMappingReadPlatformService = accountMappingReadPlatformService; } @POST @@ -120,15 +140,25 @@ public class SavingsProductsApiResource { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME); - SavingsProductData savingProduct = this.savingProductReadPlatformService.retrieveOne(productId); + SavingsProductData savingProductData = this.savingProductReadPlatformService.retrieveOne(productId); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - if (settings.isTemplate()) { - savingProduct = handleTemplateRelatedData(savingProduct); + Map accountingMappings = null; + Collection paymentChannelToFundSourceMappings = null; + if (savingProductData.hasAccountingEnabled()) { + accountingMappings = accountMappingReadPlatformService.fetchAccountMappingDetailsForSavingsProduct(productId, + savingProductData.accountingRuleTypeId()); + paymentChannelToFundSourceMappings = accountMappingReadPlatformService + .fetchPaymentTypeToFundSourceMappingsForSavingsProduct(productId); } - return this.toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS); + if (settings.isTemplate()) { + savingProductData = handleTemplateRelatedData(savingProductData, accountingMappings, paymentChannelToFundSourceMappings); + } + + return this.toApiJsonSerializer + .serialize(settings, savingProductData, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS); } @GET @@ -139,13 +169,14 @@ public class SavingsProductsApiResource { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME); - SavingsProductData savingProduct = handleTemplateRelatedData(null); + SavingsProductData savingProduct = handleTemplateRelatedData(null, new HashMap(), null); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS); } - private SavingsProductData handleTemplateRelatedData(final SavingsProductData savingsProduct) { + private SavingsProductData handleTemplateRelatedData(final SavingsProductData savingsProduct, + final Map accountingMappings, final Collection paymentChannelToFundSourceMappings) { final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations .compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY); @@ -159,6 +190,8 @@ public class SavingsProductsApiResource { final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType.DAYS_365); + final EnumOptionData accountingRule = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE); + CurrencyData currency = CurrencyData.blank(); final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); if (currencyOptions.size() == 1) { @@ -182,16 +215,26 @@ public class SavingsProductsApiResource { final Collection withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions(); + final Collection paymentTypeOptions = codeValueReadPlatformService + .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); + final Collection accountingRuleOptions = this.accountingDropdownReadPlatformService + .retrieveAccountingRuleTypeOptions(); + final Map> accountingMappingOptions = accountingDropdownReadPlatformService + .retrieveAccountMappingOptionsForSavingsProducts(); + SavingsProductData savingsProductToReturn = null; if (savingsProduct != null) { - savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions, + savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, accountingMappings, + paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, + accountingMappingOptions); } else { savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, currencyOptions, interestCompoundingPeriodTypeOptions, - interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, - lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, + paymentTypeOptions, accountingRuleOptions, accountingMappingOptions); } return savingsProductToReturn; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java index b5afea6d5..200e84e1f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionData.java @@ -6,9 +6,12 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; +import java.util.Collection; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; /** * Immutable data object representing a savings account transaction. @@ -22,11 +25,18 @@ public class SavingsAccountTransactionData { private final String accountNo; private final LocalDate date; private final CurrencyData currency; + private final PaymentDetailData paymentDetailData; private final BigDecimal amount; + // templates + final Collection paymentTypeOptions; + public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, - final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount) { - return new SavingsAccountTransactionData(id, transactionType, savingsId, savingsAccountNo, date, currency, amount); + final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, + final CurrencyData currency, final BigDecimal amount) { + final Collection paymentTypeOptions = null; + return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, + amount, paymentTypeOptions); } public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo, @@ -34,17 +44,31 @@ public class SavingsAccountTransactionData { final Long id = null; final SavingsAccountTransactionEnumData transactionType = null; final BigDecimal amount = null; - return new SavingsAccountTransactionData(id, transactionType, savingsId, savingsAccountNo, defaultLocalDate, currency, amount); + final PaymentDetailData paymentDetailData = null; + final Collection paymentTypeOptions = null; + return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate, + currency, amount, null); } - private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final Long savingsId, - final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount) { + public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData, + final Collection paymentTypeOptions) { + return new SavingsAccountTransactionData(savingsAccountTransactionData.id, savingsAccountTransactionData.transactionType, + savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId, + savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency, + savingsAccountTransactionData.amount, paymentTypeOptions); + } + + private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, + final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, + final CurrencyData currency, final BigDecimal amount, final Collection paymentTypeOptions) { this.id = id; this.transactionType = transactionType; + this.paymentDetailData = paymentDetailData; this.accountId = savingsId; this.accountNo = savingsAccountNo; this.date = date; this.currency = currency; this.amount = amount; + this.paymentTypeOptions = paymentTypeOptions; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index bd349bd79..e47482e48 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -5,15 +5,24 @@ */ package org.mifosplatform.portfolio.savings.data; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountNumberParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.activationDateParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.bankNumberParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.checkNumberParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.paymentTypeIdParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.receiptNumberParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.routingCodeParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionAmountParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.transactionDateParamName; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; @@ -62,6 +71,17 @@ public class SavingsAccountTransactionDataValidator { final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element); baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount(); + // Validate all string payment detail fields for max length + final Integer paymentTypeId = fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element); + baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero(); + final Set paymentDetailParameters = new HashSet(Arrays.asList(accountNumberParamName, checkNumberParamName, + routingCodeParamName, receiptNumberParamName, bankNumberParamName)); + for (String paymentDetailParameterName : paymentDetailParameters) { + final String paymentDetailParameterValue = fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element); + baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull() + .notExceedingLengthOf(50); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java index 8dbee6be2..377b68f0f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.savings.data; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountTransactionType; + /** * Immutable data object represent savings account transaction type * enumerations. @@ -16,9 +18,40 @@ public class SavingsAccountTransactionEnumData { private final String code; private final String value; + private final boolean deposit; + private final boolean withdrawal; + private final boolean interestPosting; + private final boolean feeDeduction; + public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) { this.id = id; this.code = code; this.value = value; + this.deposit = Long.valueOf(SavingsAccountTransactionType.DEPOSIT.getValue()).equals(this.id); + this.withdrawal = Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL.getValue()).equals(this.id); + this.interestPosting = Long.valueOf(SavingsAccountTransactionType.INTEREST_POSTING.getValue()).equals(this.id); + this.feeDeduction = Long.valueOf(SavingsAccountTransactionType.ANNUAL_FEE.getValue()).equals(this.id) + || Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()).equals(this.id); } + + public boolean isDeposit() { + return this.deposit; + } + + public boolean isWithdrawal() { + return this.withdrawal; + } + + public boolean isDepositOrWithdrawal() { + return this.deposit || this.withdrawal; + } + + public boolean isInterestPosting() { + return this.interestPosting; + } + + public boolean isFeeDeduction() { + return this.feeDeduction; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java index d0ccb1fb0..8f786102b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductData.java @@ -7,8 +7,14 @@ package org.mifosplatform.portfolio.savings.data; import java.math.BigDecimal; import java.util.Collection; +import java.util.List; +import java.util.Map; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingRuleType; +import org.mifosplatform.accounting.glaccount.data.GLAccountData; +import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; +import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; @@ -34,6 +40,13 @@ public class SavingsProductData { private final BigDecimal annualFeeAmount; private final MonthDay annualFeeOnMonthDay; + // accounting + private final EnumOptionData accountingRule; + @SuppressWarnings("unused") + private final Map accountingMappings; + @SuppressWarnings("unused") + private final Collection paymentChannelToFundSourceMappings; + // template @SuppressWarnings("unused") private final Collection currencyOptions; @@ -49,15 +62,23 @@ public class SavingsProductData { private final Collection lockinPeriodFrequencyTypeOptions; @SuppressWarnings("unused") private final Collection withdrawalFeeTypeOptions; + @SuppressWarnings("unused") + private final Collection paymentTypeOptions; + @SuppressWarnings("unused") + private final Collection accountingRuleOptions; + @SuppressWarnings("unused") + private final Map> accountingMappingOptions; public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, - final EnumOptionData interestCalculationDaysInYearType, final Collection currencyOptions, - final Collection interestCompoundingPeriodTypeOptions, + final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule, + final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, - final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions) { + final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, + final Collection paymentTypeOptions, final Collection accountingRuleOptions, + final Map> accountingMappingOptions) { final Long id = null; final String name = null; @@ -70,34 +91,42 @@ public class SavingsProductData { final EnumOptionData withdrawalFeeType = null; final BigDecimal annualFeeAmount = null; final MonthDay annualFeeMonthDay = null; + final Map accountingMappings = null; + final Collection paymentChannelToFundSourceMappings = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, - annualFeeMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, accountingRule, annualFeeAmount, + annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, + accountingRuleOptions, accountingMappingOptions); } /** * Returns a {@link SavingsProductData} that contains and exist * {@link SavingsProductData} data with further template data for dropdowns. */ - public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Collection currencyOptions, - final Collection interestCompoundingPeriodTypeOptions, + public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Map accountingMappings, + final Collection paymentChannelToFundSourceMappings, + final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, - final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions) { + final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, + final Collection paymentTypeOptions, final Collection accountingRuleOptions, + final Map> accountingMappingOptions) { return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.minRequiredOpeningBalance, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.withdrawalFeeAmount, - existingProduct.withdrawalFeeType, existingProduct.annualFeeAmount, existingProduct.annualFeeOnMonthDay, currencyOptions, + existingProduct.withdrawalFeeType, existingProduct.accountingRule, existingProduct.annualFeeAmount, + existingProduct.annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, - interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions); + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, + accountingRuleOptions, accountingMappingOptions); } public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency, @@ -105,7 +134,11 @@ public class SavingsProductData { final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) { + final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final EnumOptionData accountingType) { + + final Map accountingMappings = null; + final Collection paymentChannelToFundSourceMappings = null; final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -114,13 +147,17 @@ public class SavingsProductData { final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; + final Collection paymentTypeOptions = null; + final Collection accountingRuleOptions = null; + final Map> accountingMappingOptions = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, - annualFeeOnMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, accountingType, annualFeeAmount, + annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, + accountingRuleOptions, accountingMappingOptions); } public static SavingsProductData lookup(final Long id, final String name) { @@ -137,8 +174,11 @@ public class SavingsProductData { final EnumOptionData lockinPeriodFrequencyType = null; final BigDecimal withdrawalFeeAmount = null; final EnumOptionData withdrawalFeeType = null; + final EnumOptionData accountingType = null; final BigDecimal annualFeeAmount = null; final MonthDay annualFeeMonthDay = null; + final Map accountingMappings = null; + final Collection paymentChannelToFundSourceMappings = null; final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -147,13 +187,17 @@ public class SavingsProductData { final Collection interestCalculationDaysInYearTypeOptions = null; final Collection lockinPeriodFrequencyTypeOptions = null; final Collection withdrawalFeeTypeOptions = null; + final Collection paymentTypeOptions = null; + final Collection accountingRuleOptions = null; + final Map> accountingMappingOptions = null; return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, - annualFeeMonthDay, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions); + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, accountingType, annualFeeAmount, + annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, + interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, + interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, + accountingRuleOptions, accountingMappingOptions); } private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency, @@ -161,12 +205,16 @@ public class SavingsProductData { final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, - final EnumOptionData withdrawalFeeType, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, + final EnumOptionData withdrawalFeeType, final EnumOptionData accountingType, final BigDecimal annualFeeAmount, + final MonthDay annualFeeOnMonthDay, final Map accountingMappings, + final Collection paymentChannelToFundSourceMappings, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, - final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions) { + final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, + final Collection paymentTypeOptions, final Collection accountingRuleOptions, + final Map> accountingMappingOptions) { this.id = id; this.name = name; this.description = description; @@ -176,6 +224,7 @@ public class SavingsProductData { this.interestPostingPeriodType = interestPostingPeriodType; this.interestCalculationType = interestCalculationType; this.interestCalculationDaysInYearType = interestCalculationDaysInYearType; + this.accountingRule = accountingType; this.minRequiredOpeningBalance = minRequiredOpeningBalance; this.lockinPeriodFrequency = lockinPeriodFrequency; this.lockinPeriodFrequencyType = lockinPeriodFrequencyType; @@ -191,5 +240,23 @@ public class SavingsProductData { this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions; this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions; + + this.paymentTypeOptions = paymentTypeOptions; + this.accountingMappingOptions = accountingMappingOptions; + this.accountingRuleOptions = accountingRuleOptions; + if (accountingMappings == null || accountingMappings.isEmpty()) { + this.accountingMappings = null; + } else { + this.accountingMappings = accountingMappings; + } + this.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings; + } + + public boolean hasAccountingEnabled() { + return this.accountingRule.getId() > AccountingRuleType.NONE.getValue(); + } + + public int accountingRuleTypeId() { + return this.accountingRule.getId().intValue(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java index 434d6a357..ca7bbee5b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsProductDataValidator.java @@ -28,10 +28,13 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingConstants.SAVINGS_PRODUCT_ACCOUNTING_PARAMS; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; @@ -40,7 +43,9 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; @Component @@ -154,6 +159,35 @@ public class SavingsProductDataValidator { } } + // accounting related data validation + final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); + baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); + + if (isCashBasedAccounting(accountingRuleType)) { + + final Long savingsControlAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue()) + .value(savingsControlAccountId).notNull().integerGreaterThanZero(); + + final Long savingsReferenceAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue()) + .value(savingsReferenceAccountId).notNull().integerGreaterThanZero(); + + final Long interestOnSavingsAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue()) + .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero(); + + final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) + .notNull().integerGreaterThanZero(); + + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -250,10 +284,71 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(annualFeeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull(); } + final Long savingsControlAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId) + .ignoreIfNull().integerGreaterThanZero(); + + final Long savingsReferenceAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue()) + .value(savingsReferenceAccountId).ignoreIfNull().integerGreaterThanZero(); + + final Long interestOnSavingsAccountId = fromApiJsonHelper.extractLongNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue(), element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_SAVINGS.getValue()) + .value(interestOnSavingsAccountId).ignoreIfNull().integerGreaterThanZero(); + + final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + element); + baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId) + .ignoreIfNull().integerGreaterThanZero(); + + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + + private boolean isCashBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); + } + + /** + * Validation for advanced accounting options + * + * @param baseDataValidator + * @param element + */ + private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + if (fromApiJsonHelper.parameterExists(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) { + JsonArray paymentChannelMappingArray = fromApiJsonHelper.extractJsonArrayNamed( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); + if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) { + int i = 0; + do { + final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); + final Long paymentTypeId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.getValue()).getAsLong(); + final Long paymentSpecificFundAccountId = jsonObject.get(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()) + .getAsLong(); + baseDataValidator + .reset() + .parameter( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." + + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_TYPE.toString()).value(paymentTypeId).notNull() + .integerGreaterThanZero(); + baseDataValidator + .reset() + .parameter( + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." + + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue()).value(paymentSpecificFundAccountId) + .notNull().integerGreaterThanZero(); + i++; + } while (i < paymentChannelMappingArray.size()); + } + } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 5303d1370..37ea0963a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -16,8 +16,10 @@ import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,11 +49,13 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator; +import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.slf4j.Logger; @@ -271,7 +275,8 @@ public class SavingsAccount extends AbstractPersistable { return SavingsAccountStatusType.fromInt(this.status).isActive(); } - public void activate(final DateTimeFormatter formatter, final LocalDate activationDate) { + public void activate(final DateTimeFormatter formatter, final LocalDate activationDate, List existingTransactionIds, + List existingReversedTransactionIds) { if (isActive()) { final String defaultUserMessage = "Cannot activate account. Account is already active."; @@ -305,7 +310,12 @@ public class SavingsAccount extends AbstractPersistable { // activating account. final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); if (minRequiredOpeningBalance.isGreaterThanZero()) { - deposit(formatter, activationDate, minRequiredOpeningBalance.getAmount()); + /** + * Vishwas: TODO activate API should be modified to accept payment + * details (for entering minimum balance) + **/ + deposit(formatter, activationDate, minRequiredOpeningBalance.getAmount(), existingTransactionIds, + existingReversedTransactionIds, null); } } @@ -333,7 +343,8 @@ public class SavingsAccount extends AbstractPersistable { return lockedInUntilLocalDate; } - public void postInterest(final LocalDate interestPostingUpToDate) { + public void postInterest(final LocalDate interestPostingUpToDate, List existingTransactionIds, + List existingReversedTransactionIds) { final SavingsInterestPostingPeriodType postingPeriodType = SavingsInterestPostingPeriodType.fromInt(this.interestPostingPeriodType); final List postingLocalDates = determineInterestPostingDates(getActivationLocalDate(), interestPostingUpToDate, @@ -344,6 +355,8 @@ public class SavingsAccount extends AbstractPersistable { Money interestPostedToDate = Money.zero(this.currency); boolean recalucateDailyBalanceDetails = false; + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); for (LocalDate postingDate : postingLocalDates) { Money interestEarnedToBePostedForPeriod = findInterestEarnedToBePostedOn(interestPostedToDate, postingDate, compoundingPeriods); @@ -734,7 +747,8 @@ public class SavingsAccount extends AbstractPersistable { } public SavingsAccountTransaction deposit(final DateTimeFormatter formatter, final LocalDate transactionDate, - final BigDecimal transactionAmount) { + final BigDecimal transactionAmount, List existingTransactionIds, List existingReversedTransactionIds, + final PaymentDetail paymentDetail) { if (isNotActive()) { final String defaultUserMessage = "Transaction is not allowed. Account is not active."; @@ -771,8 +785,11 @@ public class SavingsAccount extends AbstractPersistable { throw new PlatformApiDataValidationException(dataValidationErrors); } + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + final Money amount = Money.of(this.currency, transactionAmount); - final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, transactionDate, amount); + final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, paymentDetail, transactionDate, amount); this.transactions.add(transaction); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); @@ -792,7 +809,8 @@ public class SavingsAccount extends AbstractPersistable { } public SavingsAccountTransaction withdraw(final DateTimeFormatter formatter, final LocalDate transactionDate, - final BigDecimal transactionAmount) { + final BigDecimal transactionAmount, List existingTransactionIds, List existingReversedTransactionIds, + final PaymentDetail paymentDetail) { if (isNotActive()) { @@ -843,6 +861,9 @@ public class SavingsAccount extends AbstractPersistable { throw new PlatformApiDataValidationException(dataValidationErrors); } + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + final Money transactionAmountMoney = Money.of(this.currency, transactionAmount); if (isNotEnoughFundsToWithdraw(transactionAmountMoney)) { @@ -850,7 +871,8 @@ public class SavingsAccount extends AbstractPersistable { throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), transactionAmount); } - final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, transactionDate, transactionAmountMoney); + final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, paymentDetail, transactionDate, + transactionAmountMoney); this.transactions.add(transaction); if (isAutomaticWithdrawalFee()) { @@ -875,6 +897,7 @@ public class SavingsAccount extends AbstractPersistable { } } + /***TODO Fixme: Annual fee seems to be added for all withdrawals?**/ final Money annualFee = Money.of(this.currency, this.annualFeeAmount); SavingsAccountTransaction annualFeeTransaction = SavingsAccountTransaction.annualFee(this, transactionDate, annualFee); this.transactions.add(annualFeeTransaction); @@ -1096,6 +1119,53 @@ public class SavingsAccount extends AbstractPersistable { } } + public Map deriveAccountingBridgeData(final CurrencyData currencyData, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final Map accountingBridgeData = new LinkedHashMap(); + accountingBridgeData.put("savingsId", this.getId()); + accountingBridgeData.put("savingsProductId", this.productId()); + accountingBridgeData.put("officeId", this.officeId()); + accountingBridgeData.put("cashBasedAccountingEnabled", this.isCashBasedAccountingEnabledOnSavingsProduct()); + accountingBridgeData.put("accrualBasedAccountingEnabled", this.isAccrualBasedAccountingEnabledOnSavingsProduct()); + + final List> newLoanTransactions = new ArrayList>(); + for (SavingsAccountTransaction transaction : this.transactions) { + if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(transaction.toMapData(currencyData)); + } else if (!existingTransactionIds.contains(transaction.getId())) { + newLoanTransactions.add(transaction.toMapData(currencyData)); + } + } + + accountingBridgeData.put("newSavingsTransactions", newLoanTransactions); + return accountingBridgeData; + } + + private Collection findExistingTransactionIds() { + + Collection ids = new ArrayList(); + + for (SavingsAccountTransaction transaction : this.transactions) { + ids.add(transaction.getId()); + } + + return ids; + } + + private Collection findExistingReversedTransactionIds() { + + final Collection ids = new ArrayList(); + + for (SavingsAccountTransaction transaction : this.transactions) { + if (transaction.isReversed()) { + ids.add(transaction.getId()); + } + } + + return ids; + } + public void update(final Client client) { this.client = client; } @@ -1117,6 +1187,18 @@ public class SavingsAccount extends AbstractPersistable { return this.accountNumberRequiresAutoGeneration; } + public Long productId() { + return this.product.getId(); + } + + private Boolean isCashBasedAccountingEnabledOnSavingsProduct() { + return this.product.isCashBasedAccountingEnabled(); + } + + private Boolean isAccrualBasedAccountingEnabledOnSavingsProduct() { + return this.product.isAccrualBasedAccountingEnabled(); + } + public Long officeId() { Long officeId = null; if (this.client != null) { @@ -1142,4 +1224,9 @@ public class SavingsAccount extends AbstractPersistable { } return id; } + + public MonetaryCurrency getCurrency() { + return this.currency; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index 67ea6e851..631a54ad6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -21,6 +21,7 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdr import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; import java.math.BigDecimal; +import java.util.List; import java.util.Locale; import org.joda.time.LocalDate; @@ -64,8 +65,12 @@ public class SavingsAccountAssembler { * Assembles a new {@link SavingsAccount} from JSON details passed in * request inheriting details where relevant from chosen * {@link SavingsProduct}. + * + * @param existingReversedTransactionIds + * @param existingTransactionIds */ - public SavingsAccount assembleFrom(final JsonCommand command) { + public SavingsAccount assembleFrom(final JsonCommand command, List existingTransactionIds, + List existingReversedTransactionIds) { final JsonElement element = command.parsedJson(); @@ -198,7 +203,7 @@ public class SavingsAccountAssembler { if (active) { final Locale locale = command.extractLocale(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - account.activate(formatter, activationDate); + account.activate(formatter, activationDate, existingTransactionIds, existingReversedTransactionIds); } return account; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 48bc6ccda..d4eff548a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -7,6 +7,8 @@ package org.mifosplatform.portfolio.savings.domain; import java.math.BigDecimal; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; import javax.persistence.Column; import javax.persistence.Entity; @@ -17,8 +19,12 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumData; +import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.springframework.data.jpa.domain.AbstractPersistable; /** @@ -34,6 +40,10 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @JoinColumn(name = "savings_account_id", nullable = false) private SavingsAccount savingsAccount; + @ManyToOne(optional = true) + @JoinColumn(name = "payment_detail_id", nullable = true) + private PaymentDetail paymentDetail; + @Column(name = "transaction_type_enum", nullable = false) private final Integer typeOf; @@ -66,14 +76,18 @@ public final class SavingsAccountTransaction extends AbstractPersistable { this.typeOf = null; } - public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final LocalDate date, final Money amount) { + public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final PaymentDetail paymentDetail, + final LocalDate date, final Money amount) { final boolean isReversed = false; - return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.DEPOSIT.getValue(), date, amount, isReversed); + return new SavingsAccountTransaction(savingsAccount, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, amount, + isReversed); } - public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final LocalDate date, final Money amount) { + public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final PaymentDetail paymentDetail, + final LocalDate date, final Money amount) { final boolean isReversed = false; - return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.WITHDRAWAL.getValue(), date, amount, isReversed); + return new SavingsAccountTransaction(savingsAccount, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(), date, + amount, isReversed); } public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final LocalDate date, final Money amount) { @@ -90,17 +104,23 @@ public final class SavingsAccountTransaction extends AbstractPersistable { public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final LocalDate date, final Money amount) { final boolean isReversed = false; - return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, - isReversed); + return new SavingsAccountTransaction(savingsAccount, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, isReversed); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed) { + this(savingsAccount, null, typeOf, transactionLocalDate, amount, isReversed); + } + + private SavingsAccountTransaction(final SavingsAccount savingsAccount, final PaymentDetail paymentDetail, final Integer typeOf, + final LocalDate transactionLocalDate, final Money amount, final boolean isReversed) { this.savingsAccount = savingsAccount; this.typeOf = typeOf; this.dateOf = transactionLocalDate.toDate(); this.amount = amount.getAmount(); this.reversed = isReversed; + this.paymentDetail = paymentDetail; + } public LocalDate transactionLocalDate() { @@ -209,4 +229,23 @@ public final class SavingsAccountTransaction extends AbstractPersistable { final Money transactionAmount = getAmount(amountToCheck.getCurrency()); return transactionAmount.isNotEqualTo(amountToCheck); } + + public Map toMapData(final CurrencyData currencyData) { + final Map thisTransactionData = new LinkedHashMap(); + + final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(this.typeOf); + + thisTransactionData.put("id", this.getId()); + thisTransactionData.put("type", transactionType); + thisTransactionData.put("reversed", Boolean.valueOf(this.isReversed())); + thisTransactionData.put("date", this.getTransactionLocalDate()); + thisTransactionData.put("currency", currencyData); + thisTransactionData.put("amount", this.amount); + + if (this.paymentDetail != null) { + thisTransactionData.put("paymentTypeId", this.paymentDetail.getPaymentType().getId()); + } + + return thisTransactionData; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java index 9ddd678a1..a6e8297fd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProduct.java @@ -22,6 +22,7 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.namePa import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeAmountParamName; import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdrawalFeeTypeParamName; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountingRuleParamName; import java.math.BigDecimal; import java.util.LinkedHashMap; @@ -33,6 +34,7 @@ import javax.persistence.Entity; import javax.persistence.Table; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -93,6 +95,12 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "lockin_period_frequency_enum", nullable = true) private Integer lockinPeriodFrequencyType; + /** + * A value from the {@link AccountingRuleType} enumeration. + */ + @Column(name = "accounting_type", nullable = false) + private Integer accountingRule; + @Column(name = "withdrawal_fee_amount", scale = 6, precision = 19, nullable = true) private BigDecimal withdrawalFeeAmount; @@ -114,11 +122,11 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, - final MonthDay annualFeeOnMonthDay) { + final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) { return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay); + lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, annualFeeOnMonthDay, accountingRuleType); } protected SavingsProduct() { @@ -132,7 +140,7 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final BigDecimal annualFeeAmount, - final MonthDay annualFeeOnMonthDay) { + final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) { this.name = name; this.description = description; @@ -163,6 +171,10 @@ public class SavingsProduct extends AbstractPersistable { this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth(); } + if (accountingRuleType != null) { + this.accountingRule = accountingRuleType.getValue(); + } + validateLockinDetails(); validateWithdrawalFeeDetails(); validateAnnualFeeDetails(); @@ -356,6 +368,12 @@ public class SavingsProduct extends AbstractPersistable { this.annualFeeOnMonth = newValue; } + if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) { + final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName); + actualChanges.put(accountingRuleParamName, newValue); + this.accountingRule = newValue; + } + validateLockinDetails(); validateWithdrawalFeeDetails(); validateAnnualFeeDetails(); @@ -401,4 +419,16 @@ public class SavingsProduct extends AbstractPersistable { return (this.lockinPeriodFrequency == null && this.lockinPeriodFrequencyType != null) || (this.lockinPeriodFrequencyType == null && this.lockinPeriodFrequency != null); } + + public boolean isCashBasedAccountingEnabled() { + return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingRule); + } + + public boolean isAccrualBasedAccountingEnabled() { + return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule); + } + + public Integer getAccountingType() { + return this.accountingRule; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java index 6ce98d52e..b6ad15a25 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsProductAssembler.java @@ -25,6 +25,7 @@ import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.withdr import java.math.BigDecimal; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingRuleType; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.springframework.stereotype.Component; @@ -86,10 +87,11 @@ public class SavingsProductAssembler { final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamed(annualFeeAmountParamName); final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName); + final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, - monthDayOfAnnualFee); + monthDayOfAnnualFee, accountingRuleType); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 04cb8a2b2..ef142ec0f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -12,6 +12,7 @@ import java.util.Collection; import org.joda.time.LocalDate; import org.joda.time.MonthDay; +import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.DateUtils; @@ -22,6 +23,7 @@ import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; +import org.mifosplatform.portfolio.paymentdetail.data.PaymentDetailData; import org.mifosplatform.portfolio.savings.data.SavingsAccountData; import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData; import org.mifosplatform.portfolio.savings.data.SavingsAccountSummaryData; @@ -342,12 +344,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo, "); + sqlBuilder.append("pd.payment_type_cv_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); + sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + sqlBuilder.append("cv.code_value as paymentTypeName "); sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id "); + sqlBuilder.append("left join m_code_value cv on pd.payment_type_cv_id = cv.id "); this.schemaSql = sqlBuilder.toString(); } @@ -368,6 +375,22 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Long savingsId = rs.getLong("savingsId"); final String accountNo = rs.getString("accountNo"); + PaymentDetailData paymentDetailData = null; + if (transactionType.isDepositOrWithdrawal()) { + final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType"); + if (paymentTypeId != null) { + final String typeName = rs.getString("paymentTypeName"); + CodeValueData paymentType = CodeValueData.instance(paymentTypeId, typeName); + final String accountNumber = rs.getString("accountNumber"); + final String checkNumber = rs.getString("checkNumber"); + final String routingCode = rs.getString("routingCode"); + final String receiptNumber = rs.getString("receiptNumber"); + final String bankNumber = rs.getString("bankNumber"); + paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, + bankNumber); + } + } + final String currencyCode = rs.getString("currencyCode"); final String currencyName = rs.getString("currencyName"); final String currencyNameCode = rs.getString("currencyNameCode"); @@ -376,7 +399,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, currencyNameCode); - return SavingsAccountTransactionData.create(id, transactionType, savingsId, accountNo, date, currency, amount); + return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, + amount); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 00ecddd65..9ccbbfacb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -6,19 +6,25 @@ package org.mifosplatform.portfolio.savings.service; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.Client; @@ -26,6 +32,8 @@ import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepository; import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; +import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; +import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.mifosplatform.portfolio.savings.api.SavingsApiConstants; import org.mifosplatform.portfolio.savings.data.SavingsAccountDataValidator; import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator; @@ -59,6 +67,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final ClientRepositoryWrapper clientRepository; private final GroupRepository groupRepository; private final SavingsProductRepository savingsProductRepository; + private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -67,7 +78,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountAssembler savingAccountAssembler, final SavingsAccountDataValidator savingsAccountDataValidator, final SavingsAccountTransactionDataValidator savingsAccountTransactionDataValidator, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final ClientRepositoryWrapper clientRepository, - final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository) { + final GroupRepository groupRepository, final SavingsProductRepository savingsProductRepository, + final PaymentDetailWritePlatformService paymentDetailWritePlatformService, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + final JournalEntryWritePlatformService journalEntryWritePlatformService) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; @@ -78,6 +92,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.clientRepository = clientRepository; this.groupRepository = groupRepository; this.savingsProductRepository = savingsProductRepository; + this.paymentDetailWritePlatformService = paymentDetailWritePlatformService; + this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; } /* @@ -115,7 +132,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.context.authenticatedUser(); this.savingsAccountDataValidator.validateForCreate(command.json()); - final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command, existingTransactionIds, + existingReversedTransactionIds); this.savingAccountRepository.save(account); if (account.isAccountNumberRequiresAutoGeneration()) { @@ -126,6 +146,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepository.save(account); } + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + final Long savingsId = account.getId(); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // @@ -237,10 +259,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); - account.activate(fmt, activationDate); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + + account.activate(fmt, activationDate, existingReversedTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.save(account); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -266,17 +293,26 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); - final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + final Map changes = new LinkedHashMap(); + PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + + final SavingsAccountTransaction deposit = account.deposit(fmt, transactionDate, transactionAmount, existingTransactionIds, + existingReversedTransactionIds, paymentDetail); final Long transactionId = saveTransactionToGenerateTransactionId(deposit); this.savingAccountRepository.save(account); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + return new CommandProcessingResultBuilder() // .withEntityId(transactionId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // .withSavingsId(savingsId) // + .with(changes)// .build(); } @@ -301,16 +337,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); - final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); + final Map changes = new LinkedHashMap(); + PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + + final SavingsAccountTransaction withdrawal = account.withdraw(fmt, transactionDate, transactionAmount, existingTransactionIds, + existingReversedTransactionIds, paymentDetail); final Long transactionId = saveTransactionToGenerateTransactionId(withdrawal); this.savingAccountRepository.save(account); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + return new CommandProcessingResultBuilder() // .withEntityId(transactionId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // .withSavingsId(savingsId) // + .with(changes)// .build(); } @@ -337,13 +382,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult postInterest(final Long savingsId, final JsonCommand command) { this.context.authenticatedUser(); + final List existingTransactionIds = new ArrayList(); + final List existingReversedTransactionIds = new ArrayList(); final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(today); + account.postInterest(today, existingTransactionIds, existingReversedTransactionIds); this.savingAccountRepository.save(account); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -352,4 +401,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withSavingsId(savingsId) // .build(); } + + private void postJournalEntries(final SavingsAccount savingsAccount, final List existingTransactionIds, + final List existingReversedTransactionIds) { + + final MonetaryCurrency currency = savingsAccount.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); + + final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds); + journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 922630c31..575414fe7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.Collection; import org.joda.time.MonthDay; +import org.mifosplatform.accounting.common.AccountingEnumerations; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; @@ -89,7 +90,8 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead sqlBuilder.append("sp.withdrawal_fee_type_enum as withdrawalFeeTypeEnum, "); sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); - sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); + sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay, "); + sqlBuilder.append("sp.accounting_type as accountingType "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); @@ -134,6 +136,9 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead .interestCalculationDaysInYearType(interestCalculationDaysInYearTypeValue); } + final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType"); + final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId); + final BigDecimal minRequiredOpeningBalance = rs.getBigDecimal("minRequiredOpeningBalance"); Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); @@ -163,7 +168,7 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead return SavingsProductData.instance(id, name, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, annualFeeAmount, - annualFeeOnMonthDay); + annualFeeOnMonthDay, accountingRuleType); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java index 0c3eb5dbf..43740ddaa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.savings.service; import java.util.Map; +import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static org.mifosplatform.portfolio.savings.api.SavingsApiConstants.accountingRuleParamName; @Service public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements SavingsProductWritePlatformService { @@ -32,16 +34,19 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi private final SavingsProductRepository savingProductRepository; private final SavingsProductDataValidator fromApiJsonDataValidator; private final SavingsProductAssembler savingsProductAssembler; + private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; @Autowired public SavingsProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsProductRepository savingProductRepository, final SavingsProductDataValidator fromApiJsonDataValidator, - final SavingsProductAssembler savingsProductAssembler) { + final SavingsProductAssembler savingsProductAssembler, + final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService) { this.context = context; this.savingProductRepository = savingProductRepository; this.fromApiJsonDataValidator = fromApiJsonDataValidator; this.savingsProductAssembler = savingsProductAssembler; this.logger = LoggerFactory.getLogger(SavingsProductWritePlatformServiceJpaRepositoryImpl.class); + this.accountMappingWritePlatformService = accountMappingWritePlatformService; } /* @@ -78,6 +83,9 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi this.savingProductRepository.save(product); + // save accounting mappings + accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command); + return new CommandProcessingResultBuilder() // .withEntityId(product.getId()) // .build(); @@ -99,6 +107,13 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi if (product == null) { throw new SavingsProductNotFoundException(productId); } final Map changes = product.update(command); + + // accounting related changes + boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName); + final Map accountingMappingChanges = accountMappingWritePlatformService.updateSavingsProductToGLAccountMapping( + product.getId(), command, accountingTypeChanged, product.getAccountingType()); + changes.putAll(accountingMappingChanges); + if (!changes.isEmpty()) { this.savingProductRepository.saveAndFlush(product); }