FINERACT-1960: GL account mappings to use Savings product with Accrual accounting
diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java
new file mode 100644
index 0000000..aac47c5
--- /dev/null
+++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.common;
+
+public final class AccountingValidations {
+
+ private AccountingValidations() {}
+
+ public static boolean isCashBasedAccounting(final Integer accountingRuleType) {
+ return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
+ }
+
+ public static boolean isAccrualPeriodicBasedAccounting(final Integer accountingRuleType) {
+ return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType);
+ }
+
+ public static boolean isUpfrontAccrualAccounting(final Integer accountingRuleType) {
+ return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
+ }
+
+ public static boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
+ return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType)
+ || AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
+ }
+
+ public static boolean isCashOrAccrualBasedAccounting(final Integer accountingRuleType) {
+ return isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType);
+ }
+
+}
diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java
index 13e175d..511ce5e 100644
--- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java
+++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java
@@ -31,7 +31,7 @@
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
-import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
@@ -83,7 +83,8 @@
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
- if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).notNull()
@@ -131,7 +132,7 @@
}
- if (isAccrualBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
@@ -168,7 +169,7 @@
Locale.getDefault());
baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);
- if (isCashBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)) {
final Long savingsControlAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
@@ -227,6 +228,23 @@
}
+ // Periodic Accrual Accounting aditional GL Accounts
+ if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
+ final Long feeReceivableAccountId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.FEES_RECEIVABLE.getValue()).value(feeReceivableAccountId)
+ .notNull().integerGreaterThanZero();
+ final Long penaltyReceivableAccountId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue())
+ .value(penaltyReceivableAccountId).notNull().integerGreaterThanZero();
+
+ final Long interestPayableAccountId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_PAYABLE.getValue()).value(interestPayableAccountId)
+ .notNull().integerGreaterThanZero();
+ }
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -246,7 +264,7 @@
Locale.getDefault());
baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);
- if (isCashBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)) {
final Long shareReferenceId = this.fromApiJsonHelper.extractLongNamed(SharesProductAccountingParams.SHARES_REFERENCE.getValue(),
element);
@@ -273,15 +291,6 @@
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
- private boolean isCashBasedAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
- }
-
- private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType)
- || AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
- }
-
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
index fa9bb08..b9edf47 100644
--- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
+++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
@@ -25,7 +25,9 @@
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
+import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
@@ -33,6 +35,7 @@
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingDataParams;
import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
@@ -44,6 +47,7 @@
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
+@Slf4j
@Service
@RequiredArgsConstructor
public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService {
@@ -106,7 +110,7 @@
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { PortfolioProductType.LOAN.getValue(), loanProductId });
- if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
+ if (AccountingValidations.isCashBasedAccounting(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
@@ -159,8 +163,8 @@
}
}
- } else if (AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingType)
- || AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingType)) {
+ } else if (AccountingValidations.isAccrualBasedAccounting(accountingType)
+ || AccountingValidations.isUpfrontAccrualAccounting(accountingType)) {
for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
@@ -225,49 +229,21 @@
@Override
public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType) {
- final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
-
final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper();
final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null ";
final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR
new Object[] { PortfolioProductType.SAVING.getValue(), savingsProductId });
- if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) {
+ Map<String, Object> accountMappingDetails = null;
+ if (AccountingValidations.isCashBasedAccounting(accountingType)) {
+ accountMappingDetails = setCashSavingsProductToGLAccountMaps(listOfProductToGLAccountMaps);
- for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+ } else if (AccountingValidations.isAccrualPeriodicBasedAccounting(accountingType)) {
+ accountMappingDetails = setAccrualPeriodicSavingsProductToGLAccountMaps(listOfProductToGLAccountMaps);
- final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
- final CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(financialAccountType);
-
- final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
- final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
- final String glCode = (String) productToGLAccountMap.get("glCode");
- final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
-
- if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_REFERENCE)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_REFERENCE.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_CONTROL)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_CONTROL.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_FEES)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_PENALTIES)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.TRANSFERS_SUSPENSE)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.INTEREST_ON_SAVINGS)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_ON_SAVINGS.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.LOSSES_WRITTEN_OFF)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_INTEREST)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_INTEREST.getValue(), gLAccountData);
- } else if (glAccountForSavings.equals(CashAccountsForSavings.ESCHEAT_LIABILITY)) {
- accountMappingDetails.put(SavingProductAccountingDataParams.ESCHEAT_LIABILITY.getValue(), gLAccountData);
- }
- }
}
+
return accountMappingDetails;
}
@@ -412,4 +388,105 @@
return fetchChargeToIncomeAccountMappings(PortfolioProductType.SHARES, productId, false);
}
+ private Map<String, Object> setAccrualPeriodicSavingsProductToGLAccountMaps(
+ final List<Map<String, Object>> listOfProductToGLAccountMaps) {
+ final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
+
+ for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+
+ final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
+ AccrualAccountsForSavings glAccountForSavings = AccrualAccountsForSavings.fromInt(financialAccountType);
+
+ if (glAccountForSavings != null) {
+ final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+ final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+ final String glCode = (String) productToGLAccountMap.get("glCode");
+ final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
+
+ // Assets
+ if (glAccountForSavings.equals(AccrualAccountsForSavings.SAVINGS_REFERENCE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_REFERENCE.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.FEES_RECEIVABLE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.FEES_RECEIVABLE.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.PENALTIES_RECEIVABLE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.PENALTIES_RECEIVABLE.getValue(), glAccountData);
+ // Liabilities
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.SAVINGS_CONTROL)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_CONTROL.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.TRANSFERS_SUSPENSE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.INTEREST_PAYABLE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_PAYABLE.getValue(), glAccountData);
+ // Income
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.INCOME_FROM_FEES)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_FEES.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.INCOME_FROM_PENALTIES)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.INCOME_FROM_INTEREST)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_INTEREST.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.ESCHEAT_LIABILITY)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.ESCHEAT_LIABILITY.getValue(), glAccountData);
+ // Expense
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.INTEREST_ON_SAVINGS)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_ON_SAVINGS.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(AccrualAccountsForSavings.LOSSES_WRITTEN_OFF)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData);
+ }
+ } else {
+ log.error("Accounting mapping null {}", financialAccountType);
+ }
+ }
+
+ return accountMappingDetails;
+ }
+
+ private Map<String, Object> setCashSavingsProductToGLAccountMaps(final List<Map<String, Object>> listOfProductToGLAccountMaps) {
+ final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8);
+
+ for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) {
+
+ final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType");
+ CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(financialAccountType);
+
+ if (glAccountForSavings != null) {
+ final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId");
+ final String glAccountName = (String) productToGLAccountMap.get("glAccountName");
+ final String glCode = (String) productToGLAccountMap.get("glCode");
+ final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
+
+ // Assets
+ if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_REFERENCE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_REFERENCE.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), glAccountData);
+ // Liabilities
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_CONTROL)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_CONTROL.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.TRANSFERS_SUSPENSE)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), glAccountData);
+ // Income
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_FEES)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_FEES.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_PENALTIES)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_INTEREST)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_INTEREST.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.ESCHEAT_LIABILITY)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.ESCHEAT_LIABILITY.getValue(), glAccountData);
+ // Expense
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.INTEREST_ON_SAVINGS)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_ON_SAVINGS.getValue(), glAccountData);
+ } else if (glAccountForSavings.equals(CashAccountsForSavings.LOSSES_WRITTEN_OFF)) {
+ accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData);
+ }
+ } else {
+ log.error("Accounting mapping null {}", financialAccountType);
+ }
+ }
+
+ return accountMappingDetails;
+ }
+
}
diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
index 6cccdfe..4b3e75b 100644
--- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
+++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java
@@ -21,6 +21,7 @@
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.Map;
+import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
@@ -167,6 +168,25 @@
changes.put(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
break;
case ACCRUAL_PERIODIC:
+ final Long feeReceivableId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), element);
+ final Long penaltyReceivableId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element);
+ final Long interestPayableId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), element);
+
+ changes.put(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingsControlId);
+ changes.put(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingsReferenceId);
+ changes.put(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId);
+ changes.put(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), incomeFromFeesId);
+ changes.put(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId);
+ changes.put(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
+ changes.put(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), overdraftControlId);
+ changes.put(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), incomeFromInterest);
+ changes.put(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
+ changes.put(SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), feeReceivableId);
+ changes.put(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), penaltyReceivableId);
+ changes.put(SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), interestPayableId);
break;
case ACCRUAL_UPFRONT:
break;
@@ -229,6 +249,61 @@
savingsProductId, CashAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes);
break;
case ACCRUAL_PERIODIC:
+ // asset
+ mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
+ savingsProductId, AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(),
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.toString(), changes);
+
+ mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+ savingsProductId, AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.toString(), changes);
+
+ mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.FEES_RECEIVABLE.getValue(),
+ savingsProductId, AccrualAccountsForSavings.FEES_RECEIVABLE.getValue(),
+ AccrualAccountsForSavings.FEES_RECEIVABLE.toString(), changes);
+
+ mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
+ savingsProductId, AccrualAccountsForSavings.PENALTIES_RECEIVABLE.getValue(),
+ AccrualAccountsForSavings.PENALTIES_RECEIVABLE.toString(), changes);
+
+ // income
+ mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
+ savingsProductId, AccrualAccountsForSavings.INCOME_FROM_FEES.getValue(),
+ AccrualAccountsForSavings.INCOME_FROM_FEES.toString(), changes);
+
+ mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
+ savingsProductId, AccrualAccountsForSavings.INCOME_FROM_PENALTIES.getValue(),
+ AccrualAccountsForSavings.INCOME_FROM_PENALTIES.toString(), changes);
+
+ mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
+ savingsProductId, AccrualAccountsForSavings.INCOME_FROM_INTEREST.getValue(),
+ AccrualAccountsForSavings.INCOME_FROM_INTEREST.toString(), changes);
+
+ // expenses
+ mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
+ savingsProductId, AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(),
+ AccrualAccountsForSavings.INTEREST_ON_SAVINGS.toString(), changes);
+
+ mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
+ savingsProductId, AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(),
+ AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.toString(), changes);
+
+ // liability
+ mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
+ savingsProductId, AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ AccrualAccountsForSavings.SAVINGS_CONTROL.toString(), changes);
+
+ mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
+ savingsProductId, AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(),
+ AccrualAccountsForSavings.TRANSFERS_SUSPENSE.toString(), changes);
+
+ mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_PAYABLE.getValue(),
+ savingsProductId, AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(),
+ AccrualAccountsForSavings.INTEREST_PAYABLE.toString(), changes);
+
+ createOrmergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(),
+ savingsProductId, AccrualAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes);
+
break;
case ACCRUAL_UPFRONT:
break;
diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
index 257dc1f..669ad59 100644
--- a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
+++ b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
@@ -278,6 +278,54 @@
}
/***
+ * Accounting placeholders for periodic accrual based accounting for savings products
+ ***/
+ public enum AccrualAccountsForSavings {
+
+ SAVINGS_REFERENCE(1), //
+ SAVINGS_CONTROL(2), //
+ INTEREST_ON_SAVINGS(3), //
+ INCOME_FROM_FEES(4), //
+ INCOME_FROM_PENALTIES(5), //
+ TRANSFERS_SUSPENSE(10), //
+ OVERDRAFT_PORTFOLIO_CONTROL(11), //
+ INCOME_FROM_INTEREST(12), //
+ LOSSES_WRITTEN_OFF(13), //
+ ESCHEAT_LIABILITY(14), //
+ FEES_RECEIVABLE(15), //
+ PENALTIES_RECEIVABLE(16), //
+ INTEREST_PAYABLE(17);
+
+ private final Integer value;
+
+ AccrualAccountsForSavings(final Integer value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return name().toString().replaceAll("_", " ");
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ private static final Map<Integer, AccrualAccountsForSavings> intToEnumMap = new HashMap<>();
+
+ static {
+ for (final AccrualAccountsForSavings type : AccrualAccountsForSavings.values()) {
+ intToEnumMap.put(type.value, type);
+ }
+ }
+
+ public static AccrualAccountsForSavings fromInt(final int i) {
+ final AccrualAccountsForSavings type = intToEnumMap.get(Integer.valueOf(i));
+ return type;
+ }
+ }
+
+ /***
* Enum of all accounting related input parameter names used while creating/updating a savings product
***/
public enum SavingProductAccountingParams {
@@ -298,7 +346,10 @@
OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControlId"), //
INCOME_FROM_INTEREST("incomeFromInterestId"), //
LOSSES_WRITTEN_OFF("writeOffAccountId"), //
- ESCHEAT_LIABILITY("escheatLiabilityId"); //
+ ESCHEAT_LIABILITY("escheatLiabilityId"), //
+ PENALTIES_RECEIVABLE("penaltiesReceivableAccountId"), //
+ FEES_RECEIVABLE("feesReceivableAccountId"), //
+ INTEREST_PAYABLE("interestPayableAccountId");
private final String value;
@@ -332,7 +383,10 @@
OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControl"), //
INCOME_FROM_INTEREST("incomeFromInterest"), //
LOSSES_WRITTEN_OFF("writeOffAccount"), //
- ESCHEAT_LIABILITY("escheatLiabilityAccount"); //
+ ESCHEAT_LIABILITY("escheatLiabilityAccount"), //
+ FEES_RECEIVABLE("feeReceivableAccount"), //
+ PENALTIES_RECEIVABLE("penaltyReceivableAccount"), //
+ INTEREST_PAYABLE("interestPayableAccount"); //
private final String value;
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
index 517003b..1665603 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
@@ -218,8 +218,10 @@
SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
- SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), chartsParamName,
- SavingsApiConstants.withHoldTaxParamName, SavingsApiConstants.taxGroupIdParamName));
+ SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
+ SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
+ SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), chartsParamName, SavingsApiConstants.withHoldTaxParamName,
+ SavingsApiConstants.taxGroupIdParamName));
private static final Set<String> PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName));
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java
index 639806c..af1034a 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java
@@ -40,6 +40,7 @@
WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), //
PAY_CHARGE(7, "savingsAccountTransactionType.payCharge", TransactionEntryType.DEBIT), //
DIVIDEND_PAYOUT(8, "savingsAccountTransactionType.dividendPayout", TransactionEntryType.CREDIT), //
+ ACCRUAL(10, "savingsAccountTransactionType.accrual"), //
INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), //
@@ -181,6 +182,10 @@
return this == AMOUNT_RELEASE;
}
+ public boolean isAccrual() {
+ return this == ACCRUAL;
+ }
+
public boolean isCredit() {
// AMOUNT_RELEASE is not credit, because the account balance is not changed
return isCreditEntryType() && !isAmountRelease();
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java
index 60401d5..6d69469 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java
@@ -49,6 +49,7 @@
private final boolean escheat;
private final boolean amountHold;
private final boolean amountRelease;
+ private final boolean accrual;
public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
@@ -57,6 +58,7 @@
SavingsAccountTransactionType transactionType = id == null ? null : SavingsAccountTransactionType.fromInt(id.intValue());
this.deposit = transactionType == SavingsAccountTransactionType.DEPOSIT;
this.dividendPayout = transactionType == SavingsAccountTransactionType.DIVIDEND_PAYOUT;
+ this.accrual = transactionType == SavingsAccountTransactionType.ACCRUAL;
this.withdrawal = transactionType == SavingsAccountTransactionType.WITHDRAWAL;
this.interestPosting = transactionType == SavingsAccountTransactionType.INTEREST_POSTING;
this.feeDeduction = transactionType == SavingsAccountTransactionType.ANNUAL_FEE
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java
index 9d14a87..21c5943 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java
@@ -141,6 +141,10 @@
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHDRAWAL.getValue().longValue(),
SavingsAccountTransactionType.WITHDRAWAL.getCode(), "Withdrawal");
break;
+ case ACCRUAL:
+ optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.ACCRUAL.getValue().longValue(),
+ SavingsAccountTransactionType.ACCRUAL.getCode(), "Accrual");
+ break;
case INTEREST_POSTING:
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.INTEREST_POSTING.getValue().longValue(),
SavingsAccountTransactionType.INTEREST_POSTING.getCode(), "Interest posting");
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java
index e5c9fb4..5bccce4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java
@@ -40,6 +40,9 @@
if (savingsDTO.isCashBasedAccountingEnabled()) {
accountingProcessorForSavings = this.applicationContext.getBean("cashBasedAccountingProcessorForSavings",
AccountingProcessorForSavings.class);
+ } else if (savingsDTO.isAccrualBasedAccountingEnabled()) {
+ accountingProcessorForSavings = this.applicationContext.getBean("accrualBasedAccountingProcessorForSavings",
+ AccountingProcessorForSavings.class);
}
return accountingProcessorForSavings;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
index 6c637e9..804184c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
@@ -30,6 +30,7 @@
import org.apache.fineract.accounting.closure.domain.GLClosure;
import org.apache.fineract.accounting.closure.domain.GLClosureRepository;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
+import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
@@ -573,6 +574,28 @@
paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
}
+ public void createAccrualBasedJournalEntriesAndReversalsForSavingsTax(final Office office, final String currencyCode,
+ final AccrualAccountsForSavings accountTypeToBeDebited, final AccrualAccountsForSavings accountTypeToBeCredited,
+ final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId,
+ final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal, final List<TaxPaymentDTO> taxDetails) {
+
+ for (TaxPaymentDTO taxPaymentDTO : taxDetails) {
+ if (taxPaymentDTO.getAmount() != null) {
+ if (taxPaymentDTO.getCreditAccountId() == null) {
+ createAccrualBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeCredited.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(),
+ isReversal);
+ } else {
+ createAccrualBasedBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode,
+ taxPaymentDTO.getCreditAccountId(), savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(),
+ isReversal);
+ }
+ }
+ }
+ createAccrualBasedDebitJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeDebited.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+
public void createCashBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
final Integer accountTypeToBeDebited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId,
final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
@@ -623,6 +646,56 @@
}
}
+ public void createAccrualBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
+ final Integer accountTypeToBeDebited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId,
+ final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
+ // reverse debits and credits for reversals
+ if (isReversal) {
+ createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount);
+ } else {
+ createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount);
+ }
+ }
+
+ public void createAccrualBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
+ final Integer accountTypeToBeCredited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId,
+ final String transactionId, final LocalDate transactionDate, final BigDecimal amount, final Boolean isReversal) {
+ // reverse debits and credits for reversals
+ if (isReversal) {
+ createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount);
+ } else {
+ createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount);
+ }
+ }
+
+ public void createAccrualBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
+ final Long debitAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
+ final BigDecimal amount, final Boolean isReversal) {
+ // reverse debits and credits for reversals
+ final GLAccount debitAccount = getGLAccountById(debitAccountId);
+ if (isReversal) {
+ createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
+ } else {
+ createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount);
+ }
+ }
+
+ public void createAccrualBasedBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode,
+ final Long creditAccountId, final Long savingsId, final String transactionId, final LocalDate transactionDate,
+ final BigDecimal amount, final Boolean isReversal) {
+ // reverse debits and credits for reversals
+ final GLAccount creditAccount = getGLAccountById(creditAccountId);
+ if (isReversal) {
+ createDebitJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
+ } else {
+ createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
+ }
+ }
+
private void createDebitJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToDebitId,
final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId,
final LocalDate transactionDate, final BigDecimal amount) {
@@ -768,6 +841,41 @@
}
}
+ public void createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(final Office office, final String currencyCode,
+ final AccrualAccountsForSavings accountTypeToBeDebited, AccrualAccountsForSavings accountTypeToBeCredited,
+ final Long savingsProductId, final Long paymentTypeId, final Long loanId, final String transactionId,
+ final LocalDate transactionDate, final BigDecimal totalAmount, final Boolean isReversal,
+ final List<ChargePaymentDTO> chargePaymentDTOs) {
+ // TODO Vishwas: Remove this validation, as and when appropriate Junit
+ // tests are written for accounting
+ /**
+ * Accounting module currently supports a single charge per transaction, throw an error if this is not the case
+ * here so any developers changing the expected portfolio behavior would also take care of modifying the
+ * accounting code appropriately
+ **/
+ if (chargePaymentDTOs.size() != 1) {
+ throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code",
+ "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code");
+ }
+ ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0);
+ GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(),
+ chargePaymentDTO.getChargeId());
+
+ final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(),
+ paymentTypeId);
+ if (isReversal) {
+ createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate,
+ totalAmount);
+ createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate,
+ totalAmount);
+ } else {
+ createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate,
+ totalAmount);
+ createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate,
+ totalAmount);
+ }
+ }
+
public LoanTransaction getLoanTransactionById(final Long loanTransactionId) {
return this.loanTransactionRepository.getReferenceById(loanTransactionId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java
new file mode 100644
index 0000000..4aa1b93
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java
@@ -0,0 +1,272 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.accounting.closure.domain.GLClosure;
+import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings;
+import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
+import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
+import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class AccrualBasedAccountingProcessorForSavings implements AccountingProcessorForSavings {
+
+ private final AccountingProcessorHelper helper;
+
+ @Override
+ public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) {
+ final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId());
+ final Long savingsProductId = savingsDTO.getSavingsProductId();
+ final Long savingsId = savingsDTO.getSavingsId();
+ final String currencyCode = savingsDTO.getCurrencyCode();
+ for (final SavingsTransactionDTO savingsTransactionDTO : savingsDTO.getNewSavingsTransactions()) {
+ final LocalDate transactionDate = savingsTransactionDTO.getTransactionDate();
+ final String transactionId = savingsTransactionDTO.getTransactionId();
+ final Office office = this.helper.getOfficeById(savingsTransactionDTO.getOfficeId());
+ final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId();
+ final boolean isReversal = savingsTransactionDTO.isReversed();
+ final BigDecimal amount = savingsTransactionDTO.getAmount();
+ final BigDecimal overdraftAmount = savingsTransactionDTO.getOverdraftAmount();
+ final List<ChargePaymentDTO> feePayments = savingsTransactionDTO.getFeePayments();
+ final List<ChargePaymentDTO> penaltyPayments = savingsTransactionDTO.getPenaltyPayments();
+
+ this.helper.checkForBranchClosures(latestGLClosure, transactionDate);
+
+ if (savingsTransactionDTO.getTransactionType().isWithdrawal() && savingsTransactionDTO.isOverdraftTransaction()) {
+ boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0;
+ if (savingsTransactionDTO.isAccountTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+ FinancialActivity.LIABILITY_TRANSFER.getValue(), savingsProductId, paymentTypeId, savingsId, transactionId,
+ transactionDate, overdraftAmount, isReversal);
+ if (isPositive) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), FinancialActivity.LIABILITY_TRANSFER.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+ amount.subtract(overdraftAmount), isReversal);
+ }
+ } else {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, overdraftAmount, isReversal);
+ if (isPositive) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+ }
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isDeposit() && savingsTransactionDTO.isOverdraftTransaction()) {
+ boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0;
+ if (savingsTransactionDTO.isAccountTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ FinancialActivity.LIABILITY_TRANSFER.getValue(),
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, overdraftAmount, isReversal);
+ if (isPositive) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ FinancialActivity.LIABILITY_TRANSFER.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+ amount.subtract(overdraftAmount), isReversal);
+ }
+ } else {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(),
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, overdraftAmount, isReversal);
+ if (isPositive) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(),
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+ }
+ }
+ }
+
+ /** Handle Deposits and reversals of deposits **/
+ else if (savingsTransactionDTO.getTransactionType().isDeposit()) {
+ if (savingsTransactionDTO.isAccountTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ FinancialActivity.LIABILITY_TRANSFER.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ } else {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+ }
+
+ /** Handle Deposits and reversals of Dividend pay outs **/
+ else if (savingsTransactionDTO.getTransactionType().isDividendPayout()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ FinancialActivity.PAYABLE_DIVIDENDS.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+
+ /** Handle withdrawals and reversals of withdrawals **/
+ else if (savingsTransactionDTO.getTransactionType().isWithdrawal()) {
+ if (savingsTransactionDTO.isAccountTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), FinancialActivity.LIABILITY_TRANSFER.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ } else {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isEscheat()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.ESCHEAT_LIABILITY.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+ /**
+ * Handle Interest Applications and reversals of Interest Applications
+ **/
+ else if (savingsTransactionDTO.getTransactionType().isInterestPosting() && savingsTransactionDTO.isOverdraftTransaction()) {
+ boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0;
+ // Post journal entry if earned interest amount is greater than
+ // zero
+ if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(),
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, overdraftAmount, isReversal);
+ if (isPositive) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(),
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal);
+ }
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) {
+ // Post journal entry if earned interest amount is greater than
+ // zero
+ if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isAccrual()) {
+ // Post journal entry for Accrual Recognition
+ if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isWithholdTax()) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsTax(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.SAVINGS_REFERENCE, savingsProductId,
+ paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal,
+ savingsTransactionDTO.getTaxPayments());
+ }
+
+ /** Handle Fees Deductions and reversals of Fees Deductions **/
+ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction() && savingsTransactionDTO.isOverdraftTransaction()) {
+ boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0;
+ // Is the Charge a penalty?
+ if (penaltyPayments.size() > 0) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES,
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal,
+ penaltyPayments);
+ if (isPositive) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES,
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate,
+ amount.subtract(overdraftAmount), isReversal, penaltyPayments);
+ }
+ } else {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES,
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal,
+ feePayments);
+ if (isPositive) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId,
+ paymentTypeId, savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal,
+ feePayments);
+ }
+ }
+ }
+
+ else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) {
+ // Is the Charge a penalty?
+ if (penaltyPayments.size() > 0) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, savingsProductId,
+ paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments);
+ } else {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId,
+ paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
+ }
+ }
+
+ /** Handle Transfers proposal **/
+ else if (savingsTransactionDTO.getTransactionType().isInitiateTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+
+ /** Handle Transfer Withdrawal or Acceptance **/
+ else if (savingsTransactionDTO.getTransactionType().isWithdrawTransfer()
+ || savingsTransactionDTO.getTransactionType().isApproveTransfer()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ }
+
+ /** overdraft **/
+ else if (savingsTransactionDTO.getTransactionType().isOverdraftInterest()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), AccrualAccountsForSavings.INCOME_FROM_INTEREST.getValue(),
+ savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+ } else if (savingsTransactionDTO.getTransactionType().isWrittenoff()) {
+ this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
+ AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(),
+ AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId,
+ transactionId, transactionDate, amount, isReversal);
+ } else if (savingsTransactionDTO.getTransactionType().isOverdraftFee()) {
+ this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode,
+ AccrualAccountsForSavings.SAVINGS_REFERENCE, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId,
+ paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
+ }
+ }
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
index 751bc65..5349600 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
@@ -27,6 +27,7 @@
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
+import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
@@ -211,6 +212,65 @@
}
}
+ private void saveSavingsBaseAccountMapping(final Long savingProductId, final DepositAccountType accountType, final JsonCommand command,
+ final JsonElement element) {
+ // asset
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
+ SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingProductId,
+ CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
+
+ if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
+ SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingProductId,
+ CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue());
+ }
+
+ // income
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+ SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), savingProductId,
+ CashAccountsForSavings.INCOME_FROM_FEES.getValue());
+
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+ SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), savingProductId,
+ CashAccountsForSavings.INCOME_FROM_PENALTIES.getValue());
+
+ if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
+ SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), savingProductId,
+ CashAccountsForSavings.INCOME_FROM_INTEREST.getValue());
+ }
+
+ // expenses
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
+ SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), savingProductId,
+ CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue());
+
+ if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
+ SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), savingProductId,
+ CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue());
+ }
+
+ // liability
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
+ SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingProductId,
+ CashAccountsForSavings.SAVINGS_CONTROL.getValue());
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
+ SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), savingProductId,
+ CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue());
+
+ final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
+ if (null != isDormancyTrackingActive && isDormancyTrackingActive) {
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
+ SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), savingProductId,
+ CashAccountsForSavings.ESCHEAT_LIABILITY.getValue());
+ }
+
+ // advanced accounting mappings
+ this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
+ this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
+ }
+
@Override
@Transactional
public void createSavingProductToGLAccountMapping(final Long savingProductId, final JsonCommand command,
@@ -219,67 +279,28 @@
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
+ saveSavingsBaseAccountMapping(savingProductId, accountType, command, element);
+ break;
+
+ case ACCRUAL_PERIODIC:
+ saveSavingsBaseAccountMapping(savingProductId, accountType, command, element);
+ // assets
this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
- SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingProductId,
- CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
+ SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), savingProductId,
+ AccrualAccountsForSavings.FEES_RECEIVABLE.getValue());
- if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
- this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
- SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingProductId,
- CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue());
- }
-
- // income
- this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
- SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), savingProductId,
- CashAccountsForSavings.INCOME_FROM_FEES.getValue());
-
- this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
- SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), savingProductId,
- CashAccountsForSavings.INCOME_FROM_PENALTIES.getValue());
-
- if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
- this.savingsProductToGLAccountMappingHelper.saveSavingsToIncomeAccountMapping(element,
- SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), savingProductId,
- CashAccountsForSavings.INCOME_FROM_INTEREST.getValue());
- }
-
- // expenses
- this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
- SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), savingProductId,
- CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue());
-
- if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
- this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element,
- SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), savingProductId,
- CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue());
- }
+ this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element,
+ SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), savingProductId,
+ AccrualAccountsForSavings.PENALTIES_RECEIVABLE.getValue());
// liability
this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
- SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingProductId,
- CashAccountsForSavings.SAVINGS_CONTROL.getValue());
- this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
- SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), savingProductId,
- CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue());
-
- final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName,
- element);
- if (null != isDormancyTrackingActive && isDormancyTrackingActive) {
- this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
- SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), savingProductId,
- CashAccountsForSavings.ESCHEAT_LIABILITY.getValue());
- }
-
- // advanced accounting mappings
- this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
- this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
+ SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), savingProductId,
+ AccrualAccountsForSavings.INTEREST_PAYABLE.getValue());
break;
default:
break;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index b99e1bd..946d1e5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -359,6 +359,8 @@
CalendarInstance compoundingCalendarInstance = null;
InterestRecalculationCompoundingMethod compoundingMethod = null;
boolean allowCompoundingOnEod = false;
+ final Boolean isFloatingInterestRate = this.fromApiJsonHelper
+ .extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
if (isInterestRecalculationEnabled) {
LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct
.getProductInterestRecalculationDetails();
@@ -409,8 +411,7 @@
if (loanProduct.isLinkedToFloatingInterestRate()) {
final BigDecimal interestRateDiff = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
- final Boolean isFloatingInterestRate = this.fromApiJsonHelper
- .extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
+
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 6a6abe9..c5ee18f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -33,10 +33,9 @@
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
-import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -65,7 +64,6 @@
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.springframework.stereotype.Component;
-@Slf4j
@RequiredArgsConstructor
@Component
public final class LoanProductDataValidator {
@@ -613,7 +611,8 @@
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed(ACCOUNTING_RULE, element, Locale.getDefault());
baseDataValidator.reset().parameter(ACCOUNTING_RULE).value(accountingRuleType).notNull().inMinMaxRange(1, 4);
- if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).notNull()
@@ -709,7 +708,7 @@
}
- if (isAccrualBasedAccounting(accountingRuleType)) {
+ if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
@@ -2144,22 +2143,6 @@
}
}
- private boolean isCashBasedAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
- }
-
- private boolean isAccrualBasedAccounting(final Integer accountingRuleType) {
- return isUpfrontAccrualAccounting(accountingRuleType) || isPeriodicAccounting(accountingRuleType);
- }
-
- private boolean isUpfrontAccrualAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType);
- }
-
- private boolean isPeriodicAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType);
- }
-
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java
index 96de367..75981cf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java
@@ -318,88 +318,31 @@
public String description;
}
+ static final class GetFixedDepositProductsGlAccount {
+
+ private GetFixedDepositProductsGlAccount() {}
+
+ @Schema(example = "12")
+ public Long id;
+ @Schema(example = "savings ref")
+ public String name;
+ @Schema(example = "20")
+ public Integer glCode;
+ }
+
static final class GetFixedDepositProductsProductIdAccountingMappings {
private GetFixedDepositProductsProductIdAccountingMappings() {}
- static final class GetFixedDepositProductsProductIdSavingsReferenceAccount {
-
- private GetFixedDepositProductsProductIdSavingsReferenceAccount() {}
-
- @Schema(example = "12")
- public Long id;
- @Schema(example = "savings ref")
- public String name;
- @Schema(example = "20")
- public String glCode;
- }
-
- static final class GetFixedDepositProductsProductIdIncomeFromFeeAccount {
-
- private GetFixedDepositProductsProductIdIncomeFromFeeAccount() {}
-
- @Schema(example = "16")
- public Long id;
- @Schema(example = "income from savings fee")
- public String name;
- @Schema(example = "24")
- public String glCode;
- }
-
- static final class GetFixedDepositProductsProductIdIncomeFromPenaltyAccount {
-
- private GetFixedDepositProductsProductIdIncomeFromPenaltyAccount() {}
-
- @Schema(example = "17")
- public Long id;
- @Schema(example = "income from sav penalties")
- public String name;
- @Schema(example = "25")
- public String glCode;
- }
-
- static final class GetFixedDepositProductsProductIdInterestOnSavingsAccount {
-
- private GetFixedDepositProductsProductIdInterestOnSavingsAccount() {}
-
- @Schema(example = "15")
- public Long id;
- @Schema(example = "interest on savings")
- public String name;
- @Schema(example = "23")
- public String glCode;
- }
-
- static final class GetFixedDepositProductsProductIdSavingsControlAccount {
-
- private GetFixedDepositProductsProductIdSavingsControlAccount() {}
-
- @Schema(example = "13")
- public Long id;
- @Schema(example = "savings ref tool kit")
- public String name;
- @Schema(example = "21")
- public String glCode;
- }
-
- static final class GetFixedDepositProductsProductIdTransfersInSuspenseAccount {
-
- private GetFixedDepositProductsProductIdTransfersInSuspenseAccount() {}
-
- @Schema(example = "14")
- public Long id;
- @Schema(example = "saving transfers")
- public String name;
- @Schema(example = "22")
- public String glCode;
- }
-
- public GetFixedDepositProductsProductIdSavingsReferenceAccount savingsReferenceAccount;
- public GetFixedDepositProductsProductIdIncomeFromFeeAccount incomeFromFeeAccount;
- public GetFixedDepositProductsProductIdIncomeFromPenaltyAccount incomeFromPenaltyAccount;
- public GetFixedDepositProductsProductIdInterestOnSavingsAccount interestOnSavingsAccount;
- public GetFixedDepositProductsProductIdSavingsControlAccount savingsControlAccount;
- public GetFixedDepositProductsProductIdTransfersInSuspenseAccount transfersInSuspenseAccount;
+ public GetFixedDepositProductsGlAccount savingsReferenceAccount;
+ public GetFixedDepositProductsGlAccount feeReceivableAccount;
+ public GetFixedDepositProductsGlAccount penaltyReceivableAccount;
+ public GetFixedDepositProductsGlAccount incomeFromFeeAccount;
+ public GetFixedDepositProductsGlAccount incomeFromPenaltyAccount;
+ public GetFixedDepositProductsGlAccount interestOnSavingsAccount;
+ public GetFixedDepositProductsGlAccount savingsControlAccount;
+ public GetFixedDepositProductsGlAccount transfersInSuspenseAccount;
+ public GetFixedDepositProductsGlAccount interestPayableAccount;
}
static final class GetFixedDepositProductsProductIdFeeToIncomeAccountMappings {
@@ -455,7 +398,7 @@
}
public GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge;
- public GetFixedDepositProductsProductIdAccountingMappings.GetFixedDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount;
+ public GetFixedDepositProductsGlAccount incomeAccount;
}
static final class GetFixedDepositProductsProductIdPreClosurePenalInterestOnType {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java
index e07b32c..57f5dd0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java
@@ -343,88 +343,30 @@
public String description;
}
+ static final class GetRecurringDepositProductsGlAccount {
+
+ private GetRecurringDepositProductsGlAccount() {}
+
+ @Schema(example = "12")
+ public Long id;
+ @Schema(example = "savings control")
+ public String name;
+ @Schema(example = "2000001")
+ public String glCode;
+ }
+
static final class GetRecurringDepositProductsProductIdAccountingMappings {
private GetRecurringDepositProductsProductIdAccountingMappings() {}
- static final class GetRecurringDepositProductsProductIdSavingsReferenceAccount {
-
- private GetRecurringDepositProductsProductIdSavingsReferenceAccount() {}
-
- @Schema(example = "12")
- public Long id;
- @Schema(example = "savings ref")
- public String name;
- @Schema(example = "20")
- public String glCode;
- }
-
- static final class GetRecurringDepositProductsProductIdIncomeFromFeeAccount {
-
- private GetRecurringDepositProductsProductIdIncomeFromFeeAccount() {}
-
- @Schema(example = "16")
- public Long id;
- @Schema(example = "income from savings fee")
- public String name;
- @Schema(example = "24")
- public String glCode;
- }
-
- static final class GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount {
-
- private GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount() {}
-
- @Schema(example = "17")
- public Long id;
- @Schema(example = "income from sav penalties")
- public String name;
- @Schema(example = "25")
- public String glCode;
- }
-
- static final class GetRecurringDepositProductsProductIdInterestOnSavingsAccount {
-
- private GetRecurringDepositProductsProductIdInterestOnSavingsAccount() {}
-
- @Schema(example = "15")
- public Long id;
- @Schema(example = "interest on savings")
- public String name;
- @Schema(example = "23")
- public String glCode;
- }
-
- static final class GetRecurringDepositProductsProductIdSavingsControlAccount {
-
- private GetRecurringDepositProductsProductIdSavingsControlAccount() {}
-
- @Schema(example = "13")
- public Long id;
- @Schema(example = "savings ref tool kit")
- public String name;
- @Schema(example = "21")
- public String glCode;
- }
-
- static final class GetRecurringDepositProductsProductIdTransfersInSuspenseAccount {
-
- private GetRecurringDepositProductsProductIdTransfersInSuspenseAccount() {}
-
- @Schema(example = "14")
- public Long id;
- @Schema(example = "saving transfers")
- public String name;
- @Schema(example = "22")
- public String glCode;
- }
-
- public GetRecurringDepositProductsProductIdSavingsReferenceAccount savingsReferenceAccount;
- public GetRecurringDepositProductsProductIdIncomeFromFeeAccount incomeFromFeeAccount;
- public GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount incomeFromPenaltyAccount;
- public GetRecurringDepositProductsProductIdInterestOnSavingsAccount interestOnSavingsAccount;
- public GetRecurringDepositProductsProductIdSavingsControlAccount savingsControlAccount;
- public GetRecurringDepositProductsProductIdTransfersInSuspenseAccount transfersInSuspenseAccount;
+ public GetRecurringDepositProductsGlAccount incomeFromFeeAccount;
+ public GetRecurringDepositProductsGlAccount incomeFromPenaltyAccount;
+ public GetRecurringDepositProductsGlAccount interestOnSavingsAccount;
+ public GetRecurringDepositProductsGlAccount savingsControlAccount;
+ public GetRecurringDepositProductsGlAccount transfersInSuspenseAccount;
+ public GetRecurringDepositProductsGlAccount feeReceivableAccount;
+ public GetRecurringDepositProductsGlAccount penaltyReceivableAccount;
+ public GetRecurringDepositProductsGlAccount interestPayableAccount;
}
static final class GetRecurringDepositProductsProductIdFeeToIncomeAccountMappings {
@@ -480,7 +422,7 @@
}
public GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge;
- public GetRecurringDepositProductsProductIdAccountingMappings.GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount;
+ public GetRecurringDepositProductsGlAccount incomeAccount;
}
static final class GetRecurringDepositProductsProductIdPreClosurePenalInterestOnType {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java
index b44afc2..f2fe2ef 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java
@@ -226,88 +226,34 @@
private GetSavingsProductsProductIdResponse() {}
+ static final class GetSavingsProductsGlAccount {
+
+ private GetSavingsProductsGlAccount() {}
+
+ @Schema(example = "12")
+ public Integer id;
+ @Schema(example = "savings control")
+ public String name;
+ @Schema(example = "2000001")
+ public String glCode;
+ }
+
static final class GetSavingsProductsAccountingMappings {
private GetSavingsProductsAccountingMappings() {}
- static final class GetSavingsProductsSavingsReferenceAccount {
-
- private GetSavingsProductsSavingsReferenceAccount() {}
-
- @Schema(example = "12")
- public Integer id;
- @Schema(example = "savings ref")
- public String name;
- @Schema(example = "20")
- public String glCode;
- }
-
- static final class GetSavingsProductsIncomeFromFeeAccount {
-
- private GetSavingsProductsIncomeFromFeeAccount() {}
-
- @Schema(example = "16")
- public Integer id;
- @Schema(example = "income from savings fee")
- public String name;
- @Schema(example = "24")
- public String glCode;
- }
-
- static final class GetSavingsProductsIncomeFromPenaltyAccount {
-
- private GetSavingsProductsIncomeFromPenaltyAccount() {}
-
- @Schema(example = "17")
- public Integer id;
- @Schema(example = "income from sav penalties")
- public String name;
- @Schema(example = "25")
- public String glCode;
- }
-
- static final class GetSavingsProductsInterestOnSavingsAccount {
-
- private GetSavingsProductsInterestOnSavingsAccount() {}
-
- @Schema(example = "15")
- public Integer id;
- @Schema(example = "interest on savings")
- public String name;
- @Schema(example = "23")
- public String glCode;
- }
-
- static final class GetSavingsProductsSavingsControlAccount {
-
- private GetSavingsProductsSavingsControlAccount() {}
-
- @Schema(example = "13")
- public Integer id;
- @Schema(example = "savings ref tool kit")
- public String name;
- @Schema(example = "21")
- public String glCode;
- }
-
- static final class GetSavingsProductsTransfersInSuspenseAccount {
-
- private GetSavingsProductsTransfersInSuspenseAccount() {}
-
- @Schema(example = "14")
- public Integer id;
- @Schema(example = "saving transfers")
- public String name;
- @Schema(example = "22")
- public String glCode;
- }
-
- public GetSavingsProductsSavingsReferenceAccount savingsReferenceAccount;
- public GetSavingsProductsIncomeFromFeeAccount incomeFromFeeAccount;
- public GetSavingsProductsIncomeFromPenaltyAccount incomeFromPenaltyAccount;
- public GetSavingsProductsInterestOnSavingsAccount interestOnSavingsAccount;
- public GetSavingsProductsSavingsControlAccount savingsControlAccount;
- public GetSavingsProductsTransfersInSuspenseAccount transfersInSuspenseAccount;
+ public GetSavingsProductsGlAccount savingsReferenceAccount;
+ public GetSavingsProductsGlAccount overdraftPortfolioControl;
+ public GetSavingsProductsGlAccount feeReceivableAccount;
+ public GetSavingsProductsGlAccount penaltyReceivableAccount;
+ public GetSavingsProductsGlAccount incomeFromFeeAccount;
+ public GetSavingsProductsGlAccount incomeFromPenaltyAccount;
+ public GetSavingsProductsGlAccount incomeFromInterest;
+ public GetSavingsProductsGlAccount interestOnSavingsAccount;
+ public GetSavingsProductsGlAccount writeOffAccount;
+ public GetSavingsProductsGlAccount savingsControlAccount;
+ public GetSavingsProductsGlAccount transfersInSuspenseAccount;
+ public GetSavingsProductsGlAccount interestPayableAccount;
}
static final class GetSavingsProductsPaymentChannelToFundSourceMappings {
@@ -393,7 +339,7 @@
}
public GetSavingsProductsPenaltyToIncomeAccountMappingsCharge charge;
- public GetSavingsProductsAccountingMappings.GetSavingsProductsIncomeFromPenaltyAccount incomeAccount;
+ public GetSavingsProductsGlAccount incomeAccount;
}
@Schema(example = "1")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java
index 96380a8..a30f97b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java
@@ -71,35 +71,33 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
-import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType;
+import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public class DepositProductDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private final InterestRateChartDataValidator chartDataValidator;
-
- @Autowired
- public DepositProductDataValidator(FromJsonHelper fromApiJsonHelper, InterestRateChartDataValidator chartDataValidator) {
- this.fromApiJsonHelper = fromApiJsonHelper;
- this.chartDataValidator = chartDataValidator;
- }
+ private final SavingsProductAccountingDataValidator savingsProductAccountingDataValidator;
public void validateForFixedDepositCreate(final String json) {
if (StringUtils.isBlank(json)) {
@@ -115,7 +113,7 @@
final JsonElement element = this.fromApiJsonHelper.parse(json);
- validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator);
+ validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator, DepositAccountType.FIXED_DEPOSIT);
validatePreClosureDetailForCreate(element, baseDataValidator);
@@ -168,7 +166,7 @@
final JsonElement element = this.fromApiJsonHelper.parse(json);
- validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator);
+ validateDepositDetailForCreate(element, this.fromApiJsonHelper, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT);
validatePreClosureDetailForCreate(element, baseDataValidator);
@@ -212,7 +210,7 @@
}
private void validateDepositDetailForCreate(final JsonElement element, final FromJsonHelper fromApiJsonHelper,
- final DataValidatorBuilder baseDataValidator) {
+ final DataValidatorBuilder baseDataValidator, final DepositAccountType accountType) {
final String name = fromApiJsonHelper.extractStringNamed(nameParamName, element);
baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
@@ -258,13 +256,6 @@
baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull()
.isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
- /*
- * if (fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName , element)) { final BigDecimal
- * minOpeningBalance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed (minRequiredOpeningBalanceParamName,
- * element); baseDataValidator.reset ().parameter(minRequiredOpeningBalanceParamName
- * ).value(minOpeningBalance).zeroOrPositiveAmount(); }
- */
-
if (fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
final Integer lockinPeriodFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
@@ -302,40 +293,16 @@
final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
- if (isCashBasedAccounting(accountingRuleType)) {
+ Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(SavingsApiConstants.isDormancyTrackingActiveParamName,
+ element);
+ if (isDormancyActive == null) {
+ isDormancyActive = false;
+ }
- final Long savingsControlAccountId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
- .notNull().integerGreaterThanZero();
-
- final Long savingsReferenceAccountId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId)
- .notNull().integerGreaterThanZero();
-
- final Long transfersInSuspenseAccountId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
- .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
-
- final Long interestOnSavingsAccountId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue())
- .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
-
- final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
- element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull()
- .integerGreaterThanZero();
-
- final Long incomeFromPenaltyId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
- .notNull().integerGreaterThanZero();
-
- validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element);
- validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element);
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
+ savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
+ baseDataValidator, accountType);
}
validateTaxWithHoldingParams(baseDataValidator, element, true);
@@ -542,8 +509,8 @@
baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
.ignoreIfNull().integerGreaterThanZero();
- validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element);
- validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element);
+ savingsProductAccountingDataValidator.validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+ savingsProductAccountingDataValidator.validateChargeToIncomeAccountMappings(baseDataValidator, element);
validateTaxWithHoldingParams(baseDataValidator, element, false);
}
@@ -611,76 +578,6 @@
}
}
- private boolean isCashBasedAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
- }
-
- /**
- * Validation for advanced accounting options
- */
- private void validatePaymentChannelFundSourceMappings(final FromJsonHelper fromApiJsonHelper,
- final DataValidatorBuilder baseDataValidator, final JsonElement element) {
- if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
- final JsonArray paymentChannelMappingArray = fromApiJsonHelper
- .extractJsonArrayNamed(SavingProductAccountingParams.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(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong();
- final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue())
- .getAsLong();
- baseDataValidator.reset()
- .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
- + SavingProductAccountingParams.PAYMENT_TYPE.toString())
- .value(paymentTypeId).notNull().integerGreaterThanZero();
- baseDataValidator.reset()
- .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
- + SavingProductAccountingParams.FUND_SOURCE.getValue())
- .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero();
- i++;
- } while (i < paymentChannelMappingArray.size());
- }
- }
- }
-
- private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper, final DataValidatorBuilder baseDataValidator,
- final JsonElement element) {
- // validate for both fee and penalty charges
- validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true);
- validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true);
- }
-
- private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper, final DataValidatorBuilder baseDataValidator,
- final JsonElement element, final boolean isPenalty) {
- String parameterName;
- if (isPenalty) {
- parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
- } else {
- parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue();
- }
-
- if (fromApiJsonHelper.parameterExists(parameterName, element)) {
- final JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
- if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
- int i = 0;
- do {
- final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
- final Long chargeId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(),
- jsonObject);
- final Long incomeAccountId = fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject);
- baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue())
- .value(chargeId).notNull().integerGreaterThanZero();
- baseDataValidator.reset()
- .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue())
- .value(incomeAccountId).notNull().integerGreaterThanZero();
- i++;
- } while (i < chargeToIncomeAccountMappingArray.size());
- }
- }
- }
-
public void validateRecurringDetailForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
index fd95092..a53e5ec 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
@@ -43,6 +43,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -55,15 +56,14 @@
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
-import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.exception.TransactionBeforePivotDateNotAllowed;
import org.apache.fineract.useradministration.domain.AppUser;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public class SavingsAccountTransactionDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@@ -71,15 +71,6 @@
Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName, SavingsApiConstants.localeParamName,
transactionAmountParamName, lienAllowedParamName, SavingsApiConstants.reasonForBlockParamName));
private final ConfigurationDomainService configurationDomainService;
- private final SavingsAccountAssembler savingAccountAssembler;
-
- @Autowired
- public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper,
- final ConfigurationDomainService configurationDomainService, final SavingsAccountAssembler savingAccountAssembler) {
- this.fromApiJsonHelper = fromApiJsonHelper;
- this.configurationDomainService = configurationDomainService;
- this.savingAccountAssembler = savingAccountAssembler;
- }
public void validateTransactionWithPivotDate(final LocalDate transactionDate, final SavingsAccount savingsAccount) {
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
index 3389a01..822a08c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
@@ -99,7 +99,6 @@
import org.springframework.util.CollectionUtils;
@RequiredArgsConstructor
-
public class DepositAccountReadPlatformServiceImpl implements DepositAccountReadPlatformService {
private static final FixedDepositAccountMapper FIXED_DEPOSIT_ACCOUNT_MAPPER = new FixedDepositAccountMapper();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index d094213..4a983e3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -611,7 +611,6 @@
final BigDecimal outstandingChargeAmount = null;
final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
final boolean reversed = rs.getBoolean("reversed");
- final boolean isManualTransaction = rs.getBoolean("manualTransaction");
final Long officeId = rs.getLong("officeId");
final BigDecimal cumulativeBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "cumulativeBalance");
@@ -684,59 +683,6 @@
}
return savingsAccountDataList;
- // final String productName = rs.getString("productName");
-
- // final String fieldOfficerName = rs.getString("fieldOfficerName");
-
- // final String submittedByUsername = rs.getString("submittedByUsername");
- // final String submittedByFirstname = rs.getString("submittedByFirstname");
- // final String submittedByLastname = rs.getString("submittedByLastname");
- //
-
- // final String rejectedByUsername = rs.getString("rejectedByUsername");
- // final String rejectedByFirstname = rs.getString("rejectedByFirstname");
- // final String rejectedByLastname = rs.getString("rejectedByLastname");
- //
-
- // final String withdrawnByUsername = rs.getString("withdrawnByUsername");
- // final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
- // final String withdrawnByLastname = rs.getString("withdrawnByLastname");
-
- // final String approvedByUsername = rs.getString("approvedByUsername");
- // final String approvedByFirstname = rs.getString("approvedByFirstname");
- // final String approvedByLastname = rs.getString("approvedByLastname");
-
- // final String activatedByUsername = rs.getString("activatedByUsername");
- // final String activatedByFirstname = rs.getString("activatedByFirstname");
- // final String activatedByLastname = rs.getString("activatedByLastname");
-
- // final String closedByUsername = rs.getString("closedByUsername");
- // final String closedByFirstname = rs.getString("closedByFirstname");
- // final String closedByLastname = rs.getString("closedByLastname");
-
- // final String currencyName = rs.getString("currencyName");
- // final String currencyNameCode = rs.getString("currencyNameCode");
- // final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
-
- /*
- * final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
- *
- * EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue =
- * JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) {
- * withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
- */
-
- /*
- * final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
- *
- * MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs,
- * "annualFeeOnMonth"); final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); if
- * (annualFeeAmount != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new
- * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
- *
- * final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
- */
-
}
}
@@ -792,20 +738,12 @@
sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
- // sqlBuilder.append("sa.withdrawal_fee_amount as
- // withdrawalFeeAmount,");
- // sqlBuilder.append("sa.withdrawal_fee_type_enum as
- // withdrawalFeeTypeEnum, ");
+
sqlBuilder.append("sa.allow_overdraft as allowOverdraft, ");
sqlBuilder.append("sa.overdraft_limit as overdraftLimit, ");
sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
- // sqlBuilder.append("sa.annual_fee_amount as annualFeeAmount,");
- // sqlBuilder.append("sa.annual_fee_on_month as annualFeeOnMonth,
- // ");
- // sqlBuilder.append("sa.annual_fee_on_day as annualFeeOnDay, ");
- // sqlBuilder.append("sa.annual_fee_next_due_date as
- // annualFeeNextDueDate, ");
+
sqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
@@ -981,14 +919,6 @@
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
}
- /*
- * final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
- *
- * EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue =
- * JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) {
- * withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
- */
-
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
@@ -1004,16 +934,6 @@
final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit");
final boolean lienAllowed = rs.getBoolean("lienAllowed");
- /*
- * final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
- *
- * MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs,
- * "annualFeeOnMonth"); final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); if
- * (annualFeeAmount != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new
- * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
- *
- * final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
- */
final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java
index aa201de..f4fb623 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java
@@ -59,11 +59,7 @@
SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY),
SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY),
SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL),
- SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL)
- // //
- // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.NO_COMPOUNDING_SIMPLE_INTEREST)
- // //
- );
+ SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.ANNUAL));
return allowedOptions;
}
diff --git a/fineract-savings/dependencies.gradle b/fineract-savings/dependencies.gradle
index bd2a158..467fc30 100644
--- a/fineract-savings/dependencies.gradle
+++ b/fineract-savings/dependencies.gradle
@@ -25,6 +25,7 @@
// implementation dependencies are directly used (compiled against) in src/main (and src/test)
//
implementation(project(path: ':fineract-core'))
+ implementation(project(path: ':fineract-accounting'))
implementation(
'org.springframework.boot:spring-boot-starter-web',
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java
new file mode 100644
index 0000000..1e527e7
--- /dev/null
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java
@@ -0,0 +1,185 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.data;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
+import org.apache.fineract.accounting.common.AccountingValidations;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class SavingsProductAccountingDataValidator {
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ public void evaluateProductAccountingData(final Integer accountingRuleType, final boolean isDormancyActive, final JsonElement element,
+ DataValidatorBuilder baseDataValidator, final DepositAccountType accountType) {
+ // GL Accounts for Cash or Accrual Periodic
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
+
+ final Long savingsControlAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
+ .notNull().integerGreaterThanZero();
+
+ final Long savingsReferenceAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId)
+ .notNull().integerGreaterThanZero();
+
+ final Long transfersInSuspenseAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
+ .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
+
+ final Long interestOnSavingsAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue())
+ .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
+
+ final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
+ element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull()
+ .integerGreaterThanZero();
+
+ final Long incomeFromPenaltyId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
+ .notNull().integerGreaterThanZero();
+
+ if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
+ final Long overdraftControlAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
+ .value(overdraftControlAccountId).notNull().integerGreaterThanZero();
+
+ final Long incomeFromInterest = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest)
+ .notNull().integerGreaterThanZero();
+
+ final Long writtenoff = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
+ element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff).notNull()
+ .integerGreaterThanZero();
+ }
+
+ if (isDormancyActive) {
+ final Long escheatLiabilityAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue())
+ .value(escheatLiabilityAccountId).notNull().integerGreaterThanZero();
+ }
+ }
+
+ // GL Accounts for Accrual Period only
+ if (AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
+ final Long feeReceivableAccountId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.FEES_RECEIVABLE.getValue(),
+ element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.FEES_RECEIVABLE.getValue()).value(feeReceivableAccountId)
+ .notNull().integerGreaterThanZero();
+
+ final Long penaltyReceivableAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue())
+ .value(penaltyReceivableAccountId).notNull().integerGreaterThanZero();
+
+ final Long interestPayableAccountId = fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), element);
+ baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_PAYABLE.getValue()).value(interestPayableAccountId)
+ .notNull().integerGreaterThanZero();
+ }
+
+ validatePaymentChannelFundSourceMappings(baseDataValidator, element);
+ validateChargeToIncomeAccountMappings(baseDataValidator, element);
+ }
+
+ /**
+ * Validation for advanced accounting options
+ */
+ public void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+ if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
+ final JsonArray paymentChannelMappingArray = fromApiJsonHelper
+ .extractJsonArrayNamed(SavingProductAccountingParams.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(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong();
+ final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue())
+ .getAsLong();
+ baseDataValidator.reset()
+ .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+ + SavingProductAccountingParams.PAYMENT_TYPE.toString())
+ .value(paymentTypeId).notNull().integerGreaterThanZero();
+ baseDataValidator.reset()
+ .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
+ + SavingProductAccountingParams.FUND_SOURCE.getValue())
+ .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero();
+ i++;
+ } while (i < paymentChannelMappingArray.size());
+ }
+ }
+ }
+
+ public void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
+ // validate for both fee and penalty charges
+ validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+ validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
+ }
+
+ private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element,
+ final boolean isPenalty) {
+ String parameterName;
+ if (isPenalty) {
+ parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
+ } else {
+ parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue();
+ }
+
+ if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
+ final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+ if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
+ int i = 0;
+ do {
+ final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
+ final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(),
+ jsonObject);
+ final Long incomeAccountId = this.fromApiJsonHelper
+ .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject);
+ baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue())
+ .value(chargeId).notNull().integerGreaterThanZero();
+ baseDataValidator.reset()
+ .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue())
+ .value(incomeAccountId).notNull().integerGreaterThanZero();
+ i++;
+ } while (i < chargeToIncomeAccountMappingArray.size());
+ }
+ }
+ }
+
+}
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java
index 368c1a8..bac9114 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java
@@ -50,9 +50,7 @@
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
-import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -64,28 +62,31 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
-import org.apache.fineract.accounting.common.AccountingRuleType;
+import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public class SavingsProductDataValidator {
private final FromJsonHelper fromApiJsonHelper;
+ private final SavingsProductAccountingDataValidator savingsProductAccountingDataValidator;
private static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
SavingsApiConstants.localeParamName, SavingsApiConstants.monthDayFormatParamName, nameParamName, shortNameParamName,
descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, inMultiplesOfParamName,
@@ -95,11 +96,12 @@
SavingsApiConstants.withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName,
SavingsApiConstants.accountingRuleParamName, SavingsApiConstants.chargesParamName,
SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
- SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
+ SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
+ SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), SavingProductAccountingParams.INTEREST_PAYABLE.getValue(),
SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), isDormancyTrackingActiveParamName, daysToDormancyParamName,
@@ -109,11 +111,6 @@
SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName,
minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName));
- @Autowired
- public SavingsProductDataValidator(final FromJsonHelper fromApiJsonHelper) {
- this.fromApiJsonHelper = fromApiJsonHelper;
- }
-
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) {
@@ -207,28 +204,6 @@
}
}
- /*
- * if (this.fromApiJsonHelper.parameterExists(withdrawalFeeAmountParamName, element)) {
- *
- * final BigDecimal withdrawalFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed
- * (withdrawalFeeAmountParamName, element); baseDataValidator.reset().parameter
- * (withdrawalFeeAmountParamName).value (withdrawalFeeAmount).zeroOrPositiveAmount();
- *
- * if (withdrawalFeeAmount != null) { final Integer withdrawalFeeType =
- * this.fromApiJsonHelper.extractIntegerSansLocaleNamed( withdrawalFeeTypeParamName, element);
- * baseDataValidator.reset().parameter (withdrawalFeeTypeParamName).value(withdrawalFeeType)
- * .isOneOfTheseValues(SavingsWithdrawalFeesType.integerValues()); } }
- *
- * if (this.fromApiJsonHelper.parameterExists(withdrawalFeeTypeParamName, element)) { final Integer
- * withdrawalFeeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed (withdrawalFeeTypeParamName,
- * element); baseDataValidator.reset().parameter
- * (withdrawalFeeTypeParamName).value(withdrawalFeeType).ignoreIfNull() .isOneOfTheseValues(1, 2);
- *
- * if (withdrawalFeeType != null) { final BigDecimal withdrawalFeeAmount =
- * this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( withdrawalFeeAmountParamName, element);
- * baseDataValidator.reset().parameter (withdrawalFeeAmountParamName).value(withdrawalFeeAmount).notNull()
- * .zeroOrPositiveAmount(); } }
- */
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper
.extractBooleanNamed(withdrawalFeeForTransfersParamName, element);
@@ -256,9 +231,12 @@
}
// dormancy
- final Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
+ Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
+ if (isDormancyActive == null) {
+ isDormancyActive = false;
+ }
- if (null != isDormancyActive && isDormancyActive) {
+ if (isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element);
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
@@ -278,62 +256,10 @@
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
- if (isCashBasedAccounting(accountingRuleType)) {
-
- final Long savingsControlAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
- .notNull().integerGreaterThanZero();
-
- final Long savingsReferenceAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId)
- .notNull().integerGreaterThanZero();
-
- final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
- .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
-
- final Long interestOnSavingsAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue())
- .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero();
-
- final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
- element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull()
- .integerGreaterThanZero();
-
- final Long incomeFromPenaltyId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
- .notNull().integerGreaterThanZero();
-
- final Long overdraftControlAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
- .value(overdraftControlAccountId).notNull().integerGreaterThanZero();
-
- final Long incomeFromInterest = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest)
- .notNull().integerGreaterThanZero();
-
- final Long writtenoff = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
- element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff).notNull()
- .integerGreaterThanZero();
-
- if (null != isDormancyActive && isDormancyActive) {
- final Long escheatLiabilityAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue())
- .value(escheatLiabilityAccountId).notNull().integerGreaterThanZero();
- }
-
- validatePaymentChannelFundSourceMappings(baseDataValidator, element);
- validateChargeToIncomeAccountMappings(baseDataValidator, element);
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
+ savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
+ baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT);
}
validateOverdraftParams(baseDataValidator, element);
@@ -457,56 +383,21 @@
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
}
- final Long savingsControlAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long savingsReferenceAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue()).value(transfersInSuspenseAccountId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long interestOnSavingsAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue()).value(interestOnSavingsAccountId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long incomeFromFeeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
- element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).ignoreIfNull()
- .integerGreaterThanZero();
-
- final Long incomeFromPenaltyId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long overdraftAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue()).value(overdraftAccountId)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long incomeFromInterest = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest)
- .ignoreIfNull().integerGreaterThanZero();
-
- final Long writtenoff = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
- element);
- baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff).ignoreIfNull()
- .integerGreaterThanZero();
+ // accounting related data validation
+ final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
+ baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
// dormancy
final Boolean isDormancyActive = this.fromApiJsonHelper.parameterExists(isDormancyTrackingActiveParamName, element)
? this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element)
: product.isDormancyTrackingActive();
+ if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
+ || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
+ savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
+ baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT);
+ }
+
if (null != isDormancyActive && isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.parameterExists(daysToInactiveParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element)
@@ -536,8 +427,6 @@
}
}
- validatePaymentChannelFundSourceMappings(baseDataValidator, element);
- validateChargeToIncomeAccountMappings(baseDataValidator, element);
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
@@ -558,74 +447,6 @@
}
}
- private boolean isCashBasedAccounting(final Integer accountingRuleType) {
- return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType);
- }
-
- /**
- * Validation for advanced accounting options
- */
- private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
- if (this.fromApiJsonHelper.parameterExists(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
- final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
- .extractJsonArrayNamed(SavingProductAccountingParams.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(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong();
- final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue())
- .getAsLong();
- baseDataValidator.reset()
- .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
- + SavingProductAccountingParams.PAYMENT_TYPE.toString())
- .value(paymentTypeId).notNull().integerGreaterThanZero();
- baseDataValidator.reset()
- .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]."
- + SavingProductAccountingParams.FUND_SOURCE.getValue())
- .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero();
- i++;
- } while (i < paymentChannelMappingArray.size());
- }
- }
- }
-
- private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
- // validate for both fee and penalty charges
- validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
- validateChargeToIncomeAccountMappings(baseDataValidator, element, true);
- }
-
- private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element,
- final boolean isPenalty) {
- String parameterName;
- if (isPenalty) {
- parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue();
- } else {
- parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue();
- }
-
- if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
- final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
- if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) {
- int i = 0;
- do {
- final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject();
- final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(),
- jsonObject);
- final Long incomeAccountId = this.fromApiJsonHelper
- .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject);
- baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue())
- .value(chargeId).notNull().integerGreaterThanZero();
- baseDataValidator.reset()
- .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue())
- .value(incomeAccountId).notNull().integerGreaterThanZero();
- i++;
- } while (i < chargeToIncomeAccountMappingArray.size());
- }
- }
- }
-
private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) {
final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
index aca2e09..47a8085 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java
@@ -46,6 +46,8 @@
import java.util.Map;
import java.util.TimeZone;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
+import org.apache.fineract.client.models.GetRecurringDepositProductsProductIdResponse;
+import org.apache.fineract.client.models.GetSavingsProductsProductIdResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
@@ -141,6 +143,7 @@
this.journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
this.schedulerJobHelper = new SchedulerJobHelper(requestSpec);
this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec, responseSpec);
+ this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
this.tenantTimeZone = TimeZone.getTimeZone(Utils.TENANT_TIME_ZONE);
}
@@ -281,7 +284,6 @@
@Test
public void checkAccountingWithSavingsFlow() {
- this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
@@ -374,9 +376,106 @@
}
@Test
+ public void checkAccountingWithSavingsFlowUsingAccrualAccounting() {
+ final Account assetAccount = this.accountHelper.createAssetAccount();
+ final Account incomeAccount = this.accountHelper.createIncomeAccount();
+ final Account expenseAccount = this.accountHelper.createExpenseAccount();
+ final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+
+ final Integer savingsProductID = createSavingsProductWithAccrualAccounting(MINIMUM_OPENING_BALANCE, assetAccount, incomeAccount,
+ expenseAccount, liabilityAccount);
+ final GetSavingsProductsProductIdResponse savingsProductsResponse = SavingsProductHelper.getSavingsProductById(requestSpec,
+ responseSpec, savingsProductID);
+ Assertions.assertNotNull(savingsProductsResponse);
+ Assertions.assertNotNull(savingsProductsResponse.getAccountingMappings());
+ Assertions.assertNotNull(savingsProductsResponse.getAccountingMappings().getSavingsControlAccount());
+ Assertions.assertNotNull(savingsProductsResponse.getAccountingMappings().getInterestPayableAccount());
+
+ final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, DATE_OF_JOINING);
+ final Integer savingsID = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
+
+ HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(requestSpec, responseSpec, savingsID);
+ SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
+
+ savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsID);
+ SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+
+ savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsID);
+ SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+
+ // Checking initial Account entries.
+ final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(SP_BALANCE, JournalEntry.TransactionType.DEBIT) };
+ final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(SP_BALANCE, JournalEntry.TransactionType.CREDIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, TRANSACTION_DATE, assetAccountInitialEntry);
+ this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, TRANSACTION_DATE, liablilityAccountInitialEntry);
+
+ // First Transaction-Deposit
+ this.savingsAccountHelper.depositToSavingsAccount(savingsID, DEPOSIT_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE,
+ CommonConstants.RESPONSE_RESOURCE_ID);
+ Float balance = SP_BALANCE + SP_DEPOSIT_AMOUNT;
+ HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+ assertEquals(balance, summary.get("accountBalance"), "Verifying Balance after Deposit");
+
+ LOG.info("----------------------Verifying Journal Entry after the Transaction Deposit----------------------------");
+ final JournalEntry[] assetAccountFirstTransactionEntry = {
+ new JournalEntry(SP_DEPOSIT_AMOUNT, JournalEntry.TransactionType.DEBIT) };
+ final JournalEntry[] liabililityAccountFirstTransactionEntry = {
+ new JournalEntry(SP_DEPOSIT_AMOUNT, JournalEntry.TransactionType.CREDIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, TRANSACTION_DATE, assetAccountFirstTransactionEntry);
+ this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, TRANSACTION_DATE,
+ liabililityAccountFirstTransactionEntry);
+
+ // Second Transaction-Withdrawal
+ this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsID, WITHDRAWAL_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE,
+ CommonConstants.RESPONSE_RESOURCE_ID);
+ balance -= SP_WITHDRAWAL_AMOUNT;
+ summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+ assertEquals(balance, summary.get("accountBalance"), "Verifying Balance after Withdrawal");
+
+ LOG.info("-------------------Verifying Journal Entry after the Transaction Withdrawal----------------------");
+ final JournalEntry[] assetAccountSecondTransactionEntry = {
+ new JournalEntry(SP_WITHDRAWAL_AMOUNT, JournalEntry.TransactionType.CREDIT) };
+ final JournalEntry[] liabililityAccountSecondTransactionEntry = {
+ new JournalEntry(SP_WITHDRAWAL_AMOUNT, JournalEntry.TransactionType.DEBIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, TRANSACTION_DATE, assetAccountSecondTransactionEntry);
+ this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, TRANSACTION_DATE,
+ liabililityAccountSecondTransactionEntry);
+
+ // Third Transaction-Add Charges for Withdrawal Fee
+ final Integer withdrawalChargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
+ ChargesHelper.getSavingsWithdrawalFeeJSON());
+ Assertions.assertNotNull(withdrawalChargeId);
+
+ this.savingsAccountHelper.addChargesForSavings(savingsID, withdrawalChargeId, false);
+ ArrayList<HashMap> chargesPendingState = this.savingsAccountHelper.getSavingsCharges(savingsID);
+ Assertions.assertEquals(1, chargesPendingState.size());
+ HashMap savingsChargeForPay = chargesPendingState.get(0);
+ HashMap paidCharge = this.savingsAccountHelper.getSavingsCharge(savingsID, (Integer) savingsChargeForPay.get("id"));
+ Float chargeAmount = (Float) paidCharge.get("amount");
+
+ // Withdrawal after adding Charge of type Withdrawal Fee
+ this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsID, WITHDRAWAL_AMOUNT_ADJUSTED, SavingsAccountHelper.TRANSACTION_DATE,
+ CommonConstants.RESPONSE_RESOURCE_ID);
+ summary = this.savingsAccountHelper.getSavingsSummary(savingsID);
+ balance = balance - SP_WITHDRAWAL_AMOUNT_ADJUSTED - chargeAmount;
+
+ final JournalEntry[] liabililityAccountThirdTransactionEntry = { new JournalEntry(chargeAmount, JournalEntry.TransactionType.DEBIT),
+ new JournalEntry(SP_WITHDRAWAL_AMOUNT_ADJUSTED, JournalEntry.TransactionType.DEBIT) };
+ final JournalEntry[] assetAccountThirdTransactionEntry = {
+ new JournalEntry(SP_WITHDRAWAL_AMOUNT_ADJUSTED, JournalEntry.TransactionType.CREDIT) };
+ final JournalEntry[] incomeAccountThirdTransactionEntry = { new JournalEntry(chargeAmount, JournalEntry.TransactionType.CREDIT) };
+ this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, TRANSACTION_DATE, assetAccountThirdTransactionEntry);
+ this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, TRANSACTION_DATE,
+ liabililityAccountThirdTransactionEntry);
+ this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, TRANSACTION_DATE, incomeAccountThirdTransactionEntry);
+
+ // Verifying Balance after applying Charge for Withdrawal Fee
+ assertEquals(balance, summary.get("accountBalance"), "Verifying Balance");
+ }
+
+ @Test
public void testFixedDepositAccountingFlow() {
this.accountHelper = new AccountHelper(requestSpec, responseSpec);
- this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
this.fixedDepositAccountHelper = new FixedDepositAccountHelper(requestSpec, responseSpec);
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US);
@@ -481,6 +580,12 @@
Integer recurringDepositProductId = createRecurringDepositProduct(VALID_FROM, VALID_TO, assetAccount, liabilityAccount,
incomeAccount, expenseAccount);
Assertions.assertNotNull(recurringDepositProductId);
+ final GetRecurringDepositProductsProductIdResponse recurringDepositProductsProduct = RecurringDepositProductHelper
+ .getRecurringDepositProductById(requestSpec, responseSpec, recurringDepositProductId);
+ Assertions.assertNotNull(recurringDepositProductsProduct);
+ Assertions.assertNotNull(recurringDepositProductsProduct.getAccountingMappings());
+ Assertions.assertNotNull(recurringDepositProductsProduct.getAccountingMappings().getSavingsControlAccount());
+ Assertions.assertNull(recurringDepositProductsProduct.getAccountingMappings().getInterestPayableAccount());
Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(),
VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, RecurringDepositTest.WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE);
@@ -573,6 +678,15 @@
return RecurringDepositAccountHelper.applyRecurringDepositApplication(recurringDepositApplicationJSON, requestSpec, responseSpec);
}
+ public static Integer createSavingsProductWithAccrualAccounting(final String minOpenningBalance, final Account... accounts) {
+ LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
+ final String savingsProductJSON = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily() //
+ .withInterestPostingPeriodTypeAsQuarterly() //
+ .withInterestCalculationPeriodTypeAsDailyBalance() //
+ .withMinimumOpenningBalance(minOpenningBalance).withAccountingRuleAsAccrualBased(accounts).build();
+ return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+ }
+
@Test
public void checkPeriodicAccrualAccountingFlow() throws InterruptedException, ParseException {
final Account assetAccount = this.accountHelper.createAssetAccount();
@@ -1124,7 +1238,6 @@
@Test
public void checkAccountingWithSharingFlow() {
- this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java
index ca2650a..73fab66 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java
@@ -25,6 +25,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.fineract.client.models.GetRecurringDepositProductsProductIdResponse;
+import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.slf4j.Logger;
@@ -36,6 +38,7 @@
private static final Logger LOG = LoggerFactory.getLogger(RecurringDepositProductHelper.class);
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
+ private static final Gson GSON = new JSON().getGson();
public RecurringDepositProductHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
@@ -241,6 +244,14 @@
return response;
}
+ public static GetRecurringDepositProductsProductIdResponse getRecurringDepositProductById(final RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec, final Integer productId) {
+ LOG.info("-------------------- RETRIEVING RECURRING DEPOSIT PRODUCT BY ID --------------------------");
+ final String GET_RD_PRODUCT_BY_ID_URL = RECURRING_DEPOSIT_PRODUCT_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER;
+ final String response = Utils.performServerGet(requestSpec, responseSpec, GET_RD_PRODUCT_BY_ID_URL);
+ return GSON.fromJson(response, GetRecurringDepositProductsProductIdResponse.class);
+ }
+
public static HashMap retrieveRecurringDepositProductById(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String productId) {
LOG.info("-------------------- RETRIEVING RECURRING DEPOSIT PRODUCT BY ID --------------------------");
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
index 64234b5..a22ba29 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
@@ -26,6 +26,8 @@
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
+import org.apache.fineract.client.models.GetSavingsProductsProductIdResponse;
+import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.slf4j.Logger;
@@ -37,6 +39,7 @@
private static final Logger LOG = LoggerFactory.getLogger(SavingsProductHelper.class);
private static final String SAVINGS_PRODUCT_URL = "/fineract-provider/api/v1/savingsproducts";
private static final String CREATE_SAVINGS_PRODUCT_URL = SAVINGS_PRODUCT_URL + "?" + Utils.TENANT_IDENTIFIER;
+ private static final Gson GSON = new JSON().getGson();
private static final String LOCALE = "en_GB";
private static final String DIGITS_AFTER_DECIMAL = "4";
@@ -56,6 +59,7 @@
private static final String DAYS_365 = "365";
private static final String NONE = "1";
private static final String CASH_BASED = "2";
+ private static final String ACCRUAL_PERIODIC = "3";
private String nameOfSavingsProduct = Utils.uniqueRandomStringGenerator("SAVINGS_PRODUCT_", 6);
private String shortName = Utils.uniqueRandomStringGenerator("", 4);
@@ -143,6 +147,9 @@
if (this.accountingRule.equals(CASH_BASED)) {
map.putAll(getAccountMappingForCashBased());
}
+ if (this.accountingRule.equals(ACCRUAL_PERIODIC)) {
+ map.putAll(getAccountMappingForAccrualBased());
+ }
if (this.isDormancyTrackingActive) {
map.put("isDormancyTrackingActive", Boolean.toString(this.isDormancyTrackingActive));
map.put("daysToInactive", this.daysToInactive);
@@ -216,6 +223,12 @@
return this;
}
+ public SavingsProductHelper withAccountingRuleAsAccrualBased(final Account[] account_list) {
+ this.accountingRule = ACCRUAL_PERIODIC;
+ this.accountList = account_list;
+ return this;
+ }
+
public SavingsProductHelper withAccountingRuleAsCashBased(final Account[] account_list) {
this.accountingRule = CASH_BASED;
this.accountList = account_list;
@@ -306,6 +319,39 @@
return map;
}
+ private Map<String, String> getAccountMappingForAccrualBased() {
+ final Map<String, String> map = new HashMap<>();
+ if (accountList != null) {
+ for (int i = 0; i < this.accountList.length; i++) {
+ if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+ final String ID = this.accountList[i].getAccountID().toString();
+ map.put("savingsReferenceAccountId", ID);
+ map.put("overdraftPortfolioControlId", ID);
+ map.put("feesReceivableAccountId", ID);
+ map.put("penaltiesReceivableAccountId", ID);
+ }
+ if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+ final String ID = this.accountList[i].getAccountID().toString();
+ map.put("savingsControlAccountId", ID);
+ map.put("transfersInSuspenseAccountId", ID);
+ map.put("interestPayableAccountId", ID);
+ }
+ if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+ final String ID = this.accountList[i].getAccountID().toString();
+ map.put("interestOnSavingsAccountId", ID);
+ map.put("writeOffAccountId", ID);
+ }
+ if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+ final String ID = this.accountList[i].getAccountID().toString();
+ map.put("incomeFromFeeAccountId", ID);
+ map.put("incomeFromPenaltyAccountId", ID);
+ map.put("incomeFromInterestId", ID);
+ }
+ }
+ }
+ return map;
+ }
+
public static Integer createSavingsProduct(final String savingsProductJSON, final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) {
return Utils.performServerPost(requestSpec, responseSpec, CREATE_SAVINGS_PRODUCT_URL, savingsProductJSON, "resourceId");
@@ -326,4 +372,13 @@
this.daysToEscheat = "90";
return this;
}
+
+ public static GetSavingsProductsProductIdResponse getSavingsProductById(final RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec, final Integer productId) {
+ LOG.info("-------------------- RETRIEVING SAVINGS DEPOSIT PRODUCT BY ID --------------------------");
+ final String GET_PRODUCT_BY_ID_URL = SAVINGS_PRODUCT_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER;
+ final String response = Utils.performServerGet(requestSpec, responseSpec, GET_PRODUCT_BY_ID_URL);
+ return GSON.fromJson(response, GetSavingsProductsProductIdResponse.class);
+ }
+
}