FINERACT-1960: GL account mappings to use Savings product with Accrual accounting

This commit is contained in:
Jose Alberto Hernandez
2024-02-26 15:25:28 +01:00
committed by Adam Saghy
parent 051b37c4b3
commit 6ee881b89a
29 changed files with 1278 additions and 796 deletions
@@ -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);
}
}
@@ -31,7 +31,7 @@ import org.apache.commons.lang3.StringUtils;
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 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
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 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
}
if (isAccrualBasedAccounting(accountingRuleType)) {
if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
@@ -168,7 +169,7 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
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 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
}
// 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 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
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 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
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.",
@@ -25,7 +25,9 @@ import java.util.LinkedHashMap;
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.LoanProductAcco
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.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService {
@@ -106,7 +110,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
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 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
}
}
} 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 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
@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 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
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;
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.accounting.producttoaccountmapping.service;
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 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
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 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
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;
@@ -277,6 +277,54 @@ public final class AccountingConstants {
}
}
/***
* 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
***/
@@ -298,7 +346,10 @@ public final class AccountingConstants {
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 @@ public final class AccountingConstants {
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;
@@ -218,8 +218,10 @@ public final class DepositsApiConstants {
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));
@@ -40,6 +40,7 @@ public enum SavingsAccountTransactionType {
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 @@ public enum SavingsAccountTransactionType {
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();
@@ -49,6 +49,7 @@ public class SavingsAccountTransactionEnumData implements Serializable {
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 @@ public class SavingsAccountTransactionEnumData implements Serializable {
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
@@ -141,6 +141,10 @@ public final class SavingsEnumerations {
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");
@@ -40,6 +40,9 @@ public class AccountingProcessorForSavingsFactory {
if (savingsDTO.isCashBasedAccountingEnabled()) {
accountingProcessorForSavings = this.applicationContext.getBean("cashBasedAccountingProcessorForSavings",
AccountingProcessorForSavings.class);
} else if (savingsDTO.isAccrualBasedAccountingEnabled()) {
accountingProcessorForSavings = this.applicationContext.getBean("accrualBasedAccountingProcessorForSavings",
AccountingProcessorForSavings.class);
}
return accountingProcessorForSavings;
@@ -30,6 +30,7 @@ import org.apache.commons.lang3.StringUtils;
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 @@ public class AccountingProcessorHelper {
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 class AccountingProcessorHelper {
}
}
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 class AccountingProcessorHelper {
}
}
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);
}
@@ -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);
}
}
}
}
@@ -27,6 +27,7 @@ import java.util.Locale;
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 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
}
}
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 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
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;
@@ -359,6 +359,8 @@ public class LoanScheduleAssembler {
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 @@ public class LoanScheduleAssembler {
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();
@@ -33,10 +33,9 @@ import java.util.Locale;
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.domain.RecalculationFrequencyTy
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public final class LoanProductDataValidator {
@@ -613,7 +611,8 @@ public final class LoanProductDataValidator {
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 @@ public final class LoanProductDataValidator {
}
if (isAccrualBasedAccounting(accountingRuleType)) {
if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
final Long receivableInterestAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element);
@@ -2144,22 +2143,6 @@ public final class LoanProductDataValidator {
}
}
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.",
@@ -318,88 +318,31 @@ final class FixedDepositProductsApiResourceSwagger {
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 @@ final class FixedDepositProductsApiResourceSwagger {
}
public GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge;
public GetFixedDepositProductsProductIdAccountingMappings.GetFixedDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount;
public GetFixedDepositProductsGlAccount incomeAccount;
}
static final class GetFixedDepositProductsProductIdPreClosurePenalInterestOnType {
@@ -343,88 +343,30 @@ final class RecurringDepositProductsApiResourceSwagger {
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 @@ final class RecurringDepositProductsApiResourceSwagger {
}
public GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge;
public GetRecurringDepositProductsProductIdAccountingMappings.GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount;
public GetRecurringDepositProductsGlAccount incomeAccount;
}
static final class GetRecurringDepositProductsProductIdPreClosurePenalInterestOnType {
@@ -226,88 +226,34 @@ final class SavingsProductsApiResourceSwagger {
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 @@ final class SavingsProductsApiResourceSwagger {
}
public GetSavingsProductsPenaltyToIncomeAccountMappingsCharge charge;
public GetSavingsProductsAccountingMappings.GetSavingsProductsIncomeFromPenaltyAccount incomeAccount;
public GetSavingsProductsGlAccount incomeAccount;
}
@Schema(example = "1")
@@ -71,35 +71,33 @@ import java.util.ArrayList;
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 @@ public class DepositProductDataValidator {
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 @@ public class DepositProductDataValidator {
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 @@ public class DepositProductDataValidator {
}
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 @@ public class DepositProductDataValidator {
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 @@ public class DepositProductDataValidator {
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 @@ public class DepositProductDataValidator {
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 @@ public class DepositProductDataValidator {
}
}
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);
@@ -43,6 +43,7 @@ import java.util.HashSet;
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.serialization.FromJsonHelper;
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 @@ public class SavingsAccountTransactionDataValidator {
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();
@@ -99,7 +99,6 @@ import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.CollectionUtils;
@RequiredArgsConstructor
public class DepositAccountReadPlatformServiceImpl implements DepositAccountReadPlatformService {
private static final FixedDepositAccountMapper FIXED_DEPOSIT_ACCOUNT_MAPPER = new FixedDepositAccountMapper();
@@ -611,7 +611,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
}
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 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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");
@@ -59,11 +59,7 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe
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;
}
+1
View File
@@ -25,6 +25,7 @@ dependencies {
// 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',
@@ -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());
}
}
}
}
@@ -50,9 +50,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroup
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.List;
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 @@ public class SavingsProductDataValidator {
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 @@ public class SavingsProductDataValidator {
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 @@ public class SavingsProductDataValidator {
}
}
/*
* 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 @@ public class SavingsProductDataValidator {
}
// 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 @@ public class SavingsProductDataValidator {
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 @@ public class SavingsProductDataValidator {
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 @@ public class SavingsProductDataValidator {
}
}
validatePaymentChannelFundSourceMappings(baseDataValidator, element);
validateChargeToIncomeAccountMappings(baseDataValidator, element);
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
@@ -558,74 +447,6 @@ public class SavingsProductDataValidator {
}
}
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);
@@ -46,6 +46,8 @@ import java.util.Locale;
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 @@ public class AccountingScenarioIntegrationTest {
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 @@ public class AccountingScenarioIntegrationTest {
@Test
public void checkAccountingWithSavingsFlow() {
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
@@ -373,10 +375,107 @@ public class AccountingScenarioIntegrationTest {
assertEquals(balance, summary.get("accountBalance"), "Verifying Balance");
}
@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 @@ public class AccountingScenarioIntegrationTest {
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 @@ public class AccountingScenarioIntegrationTest {
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 @@ public class AccountingScenarioIntegrationTest {
@Test
public void checkAccountingWithSharingFlow() {
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
@@ -25,6 +25,8 @@ import java.util.ArrayList;
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 @@ public class RecurringDepositProductHelper {
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 @@ public class RecurringDepositProductHelper {
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 --------------------------");
@@ -26,6 +26,8 @@ import io.restassured.specification.ResponseSpecification;
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 @@ public class SavingsProductHelper {
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 @@ public class SavingsProductHelper {
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 @@ public class SavingsProductHelper {
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 @@ public class SavingsProductHelper {
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 @@ public class SavingsProductHelper {
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 @@ public class SavingsProductHelper {
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);
}
}